@linkdesk/ui 0.1.2 → 0.1.3
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/dist/index.css +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +16694 -1037
- package/dist/markdown-view/MarkdownView.d.ts +34 -0
- package/dist/overlay-portal/OverlayPortal.d.ts +6 -1
- package/dist/overlay-portal/overlayLayer.d.ts +36 -0
- package/dist/plugin-icon/defaultIdentityArt.d.ts +17 -0
- package/dist/plugin-icon/iconUtils.d.ts +15 -0
- package/package.json +3 -2
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E6#30.6a 壳共享 MarkdownView——README/markdown 渲染**唯一组件**(04-详情页设计.md §三 L108/§4.1)。
|
|
3
|
+
* 消费方:marketplace 详情 README(30.6b 已装读包/未装 fetch readmeUrl)+ 未来发行说明 Release body
|
|
4
|
+
* (05-发行说明.md §2.4 已同步)——第三方插件经 @linkdesk/ui 复用同一渲染器。
|
|
5
|
+
*
|
|
6
|
+
* 渲染链 = react-markdown + remark-gfm(表格/删除线,30.6a 定案)+ rehype-raw(原生 HTML 入树,VS Code
|
|
7
|
+
* README 作者常写 <img>/<details>)→ rehype-sanitize(hast schema 消毒,默认 GitHub schema:剥 script/
|
|
8
|
+
* 事件属性/javascript: 等危险 URI——去脚本去事件语义与定案的 DOMPurify 等价)。
|
|
9
|
+
* 🔄 **消毒器选型注(30.6a 定案 DOMPurify → 此处 rehype-sanitize)**:react-markdown 产出 React 树而非
|
|
10
|
+
* HTML 串,DOMPurify(DOM 串消毒)无法在树内使用;rehype-sanitize 是 react-markdown 官方配套的
|
|
11
|
+
* hast-schema 消毒器,于 React 建树**之前**剥除危险节点/属性,无 DOM 变异、不破坏 React 协调。
|
|
12
|
+
* 缝隙 E2「去脚本/事件属性」由此硬保证;「img src 限 https」在组件层最后把守(本文件底部 url 门禁)。
|
|
13
|
+
*
|
|
14
|
+
* 安全双闸:① rehype-sanitize 默认 schema——script/事件属性/iframe 等整枝剥除;② <a href>/<img src>
|
|
15
|
+
* 组件级 scheme 白名单(https/http/mailto/相对——javascript:/data: 一律不落 DOM)。纯文本渲染零
|
|
16
|
+
* dangerouslySetInnerHTML,markdown 内容绝不经 innerHTML 注入。
|
|
17
|
+
*
|
|
18
|
+
* E6#70a(15-详情页说明展示任务档案):README 媒体相对引用解析到「被查看插件的包内资源」。
|
|
19
|
+
* 说明文本是调用方(marketplace 详情)读出来的纯文本,渲染器不知「这张图相对谁」→ 调用方注入
|
|
20
|
+
* 可选 `assetBase`(如 `linkdesk://{pluginId}/`):提供了则 `cover.svg`/`resources/cover.svg`/`./x`
|
|
21
|
+
* 等无 scheme 相对引用按基址解析成绝对 URL(详情页说明区显形);不提供则维持旧行为。基址纯数据
|
|
22
|
+
* 注入,壳共享组件零插件名(硬约束 10);绝对 https:/linkdesk: 直通,http/data:/javascript:/file: 拒。
|
|
23
|
+
*/
|
|
24
|
+
interface MarkdownViewProps {
|
|
25
|
+
/** markdown 原文;空/undefined 渲染空容器(调用方负责降级文案) */
|
|
26
|
+
markdown?: string | null;
|
|
27
|
+
className?: string;
|
|
28
|
+
/** 媒体资源基址(E6#70a 可选)——README 相对引用解析基准(详情页注入 `linkdesk://{pluginId}/`,
|
|
29
|
+
* 被查看插件的包内资源即此可达);绝对 https 直通不受影响。不提供 = 旧行为(相对仅透传)。 */
|
|
30
|
+
assetBase?: string;
|
|
31
|
+
}
|
|
32
|
+
declare function MarkdownView({ markdown, className, assetBase }: MarkdownViewProps): import("react").JSX.Element;
|
|
33
|
+
declare const _default: import("react").MemoExoticComponent<typeof MarkdownView>;
|
|
34
|
+
export default _default;
|
|
@@ -8,6 +8,11 @@ interface OverlayPortalProps {
|
|
|
8
8
|
open?: boolean;
|
|
9
9
|
/** 外部点击 / Escape → 关闭回调。不传则无外部关闭行为(如 ToastContainer) */
|
|
10
10
|
onClose?: () => void;
|
|
11
|
+
/** E6#73b:点浮层外面**关不关**——默认 `true`(既有全部消费方行为不变)。
|
|
12
|
+
* 通知面板传 `false`(18 档 §五 A / 用户 R5-3 原话:「我在干别的事情的时候鼠标在点击别的地方
|
|
13
|
+
* 的时候 toast 不会消失,除非等我点击旁边那个最小化」)——它只认 Esc 与面板内「最小化」两个关法。
|
|
14
|
+
* ⚠️ 只关外部点击这一条:Esc 不受影响(`onClose` 仍要传)。 */
|
|
15
|
+
closeOnOutsideClick?: boolean;
|
|
11
16
|
/** 点击此 ref 指向的元素不算"外部"(如触发按钮)。每次事件回调内实时读,不缓存 */
|
|
12
17
|
triggerRef?: React.RefObject<HTMLElement>;
|
|
13
18
|
/** Tab/Shift+Tab 在 overlay 内循环。首次渲染自动 focus 第一个可聚焦元素 */
|
|
@@ -23,5 +28,5 @@ interface OverlayPortalProps {
|
|
|
23
28
|
* 自动回退 body。显式传 rootId(ContextMenu→context-menu-root)不受影响。 */
|
|
24
29
|
rootId?: string;
|
|
25
30
|
}
|
|
26
|
-
export default function OverlayPortal({ children, onClose, triggerRef, trapFocus, zIndex, rootId, open }: OverlayPortalProps): import("react").ReactPortal;
|
|
31
|
+
export default function OverlayPortal({ children, onClose, closeOnOutsideClick, triggerRef, trapFocus, zIndex, rootId, open }: OverlayPortalProps): import("react").ReactPortal;
|
|
27
32
|
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 浮层层级权威——「Esc 只关最上层浮层」的**唯一**判据来源(E6#73b ④)。
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么是 DOM 计算,不是模块级栈
|
|
5
|
+
*
|
|
6
|
+
* 插件被**独立打包**:`@linkdesk/ui` 不在 plugin-sdk 的 `DEFAULT_EXTERNAL` 里
|
|
7
|
+
* (见 `packages/plugin-sdk/src/vite-config.ts` 与 `scripts/build-pool-vendor.mjs` 的 MAP_KEYS——
|
|
8
|
+
* 只有 react 系 + i18next 走 import-map),所以同一窗口里**每份 bundle 各持一份本模块副本**。
|
|
9
|
+
* 模块级数组跨 bundle 不共享 ⇒ 各算各的栈 ⇒ 插件侧的浮层与壳侧浮层互相看不见。
|
|
10
|
+
* **DOM 是跨 bundle 的真·单一真相**(同在池窗,同一份 document)。
|
|
11
|
+
*
|
|
12
|
+
* ## 排序规则
|
|
13
|
+
*
|
|
14
|
+
* 1. **有效 z-index**——表面自身有 z 就用自身;否则沿祖先找第一个非 `auto` 的 z
|
|
15
|
+
* (内联 z 是既有约定:`Z_INDEX` 常量表 → dialog 5000 / quickPick 4000 / contextMenu 3000 /
|
|
16
|
+
* overlay-root 2000 / floatingPanel 1500,浮层表面一律取这张表的 inline 值)。
|
|
17
|
+
* 2. 同 z 按**文档序**——后挂载者在上(同一 portal root 内文档序即挂载序)。
|
|
18
|
+
*
|
|
19
|
+
* 规则与「用户眼睛看到的谁盖谁」同源:拿不准时以 z 表为准(`src/constants.ts`)。
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* 浮层**表面**标记属性——所有浮层根元素都带它:
|
|
23
|
+
* OverlayPortal 包装盒(`open` 时)、池侧 Dialog / QuickPick / FloatingPanel 面板本体。
|
|
24
|
+
* 纯行为标记,CSS 不使用(隔离地板用的是另一个属性 `data-overlay-wrapper`,两者别混)。
|
|
25
|
+
*/
|
|
26
|
+
export declare const OVERLAY_LAYER_ATTR = "data-overlay-layer";
|
|
27
|
+
/**
|
|
28
|
+
* 传进来的表面是不是当前最上层的那一个。
|
|
29
|
+
* `el` 传 null(未挂载/无 ref)→ true——没有表面就没得比,闭自己的行为最保守。
|
|
30
|
+
*/
|
|
31
|
+
export declare function isTopmostOverlay(el: HTMLElement | null | undefined): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* 同上一版,但接受**表面内部**的任意元素——沿祖先找到最近的标记表面再比。
|
|
34
|
+
* 给「组件只持有内容 ref、包装盒归 OverlayPortal」的消费方用(如 ContextMenu 的 menuRef)。
|
|
35
|
+
*/
|
|
36
|
+
export declare function isTopmostOverlayFrom(inner: HTMLElement | null | undefined): boolean;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 默认插件身份彩色块「插头的块」(E6#69a)——统一默认资产,单份共享。
|
|
3
|
+
*
|
|
4
|
+
* 消费方:壳 windowLayout(视图标签无图标兜底)+ 市场 list/detail(无图插件兜底)同一 URI——
|
|
5
|
+
* #69f 归一化「禁各写一份」;顶替历史 📄 emoji 兜底 + #66 A 家族 640 场景默认(defaultCoverArt 删)。
|
|
6
|
+
*
|
|
7
|
+
* 本文件 = 纯 SVG 资产数据(同 E6#66 defaultCoverArt 先例):外部 <img> 渲染 data-URI,CSS 变量在
|
|
8
|
+
* img 内不可达 → 颜色只能字面量内嵌;与各插件 resources 目录内 svg 资产同性质(.svg 不被 css 硬编码
|
|
9
|
+
* 审计扫描,TS 内嵌等价物文档化豁免,见 scripts/check-css-hardcode.mjs EXEMPT_FILES)。零消费逻辑。
|
|
10
|
+
*
|
|
11
|
+
* 资产:48×48 玻璃磁贴语法(同批其他 Type-2)——中性石板灰渐变块 + 白描边电源插头(两脚)。
|
|
12
|
+
* 色彩故意避开已占色系(serial 青绿 / file 青蓝 / settings 板岩 / aurora 紫 / terminal 绿)→ 青灰铜;
|
|
13
|
+
* 插头 = 「插件 = 接入插孔的通用部件」意象(2026-09-09 样张 §⑦ 已点头)。
|
|
14
|
+
*/
|
|
15
|
+
export declare const DEFAULT_PLUGIN_IDENTITY_SVG = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"48\" height=\"48\" viewBox=\"0 0 48 48\">\n <defs>\n <linearGradient id=\"defBg\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\">\n <stop offset=\"0\" stop-color=\"#3F4A5E\"/>\n <stop offset=\"1\" stop-color=\"#1B2434\"/>\n </linearGradient>\n <linearGradient id=\"defGlass\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0\" stop-color=\"#FFFFFF\" stop-opacity=\"0.14\"/>\n <stop offset=\"1\" stop-color=\"#FFFFFF\" stop-opacity=\"0.02\"/>\n </linearGradient>\n </defs>\n <rect x=\"4\" y=\"4\" width=\"40\" height=\"40\" rx=\"10\" fill=\"url(#defBg)\"/>\n <rect x=\"4\" y=\"4\" width=\"40\" height=\"40\" rx=\"10\" fill=\"url(#defGlass)\"/>\n <rect x=\"4\" y=\"4\" width=\"40\" height=\"40\" rx=\"10\" fill=\"none\" stroke=\"#E2E8F0\" stroke-opacity=\"0.22\" stroke-width=\"1\"/>\n <circle cx=\"24\" cy=\"13.5\" r=\"4.6\" fill=\"none\" stroke=\"#F1F5F9\" stroke-width=\"2.9\"/>\n <path d=\"M24 18.6 L24 27\" fill=\"none\" stroke=\"#F1F5F9\" stroke-width=\"2.9\" stroke-linecap=\"round\"/>\n <path d=\"M17 30 L23 30\" fill=\"none\" stroke=\"#F1F5F9\" stroke-width=\"2.9\" stroke-linecap=\"round\"/>\n <path d=\"M25 30 L31 30\" fill=\"none\" stroke=\"#F1F5F9\" stroke-width=\"2.9\" stroke-linecap=\"round\"/>\n</svg>";
|
|
16
|
+
/** data-URI 形态——消费方直接作 <img src> / PluginIcon src */
|
|
17
|
+
export declare const DEFAULT_PLUGIN_IDENTITY_URI: string;
|
|
@@ -36,3 +36,18 @@ export type ManifestIconShape = Pick<PluginManifest, "icon" | "iconSource">;
|
|
|
36
36
|
* - 全无 → { emoji: "📄" }
|
|
37
37
|
*/
|
|
38
38
|
export declare function resolvePluginIcon(pluginId: string, manifest: ManifestIconShape): ResolvedIcon;
|
|
39
|
+
/** manifest 中「插件身份彩色图」相关最小结构——marketIcon/icon 任一可裁决(E6#69f) */
|
|
40
|
+
export type PluginIdentityShape = Pick<PluginManifest, "icon" | "iconSource" | "marketIcon" | "marketIconSource">;
|
|
41
|
+
/** 挑出的统一 descriptor 形状——与 ManifestIconShape 结构兼容(直接可作 PluginIcon manifest / resolvePluginIcon 入参) */
|
|
42
|
+
export interface IdentityPicked {
|
|
43
|
+
icon: string;
|
|
44
|
+
iconSource?: PluginManifest["iconSource"];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 插件身份彩色图裁决——恒返有效 descriptor(顶替历史 📄/640 场景默认)。
|
|
48
|
+
* - marketIcon 在 → 用之(Type-2 身份图;source 缺省 → linkdesk:// 路径推断)
|
|
49
|
+
* - 否则 icon 在 → 用之(非图标栏插件 icon 即 Type-2;图标栏插件 icon 是 Type-1 剪影——标签/市场不消费图标栏图)
|
|
50
|
+
* - 全无 → 统一默认彩色块(E6#69a)
|
|
51
|
+
* candidates 按序(已装 manifest 优先于目录条目——先到的候选先提供字段)。
|
|
52
|
+
*/
|
|
53
|
+
export declare function pickIdentityArt(...candidates: Array<PluginIdentityShape | null | undefined>): IdentityPicked;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linkdesk/ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "@linkdesk/ui——LinkDesk 共享 UI 组件(右键/下拉/开关/取色器等)。壳 src/components/shared 单一源码经构建编译,插件作者 npm i 后与内置插件同款、自动跟随主题/玻璃的界面。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
},
|
|
26
26
|
"scripts": {
|
|
27
27
|
"build": "node scripts/build.mjs",
|
|
28
|
-
"typecheck": "tsc -p tsconfig.decl.json --noEmit --emitDeclarationOnly false"
|
|
28
|
+
"typecheck": "tsc -p tsconfig.decl.json --noEmit --emitDeclarationOnly false",
|
|
29
|
+
"prepack": "node scripts/build.mjs"
|
|
29
30
|
},
|
|
30
31
|
"peerDependencies": {
|
|
31
32
|
"react": "^18.0.0",
|