Bundle
dsh-drop-path
拖入本地文件时,自动把文件在电脑上的绝对路径填入输入框并复制到剪贴板 | Fill a dropped local file's absolute path into the DSH composer
- Source
- kongbai-shike
- License
- MIT
- Updated
- Updated yesterday
Readme
# 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 原行为。 |
控制台排障:
```js
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` 才会关掉**:
```js
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 后:
```powershell
dsh plugin --profile <profile> add dsh-drop-path
```
本地开发(把本仓库目录 link 进 profile):
```powershell
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` 数组:
```jsonc
"dsh": {
"profile": {
"bundles": [
/* ...原有 bundle... */
"dsh-drop-path"
]
}
}
```
## 生效
bundle 层是 **profile 启动时**读取的。新增 bundle 需**重启 DSH**(或让该 profile 重新加载),
之后刷新网页即可看到左下角的「路径拖放」开关。
> 注意:仅改动 `client/drop-path.js`(客户端脚本)时**不需要重启** —— 服务端路由每次请求都重新读文件
> 并带 `Cache-Control: no-store`,刷新页面即可。
## 测试
```bash
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
Install
dsh plugin --profile web add github:kongbai-shike/dsh-drop-path
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-drop-path from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.