dsh-drop-path
拖入本地文件时,自动把文件的电脑绝对路径填入输入框,并复制到剪贴板。 Drop a local file into DSH and its absolute filesystem path lands in the composer.
DSH 的输入框只接受图片附件,拖其它文件(.txt / .pdf / .zip / 代码……)通常会被拒绝,什么也不会发生。 本插件接管这类被拒绝的拖放:提取文件在磁盘上的真实路径,写进输入框,顺手复制到剪贴板 —— 于是可以直接把路径发给模型去读。
原理
DSH 桌面端 preload 暴露了 window.__DSH_DESKTOP_FILE_PATH__.getPathForFile(file)(一个
contextBridge 桥,内部是 Electron 的 webUtils.getPathForFile),本插件用它把操作系统拖拽的
File 对象解析成真实路径,例如 C:\work\report.pdf、/home/you/report.pdf。
插件本体是一个 DSH 服务端 bundle 插件(cordis 风格 name / inject / apply):
ctx.webServer.register注册/dsh-drop-path/script.js;ctx.webServer.tapIndex把<script defer src="/dsh-drop-path/script.js"></script>注入网页index;- 注入的脚本在
window捕获阶段监听拖放,把路径写进当前输入框 (兼容textarea与contenteditable,对 React 受控输入走原生 setter +input事件)。
行为模式
左下角有一个小的「路径拖放」开关,点按钮循环切换或下拉选择,状态存在浏览器 localStorage:
| 模式 | 说明 |
|---|---|
| 自动(默认) | 非图片文件(含未知类型)→ 填入路径;纯图片拖放 → 交回 DSH 当附件处理。 |
| 总是 | 所有文件的拖放都填入路径(包括图片)。 |
| 关闭 | 不拦截,恢复 DSH 原行为。 |
控制台排障:
window.__dshDropPath.reset() // 强制清掉可能残留的拖拽浮层
window.__dshDropPath.setMode('off'|'auto'|'always') // 直接切模式
window.__dshDropPath.mode() // 读当前模式
实现要点:不要和 DSH 自带的拖放浮层打架
DSH 的 ComposerAttachments(@deepseek-ai/dsh-client-ui-attachment)在 document 上以冒泡方式监听
dragenter / dragover / dragleave / drop,dragenter 时就 setDragActive(true) 弹出「拖入图片」浮层,
而浮层只有它自己的 drop 处理或 window 的 dragend 才会关掉:
const onDragEnter = (event) => { /* 有 Files 就 */ event.preventDefault(); dragDepth.current += 1; setDragActive(true) }
const onDrop = (event) => { ...; event.preventDefault(); reset(); if (canAcceptDrop) onAddImages([...dataTransfer.files]) }
window.addEventListener('dragend', reset)
又因为它的 canAcceptDrop 只判断「编辑器是否空闲、是否支持图片」而不看文件类型,
所以拖任何文件都会弹浮层。于是有一个很容易踩的坑:如果在 document 的捕获阶段监听 drop
并 stopPropagation(),DSH 的 onDrop 就永远不会执行 → reset() 不被调用 → 浮层永久卡在屏幕上。
本插件的做法:
- 监听挂在
window的捕获阶段(早于document上的任何监听)。要接管的拖放直接stopPropagation(), DSH 完全看不到这次拖拽 → 浮层根本不会出现,也就无从卡住。 - 开始接管时向 window 派发一次
dragend,强制 DSH 的reset()跑一遍,清掉可能残留的浮层; 用syntheticDragEnd标记避免这个自派事件误清自身状态。 drop阶段用真实的File.type再确认一次:若最终是纯图片,不吞事件,交回 DSH 正常当附件收下 (DSH 的onDrop不依赖dragActive,所以浮层没出现也能收下图片)。- 1.2s 看门狗:拖拽被 Esc 取消、
dragleave丢失时,提示条自己消失,绝不留残影。
安装
已发布到 npm 后:
dsh plugin --profile <profile> add dsh-drop-path
本地开发(把本仓库目录 link 进 profile):
dsh plugin --profile <profile> add link:<path-to-this-repo>
# 等价于在 profile 目录执行:pnpm add link:<path-to-this-repo>
手动方式:把包装进 profile 的 node_modules,并把 dsh-drop-path 加进
<DSH_HOME>/profiles/<profile>/package.json 的 dsh.profile.bundles 数组:
"dsh": {
"profile": {
"bundles": [
/* ...原有 bundle... */
"dsh-drop-path"
]
}
}
生效
bundle 层是 profile 启动时读取的。新增 bundle 需重启 DSH(或让该 profile 重新加载), 之后刷新网页即可看到左下角的「路径拖放」开关。
注意:仅改动
client/drop-path.js(客户端脚本)时不需要重启 —— 服务端路由每次请求都重新读文件 并带Cache-Control: no-store,刷新页面即可。
测试
node test/logic.test.mjs
用最小 DOM 桩在 Node 里加载 client/drop-path.js 并驱动事件,无需浏览器。覆盖 19 条断言:
非图片 / 图片 / 混合 / 关闭 / 无 preload 桥 的接管判定、提示条显隐、剪贴板内容,
以及「拖拽被 Esc 取消时提示条必须自动消失」的看门狗行为。改客户端逻辑后请先跑它。
兼容性
- 仅桌面端有意义:依赖 DSH Desktop 的 preload 桥。拿不到桥时会自动完全不接管,不影响 DSH 原行为。
- 仅桌面端路径:普通浏览器出于安全不暴露本地路径,本插件在这种情况下自动降级。
文件结构
dsh-drop-path/
package.json # 声明 dsh.bundle.patch -> ./cordis.patch.yml
cordis.patch.yml # 把 dsh-drop-path 插入 profile 层栈
lib/index.js # 服务端:注册路由 + tapIndex 注入脚本
client/drop-path.js # 注入到页面的客户端逻辑
test/dom-harness.mjs # 最小 DOM 桩
test/logic.test.mjs # 回归测试
License
MIT
No comments yet. Be the first to write one.