feat(plugins): add marketplace banner, docs link and card polish

This commit is contained in:
t8y2 committed 2026-09-15 01:27:32 +08:00
1 parent a81ae9f455
commit 1ea822b80e
9 files changed
+80 -19

No files matched your search

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { computed, h, onBeforeUnmount, onMounted, ref, watch } from "vue";
import { Check, ChevronRight, CircleAlert, Download, ExternalLink, FileUp, FolderTree, Globe, LayoutGrid, Link2, List, Loader2, PackageCheck, Pencil, Plus, RefreshCw, RotateCcw, Search, Settings2, ShieldCheck, Store, Trash2 } from "@lucide/vue";
import { computed, defineComponent, h, onBeforeUnmount, onMounted, ref, watch } from "vue";
import { Check, ChevronRight, CircleAlert, Download, ExternalLink, FileUp, FolderTree, Globe, Info, LayoutGrid, Link2, List, Loader2, PackageCheck, Pencil, Plus, RefreshCw, RotateCcw, Search, Settings2, ShieldCheck, Store, Trash2 } from "@lucide/vue";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
@@ -32,15 +32,18 @@ const emit = defineEmits<{
}>();
// lucide no longer ships brand icons; mirror the inline glyph used in AppToolbar.
const GithubIcon = {
const GithubIcon = defineComponent({
props: {
iconClass: { type: String, default: "size-3" },
},
render() {
return h("svg", { class: "size-3", viewBox: "0 0 24 24", fill: "currentColor" }, [
return h("svg", { class: this.iconClass, viewBox: "0 0 24 24", fill: "currentColor" }, [
h("path", {
d: "M12 0C5.37 0 0 5.37 0 12c0 5.3 3.438 9.8 8.205 11.387.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61-.546-1.387-1.333-1.756-1.333-1.756-1.09-.745.083-.729.083-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 21.795 24 17.295 24 12 24 5.37 18.627 0 12 0z",
}),
]);
},
};
});
const PLUGIN_ALLOW_UNSIGNED_STORAGE_KEY = "dbx-plugin-allow-unsigned";
@@ -107,6 +110,7 @@ const filteredMarketplaceListings = computed(() => filterMarketplacePluginListin
const catalogErrors = computed(() => catalogResults.value.filter((result) => result.error));
const customRepositories = computed(() => repositories.value.filter((repository) => !repository.managed));
const showCustomRepositoryTrustSettings = computed(() => customRepositories.value.length > 0 || trustedKeys.value.length > 0);
const pluginDevelopmentDocsUrl = computed(() => `https://dbxio.com/${appLocale.value.startsWith("zh") ? "cn" : "en"}/docs/plugin-development`);
function openExternal(url?: string) {
const target = url?.trim();
@@ -559,6 +563,13 @@ onBeforeUnmount(() => {
<TabsContent value="marketplace" class="m-0 min-h-0 flex-1 overflow-y-auto">
<div class="flex min-h-full w-full flex-col gap-4 pb-2">
<div class="flex items-start gap-2.5 rounded-xl border bg-card/70 px-4 py-3">
<Info class="mt-0.5 size-3.5 shrink-0 text-primary" />
<div class="min-w-0 flex-1 text-xs leading-5 text-muted-foreground">
<span class="font-medium text-foreground">{{ t("pluginPlatform.marketplaceGuideTitle") }}</span>
<span class="mx-1.5 text-border">·</span>{{ t("pluginPlatform.marketplaceGuideDescription") }}
</div>
</div>
<div class="flex w-full flex-col gap-2 rounded-xl border bg-card/70 p-3 sm:flex-row sm:items-center">
<div class="relative">
<Search class="pointer-events-none absolute left-2.5 top-2.5 size-3.5 text-muted-foreground" />
@@ -622,15 +633,17 @@ onBeforeUnmount(() => {
</div>
<div class="mt-1 flex min-w-0 items-center gap-1 text-[11px] text-muted-foreground">
<span class="truncate">{{ listing.plugin.publisher }} · {{ listing.repository.name }}</span>
<button v-if="listing.plugin.source" type="button" class="shrink-0 rounded p-0.5 opacity-60 transition-opacity hover:opacity-100" :title="t('pluginPlatform.sourceRepository')" :aria-label="t('pluginPlatform.sourceRepository')" @click.stop="openExternal(listing.plugin.source)">
<GithubIcon />
</button>
<button v-if="listing.plugin.homepage" type="button" class="shrink-0 rounded p-0.5 opacity-60 transition-opacity hover:opacity-100" :title="t('pluginPlatform.pluginHomepage')" :aria-label="t('pluginPlatform.pluginHomepage')" @click.stop="openExternal(listing.plugin.homepage)">
<Globe class="size-3" />
</button>
</div>
</div>
<Badge variant="outline" class="h-5 px-1.5 text-[10px]">v{{ listing.plugin.latestVersion }}</Badge>
<div class="flex shrink-0 items-center gap-1">
<button v-if="listing.plugin.source" type="button" class="rounded p-0.5 opacity-60 transition-opacity hover:opacity-100" :title="t('pluginPlatform.sourceRepository')" :aria-label="t('pluginPlatform.sourceRepository')" @click.stop="openExternal(listing.plugin.source)">
<GithubIcon icon-class="size-3.5" />
</button>
<button v-if="listing.plugin.homepage" type="button" class="rounded p-0.5 opacity-60 transition-opacity hover:opacity-100" :title="t('pluginPlatform.pluginHomepage')" :aria-label="t('pluginPlatform.pluginHomepage')" @click.stop="openExternal(listing.plugin.homepage)">
<Globe class="size-3.5" />
</button>
<Badge variant="outline" class="h-5 px-1.5 text-[10px]">v{{ listing.plugin.latestVersion }}</Badge>
</div>
</div>
<p class="mt-3 line-clamp-3 text-xs leading-5 text-muted-foreground">{{ listing.description || t("pluginPlatform.noDescription") }}</p>
<div class="mt-3 flex flex-wrap gap-1.5">
@@ -667,16 +680,16 @@ onBeforeUnmount(() => {
<div class="flex min-w-0 items-center gap-2">
<span class="truncate text-sm font-semibold">{{ listing.name }}</span>
<Badge v-if="listing.verified" variant="secondary" class="hidden h-5 shrink-0 gap-1 px-1.5 text-[10px] sm:inline-flex"><ShieldCheck class="size-3" />{{ t("pluginPlatform.verified") }}</Badge>
<button v-if="listing.plugin.source" type="button" class="shrink-0 rounded p-0.5 opacity-60 transition-opacity hover:opacity-100" :title="t('pluginPlatform.sourceRepository')" :aria-label="t('pluginPlatform.sourceRepository')" @click.stop="openExternal(listing.plugin.source)">
<GithubIcon icon-class="size-3.5" />
</button>
<button v-if="listing.plugin.homepage" type="button" class="shrink-0 rounded p-0.5 opacity-60 transition-opacity hover:opacity-100" :title="t('pluginPlatform.pluginHomepage')" :aria-label="t('pluginPlatform.pluginHomepage')" @click.stop="openExternal(listing.plugin.homepage)">
<Globe class="size-3.5" />
</button>
<Badge variant="outline" class="h-5 shrink-0 px-1.5 text-[10px]">v{{ listing.plugin.latestVersion }}</Badge>
</div>
<div class="mt-0.5 flex min-w-0 items-center gap-1 text-[11px] text-muted-foreground">
<span class="truncate">{{ listing.plugin.publisher }} · {{ listing.repository.name }}</span>
<button v-if="listing.plugin.source" type="button" class="shrink-0 rounded p-0.5 opacity-60 transition-opacity hover:opacity-100" :title="t('pluginPlatform.sourceRepository')" :aria-label="t('pluginPlatform.sourceRepository')" @click.stop="openExternal(listing.plugin.source)">
<GithubIcon />
</button>
<button v-if="listing.plugin.homepage" type="button" class="shrink-0 rounded p-0.5 opacity-60 transition-opacity hover:opacity-100" :title="t('pluginPlatform.pluginHomepage')" :aria-label="t('pluginPlatform.pluginHomepage')" @click.stop="openExternal(listing.plugin.homepage)">
<Globe class="size-3" />
</button>
</div>
<p class="mt-1 truncate text-xs text-muted-foreground">{{ listing.description || t("pluginPlatform.noDescription") }}</p>
</div>
@@ -794,7 +807,7 @@ onBeforeUnmount(() => {
<Button size="sm" class="gap-1.5" @click="createConnection"><Plus class="size-3.5" />{{ t("pluginPlatform.newConnection") }}</Button>
<div v-for="connection in providerConnections" :key="connection.id" class="inline-flex items-center">
<Button size="sm" class="rounded-r-none" :variant="selectedConnectionId === connection.id ? 'secondary' : 'outline'" @click="selectConnection(connection.id)">{{ connection.name }}</Button>
<Button size="icon" variant="outline" class="h-8 w-8 rounded-l-none border-l-0" :title="t('common.edit')" :aria-label="t('common.edit')" @click="editConnection(connection.id)"><Pencil class="size-3.5" /></Button>
<Button size="icon-sm" class="rounded-l-none border-l-0" :variant="selectedConnectionId === connection.id ? 'secondary' : 'outline'" :title="t('common.edit')" :aria-label="t('common.edit')" @click="editConnection(connection.id)"><Pencil class="size-3.5" /></Button>
</div>
</div>
<div class="rounded-md border border-dashed bg-muted/20 p-3 text-xs leading-5 text-muted-foreground">{{ t("pluginPlatform.connectionManagedInDialog") }}</div>
@@ -904,6 +917,14 @@ onBeforeUnmount(() => {
<div class="text-xs leading-5">{{ t("pluginPlatform.developerOptionsWarning") }}</div>
</div>
<div class="flex items-center justify-between gap-4 rounded-lg border bg-background p-3">
<div class="min-w-0">
<div class="text-xs font-medium">{{ t("pluginPlatform.pluginDevelopmentDocsTitle") }}</div>
<div class="mt-1 text-[11px] leading-5 text-muted-foreground">{{ t("pluginPlatform.pluginDevelopmentDocsDescription") }}</div>
</div>
<Button size="sm" variant="outline" class="shrink-0 gap-1.5" @click="openExternal(pluginDevelopmentDocsUrl)"><ExternalLink class="size-3.5" />{{ t("pluginPlatform.pluginDevelopmentDocsOpen") }}</Button>
</div>
<div class="flex items-start justify-between gap-4 rounded-lg border bg-background p-3">
<div class="min-w-0">
<Label for="allow-unsigned-plugin-package" class="text-xs font-medium">{{ t("pluginPlatform.allowUnsignedDevelopmentPackage") }}</Label>