@nextclaw/ui 0.12.24 → 0.12.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +136 -29
- package/dist/assets/api-DGD9_Bg4.js +15 -0
- package/dist/assets/app-manager-provider-oYdeYPSv.js +1 -0
- package/dist/assets/{book-open-DDlN5MvX.js → book-open-BcnAiKde.js} +1 -1
- package/dist/assets/channels-list-page-HgLgrEg4.js +8 -0
- package/dist/assets/chat-page-DAKMFDrS.js +1 -0
- package/dist/assets/config-split-page-CcrEUtwu.js +1 -0
- package/dist/assets/cpu-DPPwMzoC.js +3 -0
- package/dist/assets/{createLucideIcon-BLMK3QUd.js → createLucideIcon-DzY6wN61.js} +1 -1
- package/dist/assets/desktop-DVUbOWbR.js +3 -0
- package/dist/assets/desktop-update-config-CP8dFYXK.js +1 -0
- package/dist/assets/{dialog-C3D7Be0p.js → dialog-BKo0RItd.js} +1 -1
- package/dist/assets/{dist-CPlbUgwU.js → dist-CFiwgaLs.js} +1 -1
- package/dist/assets/doc-browser-CAhfnm0D.js +1 -0
- package/dist/assets/{doc-browser-context-BJuMaI3o.js → doc-browser-context-FukQHvyo.js} +1 -1
- package/dist/assets/doc-browser-p9DDNPWB.js +1 -0
- package/dist/assets/doc-browser-rZIQIjuw.js +1 -0
- package/dist/assets/download-CMM8po31.js +1 -0
- package/dist/assets/{es2015-xqN1slyW.js → es2015-BhznEEyJ.js} +1 -1
- package/dist/assets/{external-link-DwfSfTLB.js → external-link-CpEvG65F.js} +1 -1
- package/dist/assets/i18n-D1144VAA.js +1 -0
- package/dist/assets/index-Cuwst6cc.js +100 -0
- package/dist/assets/index-dlcqieQ0.css +1 -0
- package/dist/assets/{key-round-CJ5gDAAG.js → key-round-DUq47t0P.js} +1 -1
- package/dist/assets/marketplace-page-BeFbwxR-.js +105 -0
- package/dist/assets/marketplace-page-CR4xq-TM.js +1 -0
- package/dist/assets/mcp-marketplace-page-DlRrSCj3.js +1 -0
- package/dist/assets/mcp-marketplace-page-DwnaLNTx.js +40 -0
- package/dist/assets/model-config-L2l6YAlQ.js +1 -0
- package/dist/assets/{notice-card-BFDbKQDA.js → notice-card-Dr6xCwva.js} +1 -1
- package/dist/assets/play-AqrNslHI.js +1 -0
- package/dist/assets/plus-B-YHtTNC.js +1 -0
- package/dist/assets/{popover-B86Dbfhf.js → popover-BDFNiLlg.js} +1 -1
- package/dist/assets/provider-scoped-model-input-BMTp4BEH.js +1 -0
- package/dist/assets/providers-list-DYAEunOp.js +1 -0
- package/dist/assets/refresh-cw-CrbD8EkT.js +1 -0
- package/dist/assets/remote-Dr3jcfWP.js +1 -0
- package/dist/assets/{rotate-cw-BZ2JObNs.js → rotate-cw-BN9yjccP.js} +1 -1
- package/dist/assets/runtime-config-page-BdeU8PEK.js +1 -0
- package/dist/assets/{save-euRxl8pI.js → save-CO_4qf6b.js} +1 -1
- package/dist/assets/{search-CLd7m0M7.js → search-CRtQwr-h.js} +1 -1
- package/dist/assets/search-config-CQUhd5RU.js +1 -0
- package/dist/assets/secrets-config-D-NWlW9q.js +3 -0
- package/dist/assets/{select-CJ0wbo3D.js → select-BUTwE_lC.js} +1 -1
- package/dist/assets/{setting-row-D1Yygqp7.js → setting-row-BavcnXw1.js} +1 -1
- package/dist/assets/settings-MWL2SMyk.js +1 -0
- package/dist/assets/{sparkles-DVfeSVJQ.js → sparkles-BmgOD4nY.js} +1 -1
- package/dist/assets/{status-dot-ChvPCib9.js → status-dot-l3kPFdq_.js} +1 -1
- package/dist/assets/{tabs-custom-Hia_ong0.js → tabs-custom-D48zdZoc.js} +1 -1
- package/dist/assets/{tag-chip-FrkmkT8r.js → tag-chip-Dm2Lqnpu.js} +1 -1
- package/dist/assets/use-config-Cyv5IuSt.js +1 -0
- package/dist/assets/use-infinite-scroll-loader-CFVdPpNv.js +1 -0
- package/dist/assets/x-BeyYA_h6.js +1 -0
- package/dist/index.html +29 -40
- package/package.json +9 -9
- package/src/app/components/layout/sidebar.layout.test.tsx +2 -4
- package/src/app/components/theme-provider.tsx +1 -0
- package/src/app/configs/app-navigation.config.ts +0 -6
- package/src/app/index.tsx +4 -7
- package/src/features/agents/components/agents-page.test.tsx +25 -15
- package/src/features/agents/components/agents-page.tsx +133 -172
- package/src/features/channels/components/config/channel-form.test.tsx +1 -0
- package/src/features/channels/components/config/channel-form.tsx +4 -3
- package/src/features/channels/components/config/weixin-channel-auth-section.test.tsx +38 -1
- package/src/features/channels/components/config/weixin-channel-auth-section.tsx +137 -40
- package/src/features/channels/index.ts +1 -1
- package/src/features/channels/utils/channel-form-fields.utils.test.ts +26 -0
- package/src/features/channels/utils/channel-form-fields.utils.ts +32 -18
- package/src/features/chat/components/chat-session-workspace-panel-nav.tsx +23 -4
- package/src/features/chat/components/chat-session-workspace-panel.tsx +53 -35
- package/src/features/chat/components/chat-sidebar-session-item.tsx +16 -12
- package/src/features/chat/components/conversation/chat-conversation-header.test.tsx +74 -0
- package/src/features/chat/components/conversation/chat-conversation-header.tsx +8 -2
- package/src/features/chat/components/conversation/chat-conversation-panel.test.tsx +262 -114
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +210 -174
- package/src/features/chat/components/conversation/chat-input-bar.container.tsx +11 -1
- package/src/features/chat/components/conversation/session-header/chat-session-header-actions.test.tsx +24 -0
- package/src/features/chat/components/conversation/session-header/chat-session-header-actions.tsx +27 -6
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +174 -0
- package/src/features/chat/components/layout/chat-sidebar.test.tsx +45 -8
- package/src/features/chat/components/layout/chat-sidebar.tsx +29 -46
- package/src/features/chat/components/providers/chat-presenter.provider.tsx +4 -0
- package/src/features/chat/components/workspace/session-cron-job-content.tsx +103 -0
- package/src/features/chat/hooks/use-ncp-agent-runtime.test.tsx +153 -80
- package/src/features/chat/hooks/use-ncp-chat-page-data.test.tsx +70 -0
- package/src/features/chat/hooks/use-ncp-chat-page-data.ts +1 -1
- package/src/features/chat/hooks/use-ncp-child-session-tabs-view.ts +2 -8
- package/src/features/chat/hooks/use-ncp-session-list-view.ts +1 -2
- package/src/features/chat/managers/chat-session-list.manager.test.ts +7 -9
- package/src/features/chat/managers/chat-session-list.manager.ts +5 -10
- package/src/features/chat/managers/ncp-chat-input.manager.test.ts +20 -2
- package/src/features/chat/managers/ncp-chat-input.manager.ts +18 -0
- package/src/features/chat/managers/ncp-chat-presenter.manager.ts +7 -0
- package/src/features/chat/managers/ncp-chat-thread.manager.test.ts +52 -1
- package/src/features/chat/managers/ncp-chat-thread.manager.ts +21 -0
- package/src/features/chat/pages/ncp-chat-page.tsx +9 -5
- package/src/features/chat/stores/chat-input.store.ts +3 -1
- package/src/features/chat/stores/chat-session-list.store.ts +0 -2
- package/src/features/chat/stores/chat-thread.store.ts +4 -0
- package/src/features/chat/utils/chat-session-display.utils.test.ts +83 -1
- package/src/features/chat/utils/chat-session-display.utils.ts +73 -0
- package/src/features/chat/utils/ncp-chat-input-availability.utils.test.ts +1 -0
- package/src/features/chat/utils/ncp-session-adapter.utils.test.ts +22 -0
- package/src/features/chat/utils/ncp-session-adapter.utils.ts +32 -0
- package/src/features/marketplace/components/curated-shelves/marketplace-curated-scene-route.test.tsx +235 -0
- package/src/features/marketplace/components/curated-shelves/marketplace-curated-shelves.config.ts +162 -0
- package/src/features/marketplace/components/curated-shelves/marketplace-curated-shelves.tsx +355 -0
- package/src/features/marketplace/components/curated-shelves/marketplace-shelf-card.tsx +118 -0
- package/src/features/marketplace/components/detail-doc/marketplace-detail-doc-renderer.ts +201 -0
- package/src/features/marketplace/components/detail-doc/marketplace-detail-doc.test.ts +40 -0
- package/src/features/marketplace/components/marketplace-catalog-grid.tsx +114 -0
- package/src/features/marketplace/components/marketplace-detail-doc.ts +73 -24
- package/src/features/marketplace/components/marketplace-item-icon.tsx +45 -0
- package/src/features/marketplace/components/marketplace-list-card.tsx +177 -93
- package/src/features/marketplace/components/marketplace-page-detail.test.tsx +9 -2
- package/src/features/marketplace/components/marketplace-page-parts.tsx +1 -1
- package/src/features/marketplace/components/marketplace-page.test.tsx +25 -6
- package/src/features/marketplace/components/marketplace-page.tsx +154 -132
- package/src/features/marketplace/hooks/use-marketplace-curated-scene-route.ts +97 -0
- package/src/features/marketplace/hooks/use-marketplace.ts +59 -3
- package/src/features/system-status/components/config/runtime-agent-list-card.tsx +4 -8
- package/src/features/system-status/components/config/runtime-binding-list-card.tsx +5 -7
- package/src/features/system-status/components/config/runtime-config-editor.tsx +1 -19
- package/src/features/system-status/components/config/runtime-entry-list-card.tsx +10 -11
- package/src/features/system-status/components/config/runtime-settings-card.tsx +15 -23
- package/src/features/system-status/components/runtime-control-card.test.tsx +8 -6
- package/src/features/system-status/components/runtime-control-card.tsx +7 -6
- package/src/features/system-status/pages/runtime-config-page.test.tsx +19 -9
- package/src/features/system-status/pages/runtime-config-page.tsx +2 -3
- package/src/features/system-status/utils/runtime-config-agent.utils.ts +4 -4
- package/src/features/system-status/utils/system-status.utils.ts +31 -6
- package/src/index.css +8 -0
- package/src/platforms/desktop/components/desktop-app-shell.test.tsx +68 -0
- package/src/platforms/desktop/components/desktop-app-shell.tsx +46 -18
- package/src/platforms/desktop/components/desktop-window-chrome.tsx +30 -0
- package/src/platforms/desktop/index.ts +6 -0
- package/src/platforms/desktop/types/desktop-update.types.ts +3 -0
- package/src/platforms/desktop/utils/desktop-host.utils.ts +56 -0
- package/src/shared/components/common/brand-header.tsx +36 -16
- package/src/shared/components/config/provider-form-support.ts +2 -22
- package/src/shared/components/cron-config.tsx +12 -58
- package/src/shared/components/doc-browser/doc-browser.tsx +4 -4
- package/src/shared/components/ui/select.tsx +19 -7
- package/src/shared/lib/api/channel-auth.types.ts +1 -0
- package/src/shared/lib/api/ncp-session.types.ts +9 -0
- package/src/shared/lib/api/types.ts +12 -1
- package/src/shared/lib/api/utils/marketplace.utils.ts +7 -1
- package/src/shared/lib/cron/cron-job-view.utils.ts +59 -0
- package/src/shared/lib/cron/index.ts +1 -0
- package/src/shared/lib/i18n/{channel-auth.ts → channel-auth.constants.ts} +31 -0
- package/src/shared/lib/i18n/chat-labels.utils.ts +3 -2
- package/src/shared/lib/i18n/index.ts +20 -59
- package/src/shared/lib/i18n/{runtime-control.ts → runtime-control-labels.utils.ts} +30 -1
- package/src/shared/lib/provider-models/index.test.ts +39 -0
- package/src/shared/lib/provider-models/index.ts +1 -3
- package/src/shared/lib/ui-document-title/index.ts +0 -1
- package/tsconfig.json +1 -0
- package/vite.config.ts +1 -1
- package/vitest.config.ts +1 -1
- package/dist/assets/api-D2xRKmZd.js +0 -15
- package/dist/assets/app-manager-provider-CNaZboG4.js +0 -1
- package/dist/assets/app-navigation.config-Ihhrrt--.js +0 -1
- package/dist/assets/channels-list-page-p26lgxLk.js +0 -8
- package/dist/assets/chat-Dkh2qtuz.js +0 -61
- package/dist/assets/chat-page-DoTmE2wx.js +0 -1
- package/dist/assets/chunk-JZWAC4HX-Kydj4yEz.js +0 -3
- package/dist/assets/config-split-page-DIOCjj2Q.js +0 -1
- package/dist/assets/desktop-update-config-DlpzDfKM.js +0 -1
- package/dist/assets/doc-browser-C8FM5fC0.js +0 -1
- package/dist/assets/doc-browser-RJUOL_GO.js +0 -1
- package/dist/assets/doc-browser-p82AdNO-.js +0 -1
- package/dist/assets/folder-CeJKPx5P.js +0 -1
- package/dist/assets/hash-BqxRTZW5.js +0 -1
- package/dist/assets/i18n-DnTGDIRw.js +0 -1
- package/dist/assets/index-D8MKmXtO.css +0 -1
- package/dist/assets/index-pBvbJ5Mt.js +0 -2
- package/dist/assets/loader-circle-fd-vQKtW.js +0 -1
- package/dist/assets/logo-badge-KAe-7d8c.js +0 -1
- package/dist/assets/logos-C4sYP1Vl.js +0 -1
- package/dist/assets/marketplace-page-Cql0kDi-.js +0 -1
- package/dist/assets/marketplace-page-m4P5g_Ht.js +0 -49
- package/dist/assets/mcp-marketplace-page-9WVKl1m1.js +0 -1
- package/dist/assets/mcp-marketplace-page-ByzBQZcx.js +0 -40
- package/dist/assets/message-square-z_osm9c0.js +0 -1
- package/dist/assets/model-config-Dbr_0APb.js +0 -1
- package/dist/assets/play-Dv6Nr1Ew.js +0 -1
- package/dist/assets/plus-D8eKFY7h.js +0 -1
- package/dist/assets/provider-scoped-model-input-DFm6N2f7.js +0 -1
- package/dist/assets/providers-list-BJcLOjun.js +0 -1
- package/dist/assets/refresh-ccw-ByVwmnN_.js +0 -1
- package/dist/assets/refresh-cw-PcqoYB3K.js +0 -1
- package/dist/assets/remote-BOxo9iwd.js +0 -1
- package/dist/assets/runtime-config-page-CjLhnbSl.js +0 -1
- package/dist/assets/search-config-J4Htco-P.js +0 -1
- package/dist/assets/secrets-config-CUdERjco.js +0 -3
- package/dist/assets/sessions-config-page-DpK991fs.js +0 -2
- package/dist/assets/settings-drbWqzA4.js +0 -1
- package/dist/assets/skeleton-BK1SOSRA.js +0 -1
- package/dist/assets/theme-provider-0hxjiPc_.js +0 -2
- package/dist/assets/tooltip-Cj4yA0gH.js +0 -1
- package/dist/assets/trash-2-CBsHCfqq.js +0 -1
- package/dist/assets/use-config-38Ur-89i.js +0 -1
- package/dist/assets/use-confirm-dialog-DPQThaeU.js +0 -1
- package/dist/assets/use-infinite-scroll-loader-5Gf1xQi7.js +0 -1
- package/dist/assets/use-viewport-layout-D1XzKeip.js +0 -1
- package/dist/assets/x-CM-XDMpk.js +0 -1
- package/src/features/chat/components/config/sessions-config-detail-pane.tsx +0 -244
- package/src/features/chat/pages/sessions-config-page.test.tsx +0 -152
- package/src/features/chat/pages/sessions-config-page.tsx +0 -192
- /package/dist/assets/{config-hints-MogHYQ8G.js → config-hints-BNfpOL4J.js} +0 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { buildGenericDetailDataUrl } from "@/features/marketplace/components/marketplace-detail-doc";
|
|
2
|
+
|
|
3
|
+
function readDetailHtml(params: Parameters<typeof buildGenericDetailDataUrl>[0]) {
|
|
4
|
+
return decodeURIComponent(buildGenericDetailDataUrl(params));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
describe("buildGenericDetailDataUrl", () => {
|
|
8
|
+
it("renders skill metadata and markdown content semantically", () => {
|
|
9
|
+
const html = readDetailHtml({
|
|
10
|
+
title: "Weather Skill",
|
|
11
|
+
typeLabel: "Skill",
|
|
12
|
+
spec: "@nextclaw/weather",
|
|
13
|
+
metadataRaw: "name: weather\ndescription: Local weather skill",
|
|
14
|
+
contentRaw: "# Weather Skill\n\nUse **weather** with `city`.\n\n- Local forecast\n- Severe alerts",
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
expect(html).toContain('<dl class="metadata-list">');
|
|
18
|
+
expect(html).toContain("<dt>name</dt><dd>weather</dd>");
|
|
19
|
+
expect(html).toContain("<h1>Weather Skill</h1>");
|
|
20
|
+
expect(html).toContain("<strong>weather</strong>");
|
|
21
|
+
expect(html).toContain("<code>city</code>");
|
|
22
|
+
expect(html).toContain("<li>Local forecast</li>");
|
|
23
|
+
expect(html).not.toContain('<pre class="code"># Weather Skill');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("escapes marketplace content before rendering markdown", () => {
|
|
27
|
+
const html = readDetailHtml({
|
|
28
|
+
title: "Unsafe Skill",
|
|
29
|
+
typeLabel: "Skill",
|
|
30
|
+
spec: "@nextclaw/unsafe",
|
|
31
|
+
metadataRaw: '{"name":"unsafe","nested":{"script":"<script>alert(1)</script>"}}',
|
|
32
|
+
contentRaw: "[safe](https://example.com) <script>alert(1)</script>",
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
expect(html).toContain("<dt>nested</dt>");
|
|
36
|
+
expect(html).toContain("<script>alert(1)</script>");
|
|
37
|
+
expect(html).toContain('<a href="https://example.com" target="_blank" rel="noopener noreferrer">safe</a>');
|
|
38
|
+
expect(html).not.toContain("<script>alert(1)</script>");
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MarketplaceInstalledRecord,
|
|
3
|
+
MarketplaceItemSummary,
|
|
4
|
+
MarketplaceManageAction,
|
|
5
|
+
} from "@/shared/lib/api";
|
|
6
|
+
import {
|
|
7
|
+
MarketplaceListCard,
|
|
8
|
+
type InstallState,
|
|
9
|
+
type ManageState,
|
|
10
|
+
} from "@/features/marketplace/components/marketplace-list-card";
|
|
11
|
+
import { MarketplaceListSkeleton } from "@/features/marketplace/components/marketplace-page-parts";
|
|
12
|
+
import {
|
|
13
|
+
findInstalledRecordForItem,
|
|
14
|
+
type InstalledRenderEntry,
|
|
15
|
+
} from "@/features/marketplace/components/marketplace-page-data";
|
|
16
|
+
import { cn } from "@/shared/lib/utils";
|
|
17
|
+
|
|
18
|
+
type MarketplaceCatalogGridProps = {
|
|
19
|
+
scope: "all" | "installed";
|
|
20
|
+
title: string;
|
|
21
|
+
summary: string;
|
|
22
|
+
showTitle: boolean;
|
|
23
|
+
showListSkeleton: boolean;
|
|
24
|
+
skeletonCardCount: number;
|
|
25
|
+
allItems: MarketplaceItemSummary[];
|
|
26
|
+
installedEntries: InstalledRenderEntry[];
|
|
27
|
+
installedRecordLookup: Map<string, MarketplaceInstalledRecord>;
|
|
28
|
+
language: string;
|
|
29
|
+
installState: InstallState;
|
|
30
|
+
manageState: ManageState;
|
|
31
|
+
onOpen: (item?: MarketplaceItemSummary, record?: MarketplaceInstalledRecord) => void;
|
|
32
|
+
onInstall: (item: MarketplaceItemSummary) => void;
|
|
33
|
+
onManage: (
|
|
34
|
+
action: MarketplaceManageAction,
|
|
35
|
+
record: MarketplaceInstalledRecord,
|
|
36
|
+
) => void;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function MarketplaceCatalogGrid(props: MarketplaceCatalogGridProps) {
|
|
40
|
+
const {
|
|
41
|
+
scope,
|
|
42
|
+
title,
|
|
43
|
+
summary,
|
|
44
|
+
showTitle,
|
|
45
|
+
showListSkeleton,
|
|
46
|
+
skeletonCardCount,
|
|
47
|
+
allItems,
|
|
48
|
+
installedEntries,
|
|
49
|
+
installedRecordLookup,
|
|
50
|
+
language,
|
|
51
|
+
installState,
|
|
52
|
+
manageState,
|
|
53
|
+
onOpen,
|
|
54
|
+
onInstall,
|
|
55
|
+
onManage,
|
|
56
|
+
} = props;
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<section className={cn("flex min-h-full flex-col", showTitle && "gap-3")}>
|
|
60
|
+
{showTitle && (
|
|
61
|
+
<div className="flex items-center justify-between gap-3">
|
|
62
|
+
<h3 className="text-[14px] font-semibold text-gray-950">{title}</h3>
|
|
63
|
+
<span className="text-[12px] text-gray-500">{summary}</span>
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
|
|
67
|
+
<div
|
|
68
|
+
data-testid={showListSkeleton ? "marketplace-list-skeleton" : undefined}
|
|
69
|
+
className={cn(
|
|
70
|
+
"grid grid-cols-1 gap-3 lg:grid-cols-2 2xl:grid-cols-3",
|
|
71
|
+
showListSkeleton && "min-h-0 flex-1 auto-rows-[104px] content-start",
|
|
72
|
+
)}
|
|
73
|
+
>
|
|
74
|
+
{showListSkeleton && (
|
|
75
|
+
<MarketplaceListSkeleton count={skeletonCardCount} />
|
|
76
|
+
)}
|
|
77
|
+
|
|
78
|
+
{!showListSkeleton &&
|
|
79
|
+
scope === "all" &&
|
|
80
|
+
allItems.map((item) => (
|
|
81
|
+
<MarketplaceListCard
|
|
82
|
+
key={item.id}
|
|
83
|
+
item={item}
|
|
84
|
+
record={findInstalledRecordForItem(item, installedRecordLookup)}
|
|
85
|
+
language={language}
|
|
86
|
+
installState={installState}
|
|
87
|
+
manageState={manageState}
|
|
88
|
+
onOpen={() =>
|
|
89
|
+
onOpen(item, findInstalledRecordForItem(item, installedRecordLookup))
|
|
90
|
+
}
|
|
91
|
+
onInstall={onInstall}
|
|
92
|
+
onManage={onManage}
|
|
93
|
+
/>
|
|
94
|
+
))}
|
|
95
|
+
|
|
96
|
+
{!showListSkeleton &&
|
|
97
|
+
scope === "installed" &&
|
|
98
|
+
installedEntries.map((entry) => (
|
|
99
|
+
<MarketplaceListCard
|
|
100
|
+
key={entry.key}
|
|
101
|
+
item={entry.item}
|
|
102
|
+
record={entry.record}
|
|
103
|
+
language={language}
|
|
104
|
+
installState={installState}
|
|
105
|
+
manageState={manageState}
|
|
106
|
+
onOpen={() => onOpen(entry.item, entry.record)}
|
|
107
|
+
onInstall={onInstall}
|
|
108
|
+
onManage={onManage}
|
|
109
|
+
/>
|
|
110
|
+
))}
|
|
111
|
+
</div>
|
|
112
|
+
</section>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
.replace(/"/g, """)
|
|
7
|
-
.replace(/'/g, "'");
|
|
8
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
escapeHtml,
|
|
3
|
+
renderDetailMarkdown,
|
|
4
|
+
renderDetailMetadata,
|
|
5
|
+
} from "@/features/marketplace/components/detail-doc/marketplace-detail-doc-renderer";
|
|
9
6
|
|
|
10
7
|
export function buildGenericDetailDataUrl(params: {
|
|
11
8
|
title: string;
|
|
@@ -19,6 +16,7 @@ export function buildGenericDetailDataUrl(params: {
|
|
|
19
16
|
sourceLabel?: string;
|
|
20
17
|
tags?: string[];
|
|
21
18
|
author?: string;
|
|
19
|
+
loading?: boolean;
|
|
22
20
|
}): string {
|
|
23
21
|
const {
|
|
24
22
|
title,
|
|
@@ -32,12 +30,15 @@ export function buildGenericDetailDataUrl(params: {
|
|
|
32
30
|
sourceLabel,
|
|
33
31
|
tags,
|
|
34
32
|
author,
|
|
33
|
+
loading,
|
|
35
34
|
} = params;
|
|
36
35
|
const metadata = metadataRaw?.trim() || "-";
|
|
37
36
|
const content = contentRaw?.trim() || "-";
|
|
38
37
|
const summary = rawSummary?.trim();
|
|
39
38
|
const description = rawDescription?.trim();
|
|
40
39
|
const shouldShowDescription = Boolean(description) && description !== summary;
|
|
40
|
+
const renderedMetadata = renderDetailMetadata(metadata);
|
|
41
|
+
const renderedContent = renderDetailMarkdown(content);
|
|
41
42
|
|
|
42
43
|
const html = `<!doctype html>
|
|
43
44
|
<html>
|
|
@@ -47,25 +48,72 @@ export function buildGenericDetailDataUrl(params: {
|
|
|
47
48
|
<title>${escapeHtml(title)}</title>
|
|
48
49
|
<style>
|
|
49
50
|
:root { color-scheme: light; }
|
|
50
|
-
body { margin: 0; background: #
|
|
51
|
-
.wrap { max-width:
|
|
52
|
-
.hero { border: 1px solid #
|
|
53
|
-
.hero h1 { margin: 0; font-size:
|
|
54
|
-
.meta { margin-top:
|
|
55
|
-
.summary { margin
|
|
56
|
-
.grid { display: grid; grid-template-columns:
|
|
57
|
-
.card { border: 1px solid #
|
|
58
|
-
.card h2 { margin: 0; padding:
|
|
59
|
-
.card .body { padding: 12px
|
|
60
|
-
.code { white-space: pre-wrap; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size:
|
|
51
|
+
body { margin: 0; background: #f9f8f5; color: #2f2212; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
|
|
52
|
+
.wrap { max-width: 940px; margin: 0 auto; padding: 24px 20px 36px; }
|
|
53
|
+
.hero { border: 1px solid #f0e2c8; border-radius: 14px; background: linear-gradient(180deg, #fff9f1 0%, #ffffff 28%); padding: 18px; box-shadow: 0 1px 3px rgba(30, 20, 10, 0.05); }
|
|
54
|
+
.hero h1 { margin: 0; font-size: 22px; line-height: 1.2; letter-spacing: 0; }
|
|
55
|
+
.meta { margin-top: 7px; color: #78644d; font-size: 12px; overflow-wrap: anywhere; word-break: break-word; }
|
|
56
|
+
.summary { margin: 12px 0 0; font-size: 13px; line-height: 1.65; color: #5f5142; }
|
|
57
|
+
.grid { display: grid; grid-template-columns: minmax(220px, 0.42fr) minmax(0, 1fr); gap: 12px; margin-top: 12px; }
|
|
58
|
+
.card { border: 1px solid #eee3d1; background: #fffdf9; border-radius: 12px; overflow: hidden; box-shadow: 0 1px 2px rgba(30, 20, 10, 0.035); }
|
|
59
|
+
.card h2 { margin: 0; padding: 11px 13px; font-size: 12px; font-weight: 650; color: #3f472f; border-bottom: 1px solid #f1e7d4; background: #fffaf2; }
|
|
60
|
+
.card .body { padding: 12px 13px; font-size: 12px; color: #4e463d; line-height: 1.65; }
|
|
61
|
+
.code { white-space: pre-wrap; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11.5px; line-height: 1.55; margin: 0; }
|
|
62
|
+
.metadata-list { margin: 0; }
|
|
63
|
+
.metadata-list div { display: grid; grid-template-columns: minmax(72px, 0.36fr) minmax(0, 1fr); gap: 10px; padding: 8px 0; border-bottom: 1px solid #f2eadc; }
|
|
64
|
+
.metadata-list div:last-child { border-bottom: 0; }
|
|
65
|
+
.metadata-list dt { color: #7a5a24; font-weight: 650; overflow-wrap: anywhere; }
|
|
66
|
+
.metadata-list dd { margin: 0; color: #4e463d; overflow-wrap: anywhere; }
|
|
67
|
+
.markdown { font-size: 13px; line-height: 1.68; }
|
|
68
|
+
.markdown > *:first-child { margin-top: 0; }
|
|
69
|
+
.markdown > *:last-child { margin-bottom: 0; }
|
|
70
|
+
.markdown h1, .markdown h2, .markdown h3, .markdown h4 { margin: 18px 0 8px; color: #2f2212; line-height: 1.25; letter-spacing: 0; }
|
|
71
|
+
.markdown h1 { font-size: 20px; }
|
|
72
|
+
.markdown h2 { font-size: 17px; }
|
|
73
|
+
.markdown h3 { font-size: 15px; }
|
|
74
|
+
.markdown h4 { font-size: 13px; }
|
|
75
|
+
.markdown p { margin: 10px 0; }
|
|
76
|
+
.markdown ul, .markdown ol { margin: 10px 0; padding-left: 20px; }
|
|
77
|
+
.markdown li { margin: 5px 0; }
|
|
78
|
+
.markdown blockquote { margin: 12px 0; padding: 8px 12px; border-left: 3px solid #d9b56f; border-radius: 8px; background: #fff7ea; color: #6d5841; }
|
|
79
|
+
.markdown code { border: 1px solid #eadcc6; border-radius: 5px; background: #fff7ea; padding: 1px 4px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11.5px; color: #6b4b16; }
|
|
80
|
+
.markdown a { color: #5f6b45; text-decoration: none; font-weight: 600; }
|
|
81
|
+
.markdown a:hover { text-decoration: underline; }
|
|
82
|
+
.code-block { position: relative; margin: 12px 0; overflow: hidden; border: 1px solid #eadcc6; border-radius: 10px; background: #2f2a24; }
|
|
83
|
+
.code-block pre { margin: 0; overflow-x: auto; padding: 13px; }
|
|
84
|
+
.code-block code { border: 0; border-radius: 0; background: transparent; padding: 0; color: #f7efe3; }
|
|
85
|
+
.code-language { position: absolute; right: 10px; top: 8px; color: #d8c3a0; font-size: 10px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
61
86
|
.tags { margin-top: 10px; }
|
|
62
|
-
.tag { display: inline-block; margin: 0 6px 6px 0; padding: 4px
|
|
63
|
-
.source { color: #
|
|
87
|
+
.tag { display: inline-block; margin: 0 6px 6px 0; padding: 4px 8px; border: 1px solid #ecd9b5; border-radius: 999px; background: #fff7ea; color: #7a5a24; font-size: 11px; }
|
|
88
|
+
.source { color: #5f6b45; text-decoration: none; overflow-wrap: anywhere; word-break: break-all; }
|
|
89
|
+
.source:hover { text-decoration: underline; }
|
|
90
|
+
.skeleton { display: block; border-radius: 8px; background: linear-gradient(90deg, #f0e6d6 0%, #fffaf2 42%, #f0e6d6 78%); background-size: 220% 100%; animation: shimmer 1.35s ease-in-out infinite; }
|
|
91
|
+
.detail-skeleton .hero { padding: 18px; }
|
|
92
|
+
.sk-title { width: 52%; height: 24px; }
|
|
93
|
+
.sk-meta { width: 78%; height: 12px; margin-top: 12px; }
|
|
94
|
+
.sk-line { height: 12px; margin-top: 12px; }
|
|
95
|
+
.sk-line.short { width: 62%; }
|
|
96
|
+
.sk-line.mid { width: 82%; }
|
|
97
|
+
.sk-body { height: 220px; margin: 13px; }
|
|
98
|
+
@keyframes shimmer { 0% { background-position: 120% 0; } 100% { background-position: -120% 0; } }
|
|
64
99
|
@media (max-width: 860px) { .grid { grid-template-columns: 1fr; } }
|
|
65
100
|
</style>
|
|
66
101
|
</head>
|
|
67
102
|
<body>
|
|
68
|
-
<main class="wrap">
|
|
103
|
+
<main class="wrap${loading ? " detail-skeleton" : ""}"${loading ? ' aria-busy="true"' : ""}>
|
|
104
|
+
${loading ? `
|
|
105
|
+
<section class="hero">
|
|
106
|
+
<span class="skeleton sk-title"></span>
|
|
107
|
+
<span class="skeleton sk-meta"></span>
|
|
108
|
+
<span class="skeleton sk-line mid"></span>
|
|
109
|
+
<span class="skeleton sk-line"></span>
|
|
110
|
+
<span class="skeleton sk-line short"></span>
|
|
111
|
+
</section>
|
|
112
|
+
<section class="grid">
|
|
113
|
+
<article class="card"><span class="skeleton sk-body"></span></article>
|
|
114
|
+
<article class="card"><span class="skeleton sk-body"></span></article>
|
|
115
|
+
</section>
|
|
116
|
+
` : `
|
|
69
117
|
<section class="hero">
|
|
70
118
|
<h1>${escapeHtml(title)}</h1>
|
|
71
119
|
<div class="meta">${escapeHtml(typeLabel)} · ${escapeHtml(spec)}${author ? ` · ${escapeHtml(author)}` : ""}</div>
|
|
@@ -78,13 +126,14 @@ export function buildGenericDetailDataUrl(params: {
|
|
|
78
126
|
<section class="grid">
|
|
79
127
|
<article class="card">
|
|
80
128
|
<h2>Metadata</h2>
|
|
81
|
-
<div class="body"
|
|
129
|
+
<div class="body">${renderedMetadata}</div>
|
|
82
130
|
</article>
|
|
83
131
|
<article class="card">
|
|
84
132
|
<h2>Content</h2>
|
|
85
|
-
<div class="body
|
|
133
|
+
<div class="body markdown">${renderedContent}</div>
|
|
86
134
|
</article>
|
|
87
135
|
</section>
|
|
136
|
+
`}
|
|
88
137
|
</main>
|
|
89
138
|
</body>
|
|
90
139
|
</html>`;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { cn } from "@/shared/lib/utils";
|
|
2
|
+
|
|
3
|
+
const MARKETPLACE_ITEM_ICON_COLORS = [
|
|
4
|
+
"bg-amber-600",
|
|
5
|
+
"bg-orange-500",
|
|
6
|
+
"bg-yellow-600",
|
|
7
|
+
"bg-emerald-600",
|
|
8
|
+
"bg-teal-600",
|
|
9
|
+
"bg-cyan-600",
|
|
10
|
+
"bg-stone-600",
|
|
11
|
+
"bg-rose-500",
|
|
12
|
+
"bg-violet-500",
|
|
13
|
+
] as const;
|
|
14
|
+
|
|
15
|
+
export function MarketplaceItemIcon(props: {
|
|
16
|
+
name?: string;
|
|
17
|
+
fallback: string;
|
|
18
|
+
className?: string;
|
|
19
|
+
}) {
|
|
20
|
+
const { name, fallback, className } = props;
|
|
21
|
+
const displayName = name || fallback;
|
|
22
|
+
const letters = displayName.substring(0, 2).toUpperCase();
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div
|
|
26
|
+
className={cn(
|
|
27
|
+
"flex h-10 w-10 shrink-0 items-center justify-center rounded-xl text-sm font-semibold text-white",
|
|
28
|
+
getMarketplaceItemIconColor(displayName),
|
|
29
|
+
className,
|
|
30
|
+
)}
|
|
31
|
+
>
|
|
32
|
+
{letters}
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getMarketplaceItemIconColor(text: string) {
|
|
38
|
+
let hash = 0;
|
|
39
|
+
for (let index = 0; index < text.length; index++) {
|
|
40
|
+
hash = text.charCodeAt(index) + ((hash << 5) - hash);
|
|
41
|
+
}
|
|
42
|
+
return MARKETPLACE_ITEM_ICON_COLORS[
|
|
43
|
+
Math.abs(hash) % MARKETPLACE_ITEM_ICON_COLORS.length
|
|
44
|
+
];
|
|
45
|
+
}
|
|
@@ -13,8 +13,16 @@ import {
|
|
|
13
13
|
buildLocaleFallbacks,
|
|
14
14
|
pickLocalizedText,
|
|
15
15
|
} from "@/features/marketplace/components/marketplace-localization";
|
|
16
|
+
import { MarketplaceItemIcon } from "@/features/marketplace/components/marketplace-item-icon";
|
|
16
17
|
import { t } from "@/shared/lib/i18n";
|
|
17
18
|
import { cn } from "@/shared/lib/utils";
|
|
19
|
+
import {
|
|
20
|
+
CheckCircle2,
|
|
21
|
+
Download,
|
|
22
|
+
Power,
|
|
23
|
+
PowerOff,
|
|
24
|
+
Trash2,
|
|
25
|
+
} from "lucide-react";
|
|
18
26
|
|
|
19
27
|
export type InstallState = {
|
|
20
28
|
installingSpecs: ReadonlySet<string>;
|
|
@@ -24,42 +32,22 @@ export type ManageState = {
|
|
|
24
32
|
actionsByTarget: ReadonlyMap<string, MarketplaceManageAction>;
|
|
25
33
|
};
|
|
26
34
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
return ITEM_ICON_COLORS[Math.abs(hash) % ITEM_ICON_COLORS.length];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function ItemIcon({ name, fallback }: { name?: string; fallback: string }) {
|
|
48
|
-
const displayName = name || fallback;
|
|
49
|
-
const letters = displayName.substring(0, 2).toUpperCase();
|
|
50
|
-
const colorClass = getAvatarColor(displayName);
|
|
51
|
-
|
|
52
|
-
return (
|
|
53
|
-
<div
|
|
54
|
-
className={cn(
|
|
55
|
-
"flex h-10 w-10 shrink-0 items-center justify-center rounded-xl text-sm font-semibold text-white",
|
|
56
|
-
colorClass,
|
|
57
|
-
)}
|
|
58
|
-
>
|
|
59
|
-
{letters}
|
|
60
|
-
</div>
|
|
61
|
-
);
|
|
62
|
-
}
|
|
35
|
+
type MarketplaceListCardActionProps = {
|
|
36
|
+
item?: MarketplaceItemSummary;
|
|
37
|
+
record?: MarketplaceInstalledRecord;
|
|
38
|
+
pluginRecord?: MarketplaceInstalledRecord;
|
|
39
|
+
isInstalling: boolean;
|
|
40
|
+
isDisabled: boolean;
|
|
41
|
+
canUninstall: boolean;
|
|
42
|
+
busyAction?: MarketplaceManageAction;
|
|
43
|
+
busyForRecord: boolean;
|
|
44
|
+
language: string;
|
|
45
|
+
onInstall: (item: MarketplaceItemSummary) => void;
|
|
46
|
+
onManage: (
|
|
47
|
+
action: MarketplaceManageAction,
|
|
48
|
+
record: MarketplaceInstalledRecord,
|
|
49
|
+
) => void;
|
|
50
|
+
};
|
|
63
51
|
|
|
64
52
|
function MarketplaceListCardMeta({
|
|
65
53
|
title,
|
|
@@ -98,17 +86,144 @@ function MarketplaceListCardMeta({
|
|
|
98
86
|
) : null}
|
|
99
87
|
</div>
|
|
100
88
|
|
|
89
|
+
<p className="line-clamp-2 text-left text-[12px] leading-relaxed text-gray-500/90">
|
|
90
|
+
{summary}
|
|
91
|
+
</p>
|
|
92
|
+
</TooltipProvider>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function MarketplaceListCardActions(props: MarketplaceListCardActionProps) {
|
|
97
|
+
const {
|
|
98
|
+
item,
|
|
99
|
+
record,
|
|
100
|
+
pluginRecord,
|
|
101
|
+
isInstalling,
|
|
102
|
+
isDisabled,
|
|
103
|
+
canUninstall,
|
|
104
|
+
busyAction,
|
|
105
|
+
busyForRecord,
|
|
106
|
+
language,
|
|
107
|
+
onInstall,
|
|
108
|
+
onManage,
|
|
109
|
+
} = props;
|
|
110
|
+
const hasActions = Boolean((item && !record) || pluginRecord || (record && canUninstall));
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<div
|
|
114
|
+
className={cn(
|
|
115
|
+
"relative flex h-8 shrink-0 items-center justify-end",
|
|
116
|
+
record ? "md:w-5" : "md:w-0",
|
|
117
|
+
)}
|
|
118
|
+
>
|
|
119
|
+
<div
|
|
120
|
+
className={cn(
|
|
121
|
+
"hidden items-center justify-end transition-opacity duration-150 md:flex",
|
|
122
|
+
hasActions && "group-hover:opacity-0 group-focus-within:opacity-0",
|
|
123
|
+
)}
|
|
124
|
+
>
|
|
125
|
+
{record ? (
|
|
126
|
+
<MarketplaceInstalledStatusIcon
|
|
127
|
+
disabled={isDisabled}
|
|
128
|
+
language={language}
|
|
129
|
+
/>
|
|
130
|
+
) : null}
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<div
|
|
134
|
+
className={cn(
|
|
135
|
+
"flex w-max items-center justify-end gap-2 transition-opacity duration-150",
|
|
136
|
+
"opacity-100 md:pointer-events-none md:absolute md:right-0 md:opacity-0",
|
|
137
|
+
"md:group-hover:pointer-events-auto md:group-hover:opacity-100",
|
|
138
|
+
"md:group-focus-within:pointer-events-auto md:group-focus-within:opacity-100",
|
|
139
|
+
)}
|
|
140
|
+
>
|
|
141
|
+
{item && !record && (
|
|
142
|
+
<button
|
|
143
|
+
onClick={(event) => {
|
|
144
|
+
event.stopPropagation();
|
|
145
|
+
onInstall(item);
|
|
146
|
+
}}
|
|
147
|
+
disabled={isInstalling}
|
|
148
|
+
className="inline-flex h-8 items-center gap-1.5 whitespace-nowrap rounded-xl bg-primary px-3 text-xs font-medium text-white transition-colors hover:bg-primary-600 disabled:opacity-50"
|
|
149
|
+
>
|
|
150
|
+
<Download className="h-3.5 w-3.5" />
|
|
151
|
+
{isInstalling ? t("marketplaceInstalling") : t("marketplaceInstall")}
|
|
152
|
+
</button>
|
|
153
|
+
)}
|
|
154
|
+
|
|
155
|
+
{pluginRecord && (
|
|
156
|
+
<button
|
|
157
|
+
disabled={busyForRecord}
|
|
158
|
+
onClick={(event) => {
|
|
159
|
+
event.stopPropagation();
|
|
160
|
+
onManage(isDisabled ? "enable" : "disable", pluginRecord);
|
|
161
|
+
}}
|
|
162
|
+
className="inline-flex h-8 items-center gap-1.5 whitespace-nowrap rounded-xl border border-gray-200/80 bg-white px-3 text-xs font-medium text-gray-600 transition-colors hover:border-gray-300 hover:bg-gray-50 disabled:opacity-50"
|
|
163
|
+
>
|
|
164
|
+
{isDisabled ? (
|
|
165
|
+
<Power className="h-3.5 w-3.5" />
|
|
166
|
+
) : (
|
|
167
|
+
<PowerOff className="h-3.5 w-3.5" />
|
|
168
|
+
)}
|
|
169
|
+
{busyAction && busyAction !== "uninstall"
|
|
170
|
+
? busyAction === "enable"
|
|
171
|
+
? t("marketplaceEnabling")
|
|
172
|
+
: t("marketplaceDisabling")
|
|
173
|
+
: isDisabled
|
|
174
|
+
? t("marketplaceEnable")
|
|
175
|
+
: t("marketplaceDisable")}
|
|
176
|
+
</button>
|
|
177
|
+
)}
|
|
178
|
+
|
|
179
|
+
{record && canUninstall && (
|
|
180
|
+
<button
|
|
181
|
+
disabled={busyForRecord}
|
|
182
|
+
onClick={(event) => {
|
|
183
|
+
event.stopPropagation();
|
|
184
|
+
onManage("uninstall", record);
|
|
185
|
+
}}
|
|
186
|
+
className="inline-flex h-8 items-center gap-1.5 whitespace-nowrap rounded-xl border border-gray-200/80 bg-white px-3 text-xs font-medium text-gray-500 transition-colors hover:border-rose-200 hover:bg-rose-50 hover:text-rose-600 disabled:opacity-50"
|
|
187
|
+
>
|
|
188
|
+
<Trash2 className="h-3.5 w-3.5" />
|
|
189
|
+
{busyAction === "uninstall"
|
|
190
|
+
? t("marketplaceRemoving")
|
|
191
|
+
: t("marketplaceUninstall")}
|
|
192
|
+
</button>
|
|
193
|
+
)}
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function MarketplaceInstalledStatusIcon(props: {
|
|
200
|
+
disabled: boolean;
|
|
201
|
+
language: string;
|
|
202
|
+
}) {
|
|
203
|
+
const { disabled, language } = props;
|
|
204
|
+
const label = disabled
|
|
205
|
+
? readLocalized({ zh: "已禁用", en: "Disabled" }, language)
|
|
206
|
+
: readLocalized({ zh: "已安装", en: "Installed" }, language);
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<TooltipProvider delayDuration={300}>
|
|
101
210
|
<Tooltip>
|
|
102
211
|
<TooltipTrigger asChild>
|
|
103
|
-
<
|
|
104
|
-
{
|
|
105
|
-
|
|
212
|
+
<span
|
|
213
|
+
aria-label={label}
|
|
214
|
+
className={cn(
|
|
215
|
+
"inline-flex h-5 w-5 items-center justify-center",
|
|
216
|
+
disabled ? "text-gray-400" : "text-emerald-700",
|
|
217
|
+
)}
|
|
218
|
+
>
|
|
219
|
+
{disabled ? (
|
|
220
|
+
<PowerOff className="h-4 w-4" />
|
|
221
|
+
) : (
|
|
222
|
+
<CheckCircle2 className="h-4 w-4" />
|
|
223
|
+
)}
|
|
224
|
+
</span>
|
|
106
225
|
</TooltipTrigger>
|
|
107
|
-
{
|
|
108
|
-
<TooltipContent className="max-w-[400px] text-xs leading-relaxed">
|
|
109
|
-
{summary}
|
|
110
|
-
</TooltipContent>
|
|
111
|
-
) : null}
|
|
226
|
+
<TooltipContent className="text-xs">{label}</TooltipContent>
|
|
112
227
|
</Tooltip>
|
|
113
228
|
</TooltipProvider>
|
|
114
229
|
);
|
|
@@ -171,7 +286,7 @@ export function MarketplaceListCard(props: {
|
|
|
171
286
|
className="group flex cursor-pointer items-start justify-between gap-3.5 rounded-2xl border border-gray-200/40 bg-white px-5 py-4 shadow-sm transition-all hover:border-blue-300/80 hover:shadow-md"
|
|
172
287
|
>
|
|
173
288
|
<div className="flex min-w-0 flex-1 gap-3">
|
|
174
|
-
<
|
|
289
|
+
<MarketplaceItemIcon
|
|
175
290
|
name={title}
|
|
176
291
|
fallback={spec || t("marketplaceTypeExtension")}
|
|
177
292
|
/>
|
|
@@ -180,54 +295,23 @@ export function MarketplaceListCard(props: {
|
|
|
180
295
|
</div>
|
|
181
296
|
</div>
|
|
182
297
|
|
|
183
|
-
<
|
|
184
|
-
{item
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
{pluginRecord && (
|
|
198
|
-
<button
|
|
199
|
-
disabled={busyForRecord}
|
|
200
|
-
onClick={(event) => {
|
|
201
|
-
event.stopPropagation();
|
|
202
|
-
onManage(isDisabled ? "enable" : "disable", pluginRecord);
|
|
203
|
-
}}
|
|
204
|
-
className="inline-flex h-8 items-center rounded-xl border border-gray-200/80 bg-white px-4 text-xs font-medium text-gray-600 transition-colors hover:border-gray-300 hover:bg-gray-50 disabled:opacity-50"
|
|
205
|
-
>
|
|
206
|
-
{busyAction && busyAction !== "uninstall"
|
|
207
|
-
? busyAction === "enable"
|
|
208
|
-
? t("marketplaceEnabling")
|
|
209
|
-
: t("marketplaceDisabling")
|
|
210
|
-
: isDisabled
|
|
211
|
-
? t("marketplaceEnable")
|
|
212
|
-
: t("marketplaceDisable")}
|
|
213
|
-
</button>
|
|
214
|
-
)}
|
|
215
|
-
|
|
216
|
-
{record && canUninstall && (
|
|
217
|
-
<button
|
|
218
|
-
disabled={busyForRecord}
|
|
219
|
-
onClick={(event) => {
|
|
220
|
-
event.stopPropagation();
|
|
221
|
-
onManage("uninstall", record);
|
|
222
|
-
}}
|
|
223
|
-
className="inline-flex h-8 items-center rounded-xl border border-rose-100 bg-white px-4 text-xs font-medium text-rose-500 transition-colors hover:border-rose-200 hover:bg-rose-50 disabled:opacity-50"
|
|
224
|
-
>
|
|
225
|
-
{busyAction === "uninstall"
|
|
226
|
-
? t("marketplaceRemoving")
|
|
227
|
-
: t("marketplaceUninstall")}
|
|
228
|
-
</button>
|
|
229
|
-
)}
|
|
230
|
-
</div>
|
|
298
|
+
<MarketplaceListCardActions
|
|
299
|
+
item={item}
|
|
300
|
+
record={record}
|
|
301
|
+
pluginRecord={pluginRecord}
|
|
302
|
+
isInstalling={isInstalling}
|
|
303
|
+
isDisabled={isDisabled}
|
|
304
|
+
canUninstall={canUninstall}
|
|
305
|
+
busyAction={busyAction}
|
|
306
|
+
busyForRecord={busyForRecord}
|
|
307
|
+
language={language}
|
|
308
|
+
onInstall={onInstall}
|
|
309
|
+
onManage={onManage}
|
|
310
|
+
/>
|
|
231
311
|
</article>
|
|
232
312
|
);
|
|
233
313
|
}
|
|
314
|
+
|
|
315
|
+
function readLocalized(text: { zh: string; en: string }, language: string) {
|
|
316
|
+
return language.startsWith("zh") ? text.zh : text.en;
|
|
317
|
+
}
|