@dsh-xhl/dsh-file-explorer 0.1.8 → 0.2.0
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/README.md +24 -4
- package/cordis.patch.yml +8 -8
- package/lib/client.js +72 -241
- package/lib/types/context-types.d.ts +0 -10
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
## 功能
|
|
6
6
|
|
|
7
7
|
- **聊天页标题栏图标**(`conversation.session.header.utilities`):一个内置编辑器图标,点击打开文件预览 / 编辑弹窗(聊天页保持与现状一致,无下拉)
|
|
8
|
-
- **Hero / 新建会话页 ButtonGroup
|
|
8
|
+
- **Hero / 新建会话页 ButtonGroup**:通过 `@dsh-xhl/dsh-hero-flex` 插件声明的
|
|
9
|
+
`hero.flex` 槽位渲染(**未安装该插件则不显示**);hero 上为**左主按钮 +
|
|
10
|
+
右下拉**的组合:
|
|
9
11
|
- **编辑器**(默认):点击打开文件预览 / 编辑弹窗(Ctrl+P 亦可)
|
|
10
12
|
- **文件夹**:点击直接打开会话工作区所在的**系统文件夹**(Windows `explorer` / macOS `open` / Linux `xdg-open`)
|
|
11
13
|
- **VSCode**:点击用 `code` 命令行在 **VS Code** 中打开会话工作区(自动探测安装位置 / PATH;宿主明确报告未安装时该项自动隐藏,探测请求本身失败则保持可见并退避重试)
|
|
12
14
|
- 选择记忆在 localStorage,点击后底部提示确认打开的路径;下拉选中某项会先切换默认打开方式,随后立即执行对应的打开动作
|
|
13
|
-
-
|
|
15
|
+
- 纯插件实现,不改动框架代码;hero 按钮组渲染依赖独立插件 `@dsh-xhl/dsh-hero-flex`(详见 `docs/hero-filex-button.md`)
|
|
14
16
|
- **对话内点击文件 → 编辑器预览**:聊天里的文件路径链接(工具行、产物行、正文提及)默认用系统程序打开;插件拦截 `remote.session.openWorkspacePath` 与 `workspaces.openPath` 两条通道,改为在编辑器弹窗中预览(目录或 cwd 外的路径仍落回系统打开)
|
|
15
17
|
- **文件列表**:左侧目录树(跳过 `node_modules`/`.git`/`dist` 等)
|
|
16
18
|
- **按文件名过滤**:输入即过滤
|
|
@@ -28,7 +30,7 @@ src/
|
|
|
28
30
|
+ /filex/file 媒体路由(图片/PDF 字节),loopback 信任围栏
|
|
29
31
|
trust-fence.ts Host-header loopback 校验(DNS-rebinding 防护)
|
|
30
32
|
client/
|
|
31
|
-
index.tsx 入口:聊天页 header 图标(内置编辑器)+ hero 模式 ButtonGroup
|
|
33
|
+
index.tsx 入口:聊天页 header 图标(内置编辑器)+ hero 模式 ButtonGroup(hero.flex 槽位)+ overlay 弹窗 + Ctrl+P 快捷键
|
|
32
34
|
Explorer.tsx 弹窗:文件树 / 文件名过滤 / 全文搜索 / 查看器分派(primitives 重排)
|
|
33
35
|
TextEditor.tsx CodeMirror 6 编辑器 + 搜索跳转高亮
|
|
34
36
|
lang.ts 扩展名 → CodeMirror 语言映射
|
|
@@ -41,9 +43,27 @@ src/
|
|
|
41
43
|
|
|
42
44
|
```bash
|
|
43
45
|
pnpm install
|
|
44
|
-
pnpm build # lib/index.js(host)+ lib/client.js(client bundle,CodeMirror
|
|
46
|
+
pnpm build # lib/index.js(host)+ lib/client.js(client bundle,CodeMirror 内联)+ 图标校验
|
|
45
47
|
```
|
|
46
48
|
|
|
49
|
+
> **图标命名陷阱(务必先读)**:宿主 `@deepseek-ai/dsh-client-ui-primitives`
|
|
50
|
+
> 的图标导出已从**尺寸后缀**(`Icon*16` / `Icon*14`)改为**字重后缀**
|
|
51
|
+
> (`Icon*Regular` / `Icon*Medium`),旧名在宿主里**一个都不存在**。
|
|
52
|
+
>
|
|
53
|
+
> 这个失败模式极其隐蔽:客户端 bundle 通过冻结的模块表解析原语,缺失的名字
|
|
54
|
+
> 拿到的是 `undefined`,React 在渲染时抛 "Element type is invalid",而槽位的
|
|
55
|
+
> 错误边界会**静默退位(abdicate)**——插件看起来已安装、已注册,却什么都不渲染。
|
|
56
|
+
>
|
|
57
|
+
> 曾经的坑在于开发依赖停留在 `0.1.0-rc.6`(仍是旧命名),于是 `tsc` 放行、
|
|
58
|
+
> 构建成功,只有运行时才炸。现在 `pnpm build` 末尾的
|
|
59
|
+
> [`scripts/check-icons.mjs`](scripts/check-icons.mjs) 会把产物引用的图标与
|
|
60
|
+
> **宿主真实导出**逐一比对,对不上直接构建失败。
|
|
61
|
+
>
|
|
62
|
+
> 若 `pnpm typecheck` 报某图标不存在,多半是本地 `node_modules` 里的原语包
|
|
63
|
+
> 落后于宿主:把 `node_modules/@deepseek-ai/dsh-client-ui-primitives` 指向
|
|
64
|
+
> 宿主实际安装的那份(`$env:LOCALAPPDATA\Volta\tools\image\packages\@deepseek-ai\dsh\node_modules\@deepseek-ai\dsh\node_modules\@deepseek-ai\dsh-client-ui-primitives`)
|
|
65
|
+
> 再重试。
|
|
66
|
+
|
|
47
67
|
> 构建采用**原地覆盖**(不再先删除 `lib/`)。宿主半在进程启动时载入内存,而 profile
|
|
48
68
|
> 若 link 到本工作区则会直读磁盘:构建中途清空目录会让 HMR 读到空 bundle,导致
|
|
49
69
|
> `/filex` 路由与客户端 import 瞬时失败(表现为 `unknown filex API method ...`)。
|
package/cordis.patch.yml
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# dsh-file-explorer bundle patch
|
|
2
|
-
#
|
|
3
|
-
# 通过 bundle 通道挂载:
|
|
4
|
-
# dsh plugin --profile <name> add @dsh-xhl/dsh-file-explorer@<version>
|
|
5
|
-
# 或手动把 '@dsh-xhl/dsh-file-explorer' 加进 profile 的 dsh.profile.bundles 并 link 依赖。
|
|
6
|
-
- insert:
|
|
7
|
-
- id: dsh-file-explorer
|
|
8
|
-
name: '@dsh-xhl/dsh-file-explorer'
|
|
1
|
+
# dsh-file-explorer bundle patch
|
|
2
|
+
#
|
|
3
|
+
# 通过 bundle 通道挂载:
|
|
4
|
+
# dsh plugin --profile <name> add @dsh-xhl/dsh-file-explorer@<version>
|
|
5
|
+
# 或手动把 '@dsh-xhl/dsh-file-explorer' 加进 profile 的 dsh.profile.bundles 并 link 依赖。
|
|
6
|
+
- insert:
|
|
7
|
+
- id: dsh-file-explorer
|
|
8
|
+
name: '@dsh-xhl/dsh-file-explorer'
|
package/lib/client.js
CHANGED
|
@@ -36902,7 +36902,7 @@ window.__ModuleLoader__.load({
|
|
|
36902
36902
|
children: ctxMenu.empty ? "请先在编辑器中选中文本" : reference(ctxMenu)
|
|
36903
36903
|
})]
|
|
36904
36904
|
}),
|
|
36905
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
36905
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRightUpOutlineRegular, {}),
|
|
36906
36906
|
disabled: ctxMenu.empty
|
|
36907
36907
|
}, {
|
|
36908
36908
|
id: "insertFile",
|
|
@@ -36913,7 +36913,7 @@ window.__ModuleLoader__.load({
|
|
|
36913
36913
|
children: reference()
|
|
36914
36914
|
})]
|
|
36915
36915
|
}),
|
|
36916
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
36916
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRightUpOutlineRegular, {})
|
|
36917
36917
|
}] : [];
|
|
36918
36918
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
36919
36919
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -36993,7 +36993,7 @@ window.__ModuleLoader__.load({
|
|
|
36993
36993
|
}),
|
|
36994
36994
|
insertError !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
|
|
36995
36995
|
text: `插入聊天框失败:${insertError}`,
|
|
36996
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
36996
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}),
|
|
36997
36997
|
onDone: () => setInsertError("")
|
|
36998
36998
|
}, toastKey)
|
|
36999
36999
|
] });
|
|
@@ -37020,40 +37020,19 @@ window.__ModuleLoader__.load({
|
|
|
37020
37020
|
down. An 8px radius keeps it a rounded square rather than a full circle. */
|
|
37021
37021
|
.filex-header-btn{width:28px;height:28px;padding:0;border-radius:8px}
|
|
37022
37022
|
|
|
37023
|
-
/* ── hero ButtonGroup (
|
|
37024
|
-
/* The mode group shown
|
|
37023
|
+
/* ── hero ButtonGroup (hero.flex slot entry) ─────────────────────────── */
|
|
37024
|
+
/* The mode group shown on the hero / blank-session screen: the left
|
|
37025
37025
|
button carries the remembered mode's icon, the right chevron the mode
|
|
37026
37026
|
dropdown. Outer corners use the same rounded-rectangle radius as the
|
|
37027
37027
|
header buttons (8px on a 28px-tall button), so the group reads as one
|
|
37028
|
-
pill consistent with the session header icons.
|
|
37028
|
+
pill consistent with the session header icons. The host is the
|
|
37029
|
+
dsh-hero-flex plugin's hf-row (its own stylesheet); this sheet only
|
|
37030
|
+
styles the group itself. */
|
|
37029
37031
|
.filex-group{display:inline-flex;align-items:center;position:relative;height:28px}
|
|
37030
37032
|
.filex-group .filex-group-main,.filex-group .filex-group-trigger{height:28px}
|
|
37031
37033
|
.filex-group .filex-group-main{border-radius:8px 0 0 8px;padding:0 9px}
|
|
37032
37034
|
.filex-group .filex-group-trigger{margin-left:-1px;border-radius:0 8px 8px 0;border-left:0;padding:0 7px}
|
|
37033
37035
|
|
|
37034
|
-
/* ── header corner seat (new runtime) ────────────────────────────────── */
|
|
37035
|
-
/* The corner occupant: the hero ButtonGroup directly left of the right-
|
|
37036
|
-
sidebar expand button in one flex row — the same corner seat the shell's
|
|
37037
|
-
expand button occupies alone once the header chrome is shown, now shared
|
|
37038
|
-
with the plugin's open-actions group. The seat's own margin and the
|
|
37039
|
-
header's padding position the row at the far right of the blank header. */
|
|
37040
|
-
.filex-corner{display:flex;align-items:center;gap:8px}
|
|
37041
|
-
/* Re-painted expand button, same footprint as the shell's ExpandButton
|
|
37042
|
-
(28×28, 15px mirrored panel icon) but with a rounded-rectangle corner to
|
|
37043
|
-
match the editor button beside it; visible only while the panel is
|
|
37044
|
-
collapsed. */
|
|
37045
|
-
.filex-corner-expand{width:28px;height:28px;color:var(--dsw-alias-label-secondary,#9ca3af);cursor:pointer;background:transparent;border:none;border-radius:8px;flex:none;justify-content:center;align-items:center;padding:6px;display:inline-flex}
|
|
37046
|
-
.filex-corner-expand:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(128,128,128,.15))}
|
|
37047
|
-
.filex-corner-expand-icon{transform:scaleX(-1)}
|
|
37048
|
-
|
|
37049
|
-
/* ── hero floating utility (shell.overlay entry) ─────────────────────── */
|
|
37050
|
-
/* The ButtonGroup, pinned by the plugin to the conversation column's
|
|
37051
|
-
top-right while the column is in its hero phase. The shell.overlay layer
|
|
37052
|
-
is click-through by design, so the entry opts back into pointer events;
|
|
37053
|
-
no explicit z-index keeps it below sibling overlay entries (modal,
|
|
37054
|
-
notices) and any body-portaled popup. */
|
|
37055
|
-
.filex-hero-fab{position:fixed;pointer-events:auto}
|
|
37056
|
-
|
|
37057
37036
|
/* ── transient notice strip ──────────────────────────────────────────── */
|
|
37058
37037
|
.filex-notice{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:2147483003;display:flex;align-items:center;gap:10px;max-width:70vw;padding:9px 14px;font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--dsw-alias-state-warn-primary,#f59e0b);background:var(--dsw-alias-bg-layer-2,#1b1b22);border:1px solid rgba(245,158,11,.5);border-radius:10px;box-shadow:0 8px 28px rgba(0,0,0,.45);pointer-events:auto}
|
|
37059
37038
|
.filex-notice-btn{border:0;background:transparent;color:inherit;cursor:pointer;font-size:14px;line-height:1;padding:2px 4px;opacity:.7;flex-shrink:0}
|
|
@@ -37311,7 +37290,7 @@ window.__ModuleLoader__.load({
|
|
|
37311
37290
|
onContextMenu(node.relPath, e.clientX, e.clientY);
|
|
37312
37291
|
},
|
|
37313
37292
|
children: [
|
|
37314
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
37293
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, { className: "filex-chev" }),
|
|
37315
37294
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FileIcon, {
|
|
37316
37295
|
kind: "dir",
|
|
37317
37296
|
name: node.name,
|
|
@@ -37574,7 +37553,7 @@ window.__ModuleLoader__.load({
|
|
|
37574
37553
|
children: `@file:${sideMenu.relPath} lines:${sideMenu.line}`
|
|
37575
37554
|
})]
|
|
37576
37555
|
}),
|
|
37577
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
37556
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRightUpOutlineRegular, {})
|
|
37578
37557
|
}] : [], {
|
|
37579
37558
|
id: "insertFile",
|
|
37580
37559
|
label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
@@ -37584,7 +37563,7 @@ window.__ModuleLoader__.load({
|
|
|
37584
37563
|
children: `@file:${sideMenu.relPath}`
|
|
37585
37564
|
})]
|
|
37586
37565
|
}),
|
|
37587
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
37566
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRightUpOutlineRegular, {})
|
|
37588
37567
|
}] : [];
|
|
37589
37568
|
let sideContent;
|
|
37590
37569
|
if (searchMode === "name") {
|
|
@@ -37777,7 +37756,7 @@ window.__ModuleLoader__.load({
|
|
|
37777
37756
|
type: "button",
|
|
37778
37757
|
size: "sm",
|
|
37779
37758
|
variant: "ghost",
|
|
37780
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
37759
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFullscreenOutlineRegular, { className: fullscreen ? "filex-fs-on" : void 0 }),
|
|
37781
37760
|
title: fullscreen ? "退出全屏" : "全屏",
|
|
37782
37761
|
"aria-label": fullscreen ? "退出全屏" : "全屏",
|
|
37783
37762
|
onClick: () => setFullscreen((v) => !v)
|
|
@@ -37786,7 +37765,7 @@ window.__ModuleLoader__.load({
|
|
|
37786
37765
|
type: "button",
|
|
37787
37766
|
size: "sm",
|
|
37788
37767
|
variant: "ghost",
|
|
37789
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
37768
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutlineRegular, {}),
|
|
37790
37769
|
title: "关闭(Esc)",
|
|
37791
37770
|
"aria-label": "关闭",
|
|
37792
37771
|
onClick: handleClose
|
|
@@ -37824,7 +37803,7 @@ window.__ModuleLoader__.load({
|
|
|
37824
37803
|
type: "button",
|
|
37825
37804
|
size: "sm",
|
|
37826
37805
|
variant: "outline",
|
|
37827
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
37806
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutlineRegular, {}),
|
|
37828
37807
|
disabled: loading,
|
|
37829
37808
|
onClick: () => void refresh(),
|
|
37830
37809
|
title: "刷新文件列表",
|
|
@@ -37985,7 +37964,7 @@ window.__ModuleLoader__.load({
|
|
|
37985
37964
|
}),
|
|
37986
37965
|
sideToast !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
|
|
37987
37966
|
text: `插入聊天框失败:${sideToast}`,
|
|
37988
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
37967
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}),
|
|
37989
37968
|
onDone: () => setSideToast("")
|
|
37990
37969
|
}, toastKey)
|
|
37991
37970
|
] });
|
|
@@ -38075,19 +38054,17 @@ window.__ModuleLoader__.load({
|
|
|
38075
38054
|
*
|
|
38076
38055
|
* The in-chat session header (`conversation.session.header.utilities`) keeps
|
|
38077
38056
|
* a single built-in-editor icon: clicking it opens the file-explorer modal
|
|
38078
|
-
* (preview / edit).
|
|
38079
|
-
* plugin itself through the generic `shell.overlay` layer carries the full
|
|
38080
|
-
* ButtonGroup — the left button performs the remembered open action
|
|
38081
|
-
* (内置编辑器 / 系统文件夹 / VS Code) and the right chevron opens a dropdown
|
|
38082
|
-
* to switch between them. Selecting an entry both persists it as the new
|
|
38083
|
-
* default and immediately runs that action.
|
|
38057
|
+
* (preview / edit).
|
|
38084
38058
|
*
|
|
38085
|
-
* The
|
|
38086
|
-
*
|
|
38087
|
-
*
|
|
38088
|
-
*
|
|
38089
|
-
*
|
|
38090
|
-
*
|
|
38059
|
+
* The hero / blank-session open-actions ButtonGroup (编辑器 / 系统文件夹 /
|
|
38060
|
+
* VS Code) renders ONLY through the `hero.flex` slot declared by the
|
|
38061
|
+
* dsh-hero-flex plugin, which owns the header's corner seat at priority -1
|
|
38062
|
+
* and hosts the additive flex row beside the right-sidebar expand button.
|
|
38063
|
+
* Without dsh-hero-flex installed the ButtonGroup does not render at all —
|
|
38064
|
+
* this plugin no longer occupies the corner seat nor floats a fallback
|
|
38065
|
+
* overlay. The left button performs the remembered open action and the right
|
|
38066
|
+
* chevron opens a dropdown to switch between them; selecting an entry both
|
|
38067
|
+
* persists it as the new default and immediately runs that action.
|
|
38091
38068
|
*
|
|
38092
38069
|
* Session binding: every /filex request is conversation-scoped, so the modal
|
|
38093
38070
|
* must know WHICH session it belongs to — the host resolves the workspace
|
|
@@ -38095,6 +38072,25 @@ window.__ModuleLoader__.load({
|
|
|
38095
38072
|
* the framework-resolved sessionId; Ctrl+P falls back to the currently
|
|
38096
38073
|
* selected session (`useSessions` → `state.current`).
|
|
38097
38074
|
*/
|
|
38075
|
+
/**
|
|
38076
|
+
* Icon primitives are resolved through the host's frozen module table, so a
|
|
38077
|
+
* name the host no longer exports arrives as `undefined` and React throws
|
|
38078
|
+
* "Element type is invalid" at render time. Inside a slot that throw is
|
|
38079
|
+
* swallowed by the slot error boundary, which silently abdicates the seat —
|
|
38080
|
+
* the plugin looks installed and registered while rendering nothing. Fail
|
|
38081
|
+
* loudly at load instead, where the message is still visible.
|
|
38082
|
+
*/
|
|
38083
|
+
function assertPrimitives() {
|
|
38084
|
+
const missing = Object.entries({
|
|
38085
|
+
Button: _deepseek_ai_dsh_client_ui_primitives.Button,
|
|
38086
|
+
Menu: _deepseek_ai_dsh_client_ui_primitives.Menu,
|
|
38087
|
+
Tooltip: _deepseek_ai_dsh_client_ui_primitives.Tooltip,
|
|
38088
|
+
IconChevronDownOutlineRegular: _deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular,
|
|
38089
|
+
IconEditOutlineRegular: _deepseek_ai_dsh_client_ui_primitives.IconEditOutlineRegular,
|
|
38090
|
+
IconFolderOpenRegular: _deepseek_ai_dsh_client_ui_primitives.IconFolderOpenRegular
|
|
38091
|
+
}).filter(([, v]) => v === void 0).map(([k]) => k);
|
|
38092
|
+
if (missing.length > 0) throw new Error(`[dsh-file-explorer] 宿主 @deepseek-ai/dsh-client-ui-primitives 缺少: ${missing.join(", ")}。图标导出命名已从尺寸后缀(16/14)改为字重后缀(Regular/Medium),请同步更新源码引用。`);
|
|
38093
|
+
}
|
|
38098
38094
|
/** Services required before mounting. */
|
|
38099
38095
|
const inject = [
|
|
38100
38096
|
"slots",
|
|
@@ -38315,12 +38311,12 @@ window.__ModuleLoader__.load({
|
|
|
38315
38311
|
{
|
|
38316
38312
|
id: "editor",
|
|
38317
38313
|
label: "编辑器",
|
|
38318
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
38314
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutlineRegular, {})
|
|
38319
38315
|
},
|
|
38320
38316
|
{
|
|
38321
38317
|
id: "folder",
|
|
38322
38318
|
label: "文件夹",
|
|
38323
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
38319
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenRegular, {})
|
|
38324
38320
|
},
|
|
38325
38321
|
{
|
|
38326
38322
|
id: "vscode",
|
|
@@ -38340,7 +38336,7 @@ window.__ModuleLoader__.load({
|
|
|
38340
38336
|
className: "filex-header-btn",
|
|
38341
38337
|
size: "sm",
|
|
38342
38338
|
variant: "outline",
|
|
38343
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
38339
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutlineRegular, {}),
|
|
38344
38340
|
title,
|
|
38345
38341
|
"aria-label": "文件预览 / 编辑",
|
|
38346
38342
|
onClick: (e) => {
|
|
@@ -38352,25 +38348,24 @@ window.__ModuleLoader__.load({
|
|
|
38352
38348
|
}
|
|
38353
38349
|
/**
|
|
38354
38350
|
* The hero ButtonGroup: the left button carries the remembered mode's icon and
|
|
38355
|
-
* runs its action, the right chevron opens the mode dropdown. Rendered
|
|
38356
|
-
*
|
|
38357
|
-
*
|
|
38351
|
+
* runs its action, the right chevron opens the mode dropdown. Rendered only
|
|
38352
|
+
* through the `hero.flex` slot declared by the dsh-hero-flex plugin (hero /
|
|
38353
|
+
* blank-session header) — the in-chat header keeps the single built-in-editor
|
|
38358
38354
|
* `HeaderIcon`.
|
|
38359
38355
|
*
|
|
38360
|
-
* `sessionId`
|
|
38361
|
-
*
|
|
38362
|
-
*
|
|
38356
|
+
* `sessionId` comes from the slot's session standard kit; without it the
|
|
38357
|
+
* actions fall back to the live selection (`useSessions` → `state.current`)
|
|
38358
|
+
* and show a notice when none exists.
|
|
38363
38359
|
*/
|
|
38364
38360
|
function HeroGroup(props) {
|
|
38365
38361
|
const state = (0, react.useSyncExternalStore)(subscribe, getSnapshot);
|
|
38366
38362
|
const [menuOpen, setMenuOpen] = (0, react.useState)(false);
|
|
38367
38363
|
const useSessions = props.useSessions ?? (() => void 0);
|
|
38368
|
-
const
|
|
38369
|
-
const sessionId = props.sessionId ?? current;
|
|
38364
|
+
const sessionId = props.sessionId;
|
|
38370
38365
|
const sessionCwd = useSessions((s) => sessionId !== void 0 ? s.byId?.[sessionId]?.cwd : void 0);
|
|
38371
38366
|
const mode = state.mode;
|
|
38372
38367
|
const items = state.vscode ? MODE_ITEMS : MODE_ITEMS.filter((item) => item.id !== "vscode");
|
|
38373
|
-
const mainIcon = mode === "folder" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
38368
|
+
const mainIcon = mode === "folder" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenRegular, {}) : mode === "vscode" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VscodeIcon, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutlineRegular, {});
|
|
38374
38369
|
const mainTitle = mode === "folder" ? "打开系统文件夹" : mode === "vscode" ? "用 VSCode 打开工作区" : "文件预览 / 编辑(Ctrl+P)";
|
|
38375
38370
|
const onMainClick = (e) => {
|
|
38376
38371
|
e.stopPropagation();
|
|
@@ -38420,169 +38415,12 @@ window.__ModuleLoader__.load({
|
|
|
38420
38415
|
e.stopPropagation();
|
|
38421
38416
|
setMenuOpen((v) => !v);
|
|
38422
38417
|
},
|
|
38423
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
38418
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {})
|
|
38424
38419
|
})]
|
|
38425
38420
|
})
|
|
38426
38421
|
})
|
|
38427
38422
|
});
|
|
38428
38423
|
}
|
|
38429
|
-
/**
|
|
38430
|
-
* Corner expand button re-painted beside the explorer ButtonGroup while this
|
|
38431
|
-
* plugin occupies the header's corner seat. Matches the shell's `ExpandButton`
|
|
38432
|
-
* (28×28, mirrored panel icon, tooltip); the toggle goes through the
|
|
38433
|
-
* cross-plugin `sidebarRight` face. Hides itself while the panel is open,
|
|
38434
|
-
* exactly like the shipped button — the DOM probe (`data-sidebar-right-open`)
|
|
38435
|
-
* is the same attribute the shell's panel root carries, so it tracks even
|
|
38436
|
-
* opens that did not come from this button.
|
|
38437
|
-
*/
|
|
38438
|
-
function CornerExpandButton(props) {
|
|
38439
|
-
const [expanded, setExpanded] = (0, react.useState)(false);
|
|
38440
|
-
(0, react.useEffect)(() => {
|
|
38441
|
-
const probe = () => {
|
|
38442
|
-
setExpanded(document.querySelector("[data-sidebar-right-open]") !== null);
|
|
38443
|
-
};
|
|
38444
|
-
probe();
|
|
38445
|
-
const observer = new MutationObserver(probe);
|
|
38446
|
-
observer.observe(document.body, {
|
|
38447
|
-
childList: true,
|
|
38448
|
-
subtree: true,
|
|
38449
|
-
attributes: true,
|
|
38450
|
-
attributeFilter: ["data-sidebar-right-open"]
|
|
38451
|
-
});
|
|
38452
|
-
window.addEventListener("resize", probe);
|
|
38453
|
-
return () => {
|
|
38454
|
-
observer.disconnect();
|
|
38455
|
-
window.removeEventListener("resize", probe);
|
|
38456
|
-
};
|
|
38457
|
-
}, []);
|
|
38458
|
-
if (expanded) return null;
|
|
38459
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
38460
|
-
label: "展开右侧栏",
|
|
38461
|
-
side: "bottom",
|
|
38462
|
-
delayMs: 500,
|
|
38463
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
38464
|
-
type: "button",
|
|
38465
|
-
className: "filex-corner-expand",
|
|
38466
|
-
"aria-label": "展开右侧栏",
|
|
38467
|
-
"data-sidebar-right-expand": "",
|
|
38468
|
-
onClick: () => {
|
|
38469
|
-
props.sidebar.toggleExpanded();
|
|
38470
|
-
},
|
|
38471
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPanelLeftOutline16, { className: "filex-corner-expand-icon" })
|
|
38472
|
-
})
|
|
38473
|
-
});
|
|
38474
|
-
}
|
|
38475
|
-
/**
|
|
38476
|
-
* The header's corner seat while the new-session header chrome is blank: the
|
|
38477
|
-
* explorer ButtonGroup sits directly left of the expand button — the same
|
|
38478
|
-
* corner seat, one flex row, no floating layer, no measuring. In a session
|
|
38479
|
-
* with records the `utilities` seat renders (where the editor icon lives), so
|
|
38480
|
-
* only the expand button is re-painted here; the DOM probe keeps the pair
|
|
38481
|
-
* from ever duplicating.
|
|
38482
|
-
*/
|
|
38483
|
-
function HeaderCornerGroup(props) {
|
|
38484
|
-
const [utilitiesPresent, setUtilitiesPresent] = (0, react.useState)(false);
|
|
38485
|
-
const [sidebar, setSidebar] = (0, react.useState)(void 0);
|
|
38486
|
-
(0, react.useEffect)(() => {
|
|
38487
|
-
let alive = true;
|
|
38488
|
-
let tries = 0;
|
|
38489
|
-
const read = () => {
|
|
38490
|
-
if (!alive) return;
|
|
38491
|
-
let value;
|
|
38492
|
-
try {
|
|
38493
|
-
value = ctxRef?.get?.("sidebarRight", false) ?? ctxRef?.sidebarRight;
|
|
38494
|
-
} catch {
|
|
38495
|
-
value = void 0;
|
|
38496
|
-
}
|
|
38497
|
-
if (value !== void 0) {
|
|
38498
|
-
setSidebar(value);
|
|
38499
|
-
return;
|
|
38500
|
-
}
|
|
38501
|
-
tries += 1;
|
|
38502
|
-
if (tries < 40) setTimeout(read, 250);
|
|
38503
|
-
};
|
|
38504
|
-
read();
|
|
38505
|
-
return () => {
|
|
38506
|
-
alive = false;
|
|
38507
|
-
};
|
|
38508
|
-
}, []);
|
|
38509
|
-
(0, react.useEffect)(() => {
|
|
38510
|
-
const probe = () => {
|
|
38511
|
-
setUtilitiesPresent(document.querySelector("[data-slot=\"conversation.session.header.utilities\"]") !== null);
|
|
38512
|
-
};
|
|
38513
|
-
probe();
|
|
38514
|
-
const observer = new MutationObserver(probe);
|
|
38515
|
-
observer.observe(document.body, {
|
|
38516
|
-
childList: true,
|
|
38517
|
-
subtree: true
|
|
38518
|
-
});
|
|
38519
|
-
return () => observer.disconnect();
|
|
38520
|
-
}, []);
|
|
38521
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
38522
|
-
className: "filex-corner",
|
|
38523
|
-
children: [!utilitiesPresent && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeroGroup, { sessionId: props.sessionId }), sidebar !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CornerExpandButton, { sidebar })]
|
|
38524
|
-
});
|
|
38525
|
-
}
|
|
38526
|
-
/**
|
|
38527
|
-
* Hero / new-session floating utility: the hero ButtonGroup, rendered by the
|
|
38528
|
-
* plugin itself through the generic `shell.overlay` floating layer (no shell
|
|
38529
|
-
* change needed) and pinned to the conversation column's top-right corner —
|
|
38530
|
-
* the spot where the session-header utilities sit once a conversation has
|
|
38531
|
-
* records. Shown only while the conversation column is in its `hero` phase
|
|
38532
|
-
* (no session at all, or a blank session whose header is deliberately
|
|
38533
|
-
* hidden); the position is measured from the rendered column root
|
|
38534
|
-
* (`[data-phase]`), so sidebar collapse and details-panel toggles are
|
|
38535
|
-
* tracked automatically. Clicking behaves exactly like the in-chat icon: in a
|
|
38536
|
-
* blank-session hero the actions bind to that session, and with no session
|
|
38537
|
-
* at all they surface the no-session notice.
|
|
38538
|
-
*
|
|
38539
|
-
* On the new runtime the corner seat carries the same ButtonGroup, so this
|
|
38540
|
-
* fallback hides itself the moment `.filex-corner` is rendered — the two
|
|
38541
|
-
* paths never overlap.
|
|
38542
|
-
*/
|
|
38543
|
-
function HeroFilexButton(props) {
|
|
38544
|
-
const [pos, setPos] = (0, react.useState)(null);
|
|
38545
|
-
(0, react.useEffect)(() => {
|
|
38546
|
-
const update = () => {
|
|
38547
|
-
if (document.querySelector(".filex-corner") !== null) {
|
|
38548
|
-
setPos(null);
|
|
38549
|
-
return;
|
|
38550
|
-
}
|
|
38551
|
-
const column = document.querySelector("[data-phase]");
|
|
38552
|
-
if (column === null || column.getAttribute("data-phase") !== "hero") {
|
|
38553
|
-
setPos(null);
|
|
38554
|
-
return;
|
|
38555
|
-
}
|
|
38556
|
-
const rect = column.getBoundingClientRect();
|
|
38557
|
-
if (rect.width === 0 || rect.height === 0) {
|
|
38558
|
-
setPos(null);
|
|
38559
|
-
return;
|
|
38560
|
-
}
|
|
38561
|
-
const top = rect.top + 14;
|
|
38562
|
-
const right = window.innerWidth - rect.right + 28;
|
|
38563
|
-
setPos((current) => current !== null && current.top === top && current.right === right ? current : {
|
|
38564
|
-
top,
|
|
38565
|
-
right
|
|
38566
|
-
});
|
|
38567
|
-
};
|
|
38568
|
-
const timer = window.setInterval(update, 400);
|
|
38569
|
-
window.addEventListener("resize", update);
|
|
38570
|
-
update();
|
|
38571
|
-
return () => {
|
|
38572
|
-
window.clearInterval(timer);
|
|
38573
|
-
window.removeEventListener("resize", update);
|
|
38574
|
-
};
|
|
38575
|
-
}, []);
|
|
38576
|
-
if (pos === null) return null;
|
|
38577
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
38578
|
-
className: "filex-hero-fab",
|
|
38579
|
-
style: {
|
|
38580
|
-
top: pos.top,
|
|
38581
|
-
right: pos.right
|
|
38582
|
-
},
|
|
38583
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeroGroup, { useSessions: props.useSessions })
|
|
38584
|
-
});
|
|
38585
|
-
}
|
|
38586
38424
|
/** The overlay entry: keyboard shortcut + modal + transient notice. */
|
|
38587
38425
|
function ExplorerOverlay(props) {
|
|
38588
38426
|
const state = (0, react.useSyncExternalStore)(subscribe, getSnapshot);
|
|
@@ -38637,6 +38475,7 @@ window.__ModuleLoader__.load({
|
|
|
38637
38475
|
} catch {}
|
|
38638
38476
|
};
|
|
38639
38477
|
try {
|
|
38478
|
+
assertPrimitives();
|
|
38640
38479
|
const tagId = "dsh-file-explorer/styles";
|
|
38641
38480
|
ctx.effect(() => {
|
|
38642
38481
|
const dark = detectDark();
|
|
@@ -38658,29 +38497,21 @@ window.__ModuleLoader__.load({
|
|
|
38658
38497
|
order: 10,
|
|
38659
38498
|
label: "文件预览 / 编辑"
|
|
38660
38499
|
}, HeaderIcon));
|
|
38661
|
-
ctx.slots.inject("shell.overlay", () => {
|
|
38662
|
-
|
|
38663
|
-
|
|
38664
|
-
|
|
38665
|
-
|
|
38666
|
-
|
|
38667
|
-
|
|
38668
|
-
|
|
38669
|
-
|
|
38670
|
-
|
|
38671
|
-
|
|
38672
|
-
|
|
38673
|
-
|
|
38674
|
-
|
|
38675
|
-
|
|
38676
|
-
heroFab();
|
|
38677
|
-
};
|
|
38678
|
-
});
|
|
38679
|
-
ctx.slots.inject("conversation.session.header.corner", () => ctx.slots.register({
|
|
38680
|
-
name: "conversation.session.header.corner",
|
|
38681
|
-
priority: -1,
|
|
38682
|
-
label: "文件预览 / 编辑(角落)"
|
|
38683
|
-
}, (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeaderCornerGroup, { sessionId: props.sessionId })));
|
|
38500
|
+
ctx.slots.inject("shell.overlay", () => ctx.slots.register({
|
|
38501
|
+
name: "shell.overlay",
|
|
38502
|
+
id: "file-explorer-overlay",
|
|
38503
|
+
order: 100,
|
|
38504
|
+
label: "文件预览"
|
|
38505
|
+
}, ExplorerOverlay));
|
|
38506
|
+
ctx.slots.inject("hero.flex", () => ctx.slots.register({
|
|
38507
|
+
name: "hero.flex",
|
|
38508
|
+
id: "file-explorer",
|
|
38509
|
+
order: 10,
|
|
38510
|
+
label: "文件预览 / 编辑(hero)"
|
|
38511
|
+
}, (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeroGroup, {
|
|
38512
|
+
sessionId: props.sessionId,
|
|
38513
|
+
useSessions: props.useSessions
|
|
38514
|
+
})));
|
|
38684
38515
|
probeVscode();
|
|
38685
38516
|
ctx.effect(() => {
|
|
38686
38517
|
const deps = {
|
|
@@ -117,16 +117,6 @@ export interface FilexSlotsService {
|
|
|
117
117
|
}, component: unknown): () => void;
|
|
118
118
|
inject(key: string, callback: () => () => void): () => void;
|
|
119
119
|
}
|
|
120
|
-
/**
|
|
121
|
-
* The cross-plugin right-sidebar face (`dsh-client-ui-sidebar-right` provides
|
|
122
|
-
* it as `ctx.sidebarRight`). Only the expand-toggle and expanded-state read
|
|
123
|
-
* are used here, to re-paint the header's corner expand button beside the
|
|
124
|
-
* explorer ButtonGroup while the plugin occupies the corner seat.
|
|
125
|
-
*/
|
|
126
|
-
export interface FilexSidebarRight {
|
|
127
|
-
isExpanded(): boolean;
|
|
128
|
-
toggleExpanded(): void;
|
|
129
|
-
}
|
|
130
120
|
declare module 'cordis' {
|
|
131
121
|
interface Context {
|
|
132
122
|
webServer: FilexWebServer;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-xhl/dsh-file-explorer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "DSH 外部编译型插件:工作区文件预览 / 编辑(CodeMirror 6 编辑器 + 文件树 + 全文搜索 + Markdown/HTML/图片/PDF 预览)。移植自同目录下的动态 Cordis 插件版本。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -41,9 +41,10 @@
|
|
|
41
41
|
"README.md"
|
|
42
42
|
],
|
|
43
43
|
"scripts": {
|
|
44
|
-
"build": "tsc -p tsconfig.build.json && tsdown",
|
|
44
|
+
"build": "tsc -p tsconfig.build.json && tsdown && node scripts/check-icons.mjs",
|
|
45
45
|
"typecheck": "tsc --noEmit",
|
|
46
46
|
"bundle": "tsdown",
|
|
47
|
+
"check:icons": "node scripts/check-icons.mjs",
|
|
47
48
|
"prepare": "tsdown"
|
|
48
49
|
},
|
|
49
50
|
"license": "MIT",
|