dsh-vscode-mode 0.1.53 → 0.1.55
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 +11 -0
- package/assets/vendor/pdfjs/build/pdf.mjs +26370 -0
- package/assets/vendor/pdfjs/build/pdf.worker.mjs +61080 -0
- package/assets/vendor/pdfjs/cmaps/78-EUC-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/78-EUC-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/78-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/78-RKSJ-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/78-RKSJ-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/78-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/78ms-RKSJ-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/78ms-RKSJ-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/83pv-RKSJ-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/90ms-RKSJ-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/90ms-RKSJ-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/90msp-RKSJ-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/90msp-RKSJ-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/90pv-RKSJ-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/90pv-RKSJ-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Add-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Add-RKSJ-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Add-RKSJ-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Add-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-CNS1-0.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-CNS1-1.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-CNS1-2.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-CNS1-3.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-CNS1-4.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-CNS1-5.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-CNS1-6.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-GB1-0.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-GB1-1.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-GB1-2.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-GB1-3.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-GB1-4.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-GB1-5.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-Japan1-0.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-Japan1-1.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-Japan1-2.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-Japan1-3.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-Japan1-4.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-Japan1-5.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-Japan1-6.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-Korea1-0.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-Korea1-1.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-Korea1-2.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/B5-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/B5-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/B5pc-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/B5pc-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/CNS-EUC-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/CNS-EUC-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/CNS1-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/CNS1-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/CNS2-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/CNS2-V.bcmap +3 -0
- package/assets/vendor/pdfjs/cmaps/ETHK-B5-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/ETHK-B5-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/ETen-B5-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/ETen-B5-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/ETenms-B5-H.bcmap +3 -0
- package/assets/vendor/pdfjs/cmaps/ETenms-B5-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/EUC-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/EUC-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Ext-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Ext-RKSJ-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Ext-RKSJ-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Ext-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/GB-EUC-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/GB-EUC-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/GB-H.bcmap +4 -0
- package/assets/vendor/pdfjs/cmaps/GB-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/GBK-EUC-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/GBK-EUC-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/GBK2K-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/GBK2K-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/GBKp-EUC-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/GBKp-EUC-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/GBT-EUC-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/GBT-EUC-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/GBT-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/GBT-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/GBTpc-EUC-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/GBTpc-EUC-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/GBpc-EUC-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/GBpc-EUC-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/HKdla-B5-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/HKdla-B5-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/HKdlb-B5-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/HKdlb-B5-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/HKgccs-B5-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/HKgccs-B5-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/HKm314-B5-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/HKm314-B5-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/HKm471-B5-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/HKm471-B5-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/HKscs-B5-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/HKscs-B5-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Hankaku.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Hiragana.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/KSC-EUC-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/KSC-EUC-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/KSC-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/KSC-Johab-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/KSC-Johab-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/KSC-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/KSCms-UHC-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/KSCms-UHC-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/KSCpc-EUC-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/KSCpc-EUC-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Katakana.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/LICENSE +36 -0
- package/assets/vendor/pdfjs/cmaps/NWP-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/NWP-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/RKSJ-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/RKSJ-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/Roman.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniCNS-UCS2-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniCNS-UCS2-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniCNS-UTF16-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniCNS-UTF16-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniCNS-UTF32-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniCNS-UTF32-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniCNS-UTF8-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniCNS-UTF8-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniGB-UCS2-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniGB-UCS2-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniGB-UTF16-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniGB-UTF16-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniGB-UTF32-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniGB-UTF32-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniGB-UTF8-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniGB-UTF8-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniJIS-UCS2-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniJIS-UCS2-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniJIS-UTF16-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniJIS-UTF16-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniJIS-UTF32-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniJIS-UTF32-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniJIS-UTF8-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniJIS-UTF8-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniKS-UCS2-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniKS-UCS2-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniKS-UTF16-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniKS-UTF16-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniKS-UTF32-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniKS-UTF32-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniKS-UTF8-H.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/UniKS-UTF8-V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/V.bcmap +0 -0
- package/assets/vendor/pdfjs/cmaps/WP-Symbol.bcmap +0 -0
- package/assets/vendor/pdfjs/standard_fonts/FoxitDingbats.pfb +0 -0
- package/assets/vendor/pdfjs/standard_fonts/FoxitFixed.pfb +0 -0
- package/assets/vendor/pdfjs/standard_fonts/FoxitFixedBold.pfb +0 -0
- package/assets/vendor/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
- package/assets/vendor/pdfjs/standard_fonts/FoxitFixedItalic.pfb +0 -0
- package/assets/vendor/pdfjs/standard_fonts/FoxitSerif.pfb +0 -0
- package/assets/vendor/pdfjs/standard_fonts/FoxitSerifBold.pfb +0 -0
- package/assets/vendor/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
- package/assets/vendor/pdfjs/standard_fonts/FoxitSerifItalic.pfb +0 -0
- package/assets/vendor/pdfjs/standard_fonts/FoxitSymbol.pfb +0 -0
- package/assets/vendor/pdfjs/standard_fonts/LICENSE_FOXIT +27 -0
- package/assets/vendor/pdfjs/standard_fonts/LICENSE_LIBERATION +102 -0
- package/assets/vendor/pdfjs/standard_fonts/LiberationSans-Bold.ttf +0 -0
- package/assets/vendor/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
- package/assets/vendor/pdfjs/standard_fonts/LiberationSans-Italic.ttf +0 -0
- package/assets/vendor/pdfjs/standard_fonts/LiberationSans-Regular.ttf +0 -0
- package/assets/vendor/pdfjs/web/pdf_viewer.css +6989 -0
- package/assets/vendor/pdfjs/web/pdf_viewer.mjs +9459 -0
- package/lib/client.js +852 -13
- package/lib/client.js.map +1 -1
- package/lib/index.js +137 -17
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/compat.ts +31 -0
- package/src/client/imagePreview.ts +45 -0
- package/src/client/index.ts +46 -8
- package/src/client/pdf/pdfLoader.ts +82 -0
- package/src/client/pdf/pdfPanel.ts +239 -0
- package/src/client/pdfPreview.ts +48 -0
- package/src/client/remoteOpenRouter.ts +78 -0
- package/src/client/styles/editor.css +17 -0
- package/src/client/ui/EditorView.ts +221 -2
- package/src/compat.ts +1 -1
- package/src/dshVersion.ts +1 -0
- package/src/routes.ts +3 -0
- package/src/rpc.ts +65 -13
- package/src/shared/rpc.ts +50 -2
- package/src/store.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vscode-mode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.55",
|
|
4
4
|
"description": "DSH 上的类 VSCode 编码体验:Monaco 编辑器(文件页签/QuickOpen/状态栏,可驻 dsh-better-sidebar 侧边栏与对话同屏)+ Agent 编辑差异审查(整文件差异/采纳/拒绝/归档/回滚)+ 语言服务器(LSP)智能(跳转定义/引用/hover/大纲 + VSIX 扩展市场安装),状态持久化到工作区旁车",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
package/src/client/compat.ts
CHANGED
|
@@ -92,3 +92,34 @@ export function patchMethod<T extends object, K extends keyof T>(
|
|
|
92
92
|
if (owner[key] === patched) owner[key] = original
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
|
+
|
|
96
|
+
/** patchAccessor 的包装实现形状(original 为补丁前的实时实现)。 */
|
|
97
|
+
export type PatchWrapper = (original: (...args: never[]) => unknown, ...args: never[]) => unknown
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* accessor 感知的链式补丁:DSH 0.1.3+ 的 remote 命名空间方法(如
|
|
101
|
+
* remote.session.openWorkspacePath)是 getter-only accessor(Object.defineProperty
|
|
102
|
+
* 仅定义 get,直接赋值在严格模式下抛 TypeError),须整体替换属性描述符。
|
|
103
|
+
* 新 getter 每次访问现场调用原 getter 取最新一次性函数作 original,天然跟随
|
|
104
|
+
* DSH 内部 methods 表变化;data 属性降级走 patchMethod;属性缺失返回 null。
|
|
105
|
+
* @author ddj 2026年09月08号
|
|
106
|
+
* @param owner 目标对象
|
|
107
|
+
* @param key 属性名
|
|
108
|
+
* @param wrapper 包装实现(original 为补丁前的实时实现)
|
|
109
|
+
* @returns 恢复函数(幂等、带归属校验);属性缺失时返回 null
|
|
110
|
+
*/
|
|
111
|
+
export function patchAccessor(owner: object, key: string, wrapper: PatchWrapper): (() => void) | null {
|
|
112
|
+
const descriptor = Object.getOwnPropertyDescriptor(owner, key)
|
|
113
|
+
if (!descriptor) return null
|
|
114
|
+
if (typeof descriptor.get !== 'function') return patchMethod(owner, key as never, wrapper as never)
|
|
115
|
+
const rawGet = descriptor.get as (this: object) => unknown
|
|
116
|
+
const patchedGet = function (): unknown {
|
|
117
|
+
const original = (...args: never[]) => (rawGet.call(owner) as (...args: never[]) => unknown)(...args)
|
|
118
|
+
return (...args: never[]) => wrapper(original, ...args)
|
|
119
|
+
}
|
|
120
|
+
Object.defineProperty(owner, key, { configurable: true, enumerable: descriptor.enumerable, get: patchedGet })
|
|
121
|
+
return () => {
|
|
122
|
+
const current = Object.getOwnPropertyDescriptor(owner, key)
|
|
123
|
+
if (current?.get === patchedGet) Object.defineProperty(owner, key, descriptor)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode client — 图片文件判定与 data URL 组装(编辑区图片预览面板用)。
|
|
3
|
+
* 纯函数、无 DOM 依赖,便于单测;MIME 表收敛在 shared/rpc.ts(host base64 响应同源)。
|
|
4
|
+
* @author ddj 2026年09月08号
|
|
5
|
+
*/
|
|
6
|
+
import { IMAGE_MIME } from '../shared/rpc.js'
|
|
7
|
+
|
|
8
|
+
/** 可预览的图片扩展名集合(小写、无点)。 */
|
|
9
|
+
const IMAGE_EXT_SET: Set<string> = new Set(Object.keys(IMAGE_MIME))
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 判断路径是否为可预览的图片文件(取 basename 扩展名,大小写不敏感)。
|
|
13
|
+
* @author ddj 2026年09月08号
|
|
14
|
+
* @param path 文件路径(`/` 或 `\` 分隔均可)
|
|
15
|
+
* @returns 是否图片文件
|
|
16
|
+
*/
|
|
17
|
+
export function isImagePath(path: string): boolean {
|
|
18
|
+
const base = String(path || '').split(/[\\/]/).pop() || ''
|
|
19
|
+
const dot = base.lastIndexOf('.')
|
|
20
|
+
if (dot <= 0) return false
|
|
21
|
+
return IMAGE_EXT_SET.has(base.slice(dot + 1).toLowerCase())
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 判断路径是否为 SVG 文件(唯一提供「以文本打开」切换的图片格式)。
|
|
26
|
+
* @author ddj 2026年09月08号
|
|
27
|
+
* @param path 文件路径(`/` 或 `\` 分隔均可)
|
|
28
|
+
* @returns 是否 SVG
|
|
29
|
+
*/
|
|
30
|
+
export function isSvgPath(path: string): boolean {
|
|
31
|
+
const base = String(path || '').split(/[\\/]/).pop() || ''
|
|
32
|
+
const dot = base.lastIndexOf('.')
|
|
33
|
+
return dot > 0 && base.slice(dot + 1).toLowerCase() === 'svg'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* base64 文本 → 可直接赋给 `<img src>` 的 data URL。
|
|
38
|
+
* @author ddj 2026年09月08号
|
|
39
|
+
* @param base64 base64 文本(不含 data: 前缀)
|
|
40
|
+
* @param mime MIME 类型(host edrv.read base64 响应携带)
|
|
41
|
+
* @returns data URL 字符串
|
|
42
|
+
*/
|
|
43
|
+
export function dataUrlOf(base64: string, mime: string): string {
|
|
44
|
+
return 'data:' + (mime || 'application/octet-stream') + ';base64,' + base64
|
|
45
|
+
}
|
package/src/client/index.ts
CHANGED
|
@@ -25,6 +25,7 @@ import { loadMonaco } from './monaco/loader.js'
|
|
|
25
25
|
import { createFileOpenerRegistry, scanSidebar, type FileOpenContext } from './fileOpeners.js'
|
|
26
26
|
import type { FileOpenerRegistry } from './fileOpeners.js'
|
|
27
27
|
import { installOpenPathRouter, vscodeOpener, autoValue } from './openPathRouter.js'
|
|
28
|
+
import { patchRemoteOpen, probeRemoteOpen } from './remoteOpenRouter.js'
|
|
28
29
|
import { setupExtOpen } from './externalOpen.js'
|
|
29
30
|
import { SettingsContext } from './settingsContext.js'
|
|
30
31
|
import { SIDEBAR_PLUGIN, pickSettingsBinder, registerSlotSafely } from './compat.js'
|
|
@@ -98,6 +99,17 @@ export function apply(ctx: any): void {
|
|
|
98
99
|
const binder = pickSettingsBinder(ctx)
|
|
99
100
|
const settings = binder.scope
|
|
100
101
|
let selected = autoValue('auto')
|
|
102
|
+
/** 0.1.3+ 会话文件链接路由(remote.session.openWorkspacePath)是否已安装(compatSummary 展示用)。 */
|
|
103
|
+
let remoteOpenInstalled = false
|
|
104
|
+
/** 两条文件链接路由共用的路由日志(openPathRouter / remoteOpenRouter)。 */
|
|
105
|
+
const routeLogger = (message: string): void => console.warn('[dsh-vscode-mode] ' + message)
|
|
106
|
+
/** 两条路由共用的 FileOpenContext:当前会话 id 与工作区 cwd。 */
|
|
107
|
+
const openContext = (): FileOpenContext => {
|
|
108
|
+
const current = sessions?.list?.getSnapshot?.()
|
|
109
|
+
const sessionId = current?.current
|
|
110
|
+
const summary = sessionId ? current?.byId?.[sessionId] : undefined
|
|
111
|
+
return { sessionId, cwd: summary?.cwd } as FileOpenContext
|
|
112
|
+
}
|
|
101
113
|
/** 可选探测 betterSidebar 服务(不进 inject:缺失会让插件停靠等待,杀死回退路径)。 */
|
|
102
114
|
let sideService = detectSidebarService(ctx)
|
|
103
115
|
/** 「添加到对话」动作集:编辑区/Tab 右键菜单注入文件引用与代码块(conversation 服务缺失时各动作安全降级)。 */
|
|
@@ -106,7 +118,8 @@ export function apply(ctx: any): void {
|
|
|
106
118
|
/** 客户端侧兼容摘要(与 host 报告合并展示;惰性求值保证 HMR 后仍新鲜)。 */
|
|
107
119
|
const compatSummary = (): CompatAdapter[] => [
|
|
108
120
|
{ name: '设置桥', active: settings !== undefined, note: settings !== undefined ? '使用 ' + binder.service + ' 桥' : '未绑定设置服务(fileOpenTool 持久化不可用)' },
|
|
109
|
-
{ name: '文件打开路由(workspaces.openPath)', active: Boolean(workspaces?.openPath), note: 'vscode
|
|
121
|
+
{ name: '文件打开路由(workspaces.openPath)', active: Boolean(workspaces?.openPath), note: 'vscode 打开器优先,失败回退系统打开(0.1.2 及更早 DSH 的对话文件链接主路径)' },
|
|
122
|
+
{ name: '会话文件链接路由(remote.session.openWorkspacePath)', active: remoteOpenInstalled, note: '0.1.3+ 对话文件引用/产物打开优先走插件打开器,失败回退系统打开' },
|
|
110
123
|
{ name: '侧边栏打开器(' + SIDEBAR_PLUGIN + ')', active: registry.get(SIDEBAR_PLUGIN) !== undefined, note: registry.get(SIDEBAR_PLUGIN) !== undefined ? '已注册(优先级 80)' : '未检测到侧边栏打开能力' },
|
|
111
124
|
{ name: '侧边栏编辑区(' + SIDEBAR_PLUGIN + ')', active: sideService !== undefined, note: sideService !== undefined ? '编辑区=侧边栏 Tab(对话+编辑同屏)' : '未检测到;安装 dsh-better-sidebar 后刷新启用侧边栏形态(' + SIDEBAR_INSTALL_CMD + ')' },
|
|
112
125
|
]
|
|
@@ -173,15 +186,40 @@ export function apply(ctx: any): void {
|
|
|
173
186
|
workspaces,
|
|
174
187
|
registry,
|
|
175
188
|
selected: () => selected,
|
|
176
|
-
context:
|
|
177
|
-
|
|
178
|
-
const sessionId = current?.current
|
|
179
|
-
const summary = sessionId ? current?.byId?.[sessionId] : undefined
|
|
180
|
-
return { sessionId, cwd: summary?.cwd } as FileOpenContext
|
|
181
|
-
},
|
|
182
|
-
logger: (message: string) => console.warn('[dsh-vscode-mode] ' + message),
|
|
189
|
+
context: openContext,
|
|
190
|
+
logger: routeLogger,
|
|
183
191
|
}), 'vscode-mode: file link routing')
|
|
184
192
|
}
|
|
193
|
+
// 0.1.3+ 会话文件链接主路径:dsh-client-ui-chat 的 openFile 直接调
|
|
194
|
+
// remote.session.openWorkspacePath(session/openWorkspacePath RPC),绕开
|
|
195
|
+
// workspaces.openPath;该 namespace 由 api-remotes 在 loader.await 后挂载,
|
|
196
|
+
// 晚于本插件 apply,且旧版 DSH 可能整段不存在——不加 inject(避免旧版停等
|
|
197
|
+
// 永不激活),改为定时探测有界重试(与侧栏服务探测同款节奏)。
|
|
198
|
+
let remoteRetries = 0
|
|
199
|
+
const retryRemoteOpen = (): void => {
|
|
200
|
+
if (remoteOpenInstalled || remoteRetries >= 15) {
|
|
201
|
+
if (!remoteOpenInstalled) console.warn('[dsh-vscode-mode] 未探测到 remote.session.openWorkspacePath,0.1.3+ 会话文件链接路由未安装')
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
remoteRetries += 1
|
|
205
|
+
schedule(() => {
|
|
206
|
+
if (remoteOpenInstalled) return
|
|
207
|
+
const service = probeRemoteOpen(ctx)
|
|
208
|
+
if (!service) {
|
|
209
|
+
retryRemoteOpen()
|
|
210
|
+
return
|
|
211
|
+
}
|
|
212
|
+
const disposer = patchRemoteOpen(service, { registry, selected: () => selected, context: openContext, logger: routeLogger })
|
|
213
|
+
if (!disposer) {
|
|
214
|
+
console.warn('[dsh-vscode-mode] remote.session 存在但 openWorkspacePath 不可补丁,会话文件链接路由未安装')
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
remoteOpenInstalled = true
|
|
218
|
+
ctx.effect(() => disposer, 'vscode-mode: remote file link routing')
|
|
219
|
+
console.info('[dsh-vscode-mode] 已安装 0.1.3+ 会话文件链接路由(remote.session.openWorkspacePath)')
|
|
220
|
+
}, 2000)
|
|
221
|
+
}
|
|
222
|
+
retryRemoteOpen()
|
|
185
223
|
|
|
186
224
|
// 中央「文件编辑」页签(旧形态回退):类 VSCode 编辑器;侧边栏形态可用时编辑器住 betterSidebar Tab,
|
|
187
225
|
// 本页签不注册(避免双实例:Monaco×2 + diff dock 每会话单源抢占)。
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* dsh-vscode-mode client — pdf.js 加载器(vendor 产物离线随包,/edrv/vendor/pdfjs/*)。
|
|
4
|
+
* 加载链:module script 内按序动态 import build/pdf.mjs(自挂 globalThis.pdfjsLib)→
|
|
5
|
+
* web/pdf_viewer.mjs(从 globalThis.pdfjsLib 解构)→ window.__edrvPdfjs handoff。
|
|
6
|
+
* 用 script 注入而非打包内 import():客户端包是 CJS,bundler 会改写动态 import 破坏 URL 语义。
|
|
7
|
+
* 作者 ddj 2026-09-22
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** vendor 根(routes.ts /edrv/vendor 前缀路由分发 assets/vendor/*)。 */
|
|
11
|
+
export const PDFJS_BASE = '/edrv/vendor/pdfjs'
|
|
12
|
+
/** pdf.js Worker 地址(GlobalWorkerOptions.workerSrc)。 */
|
|
13
|
+
export const PDFJS_WORKER_SRC = PDFJS_BASE + '/build/pdf.worker.mjs'
|
|
14
|
+
/** CJK 字符映射目录(中文 PDF 渲染必需,尾斜杠固定)。 */
|
|
15
|
+
export const PDFJS_CMAP_URL = PDFJS_BASE + '/cmaps/'
|
|
16
|
+
/** 标准字体目录(未嵌字体 PDF 兜底,尾斜杠固定)。 */
|
|
17
|
+
export const PDFJS_FONT_URL = PDFJS_BASE + '/standard_fonts/'
|
|
18
|
+
/** 组件层样式(pdfViewer/textLayer/annotationEditor 布局)。 */
|
|
19
|
+
const PDFJS_CSS_URL = PDFJS_BASE + '/web/pdf_viewer.css'
|
|
20
|
+
|
|
21
|
+
let pdfjsPromise = null
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 注入 pdf_viewer.css(幂等;vendor 路由带 etag 协商缓存,重复挂载走 304)。
|
|
25
|
+
* @author ddj 2026年09月22号
|
|
26
|
+
*/
|
|
27
|
+
function ensurePdfCss() {
|
|
28
|
+
if (typeof document === 'undefined') return
|
|
29
|
+
if (document.querySelector('link[data-edrv-pdfjs-css]')) return
|
|
30
|
+
const link = document.createElement('link')
|
|
31
|
+
link.rel = 'stylesheet'
|
|
32
|
+
link.href = PDFJS_CSS_URL
|
|
33
|
+
link.dataset.edrvPdfjsCss = '1'
|
|
34
|
+
document.head.appendChild(link)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 加载 pdf.js(core + viewer 组件层):去重 promise,失败重置允许重试(对齐 monaco loader)。
|
|
39
|
+
* @author ddj 2026年09月22号
|
|
40
|
+
* @returns Promise<{lib, viewer}> lib=build/pdf.mjs(getDocument/GlobalWorkerOptions/编辑器枚举等),viewer=web/pdf_viewer.mjs(PDFViewer/EventBus 等)
|
|
41
|
+
*/
|
|
42
|
+
export function loadPdfjs() {
|
|
43
|
+
if (!pdfjsPromise) {
|
|
44
|
+
pdfjsPromise = new Promise((resolve, reject) => {
|
|
45
|
+
ensurePdfCss()
|
|
46
|
+
const boot = () => {
|
|
47
|
+
const handoff = window.__edrvPdfjs
|
|
48
|
+
if (handoff && handoff.lib && handoff.viewer) {
|
|
49
|
+
try {
|
|
50
|
+
handoff.lib.GlobalWorkerOptions.workerSrc = PDFJS_WORKER_SRC
|
|
51
|
+
} catch (error) { /* worker 配置失败留给 getDocument 报错 */ }
|
|
52
|
+
resolve(handoff)
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
reject(new Error('pdf.js 模块加载失败:handoff 缺失'))
|
|
56
|
+
}
|
|
57
|
+
const fail = (error) => {
|
|
58
|
+
pdfjsPromise = null
|
|
59
|
+
reject(error instanceof Error ? error : new Error(String(error)))
|
|
60
|
+
}
|
|
61
|
+
const existing = document.querySelector('script[data-edrv-pdfjs-loader]')
|
|
62
|
+
if (existing) {
|
|
63
|
+
// 前一次注入仍在执行:等 handoff 出现(module 评估完即写入)
|
|
64
|
+
existing.addEventListener('edrv-pdfjs-ready', boot, { once: true })
|
|
65
|
+
if (window.__edrvPdfjs) boot()
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
const s = document.createElement('script')
|
|
69
|
+
s.type = 'module'
|
|
70
|
+
s.textContent =
|
|
71
|
+
'import * as lib from "' + PDFJS_BASE + '/build/pdf.mjs";\n' +
|
|
72
|
+
'import * as viewer from "' + PDFJS_BASE + '/web/pdf_viewer.mjs";\n' +
|
|
73
|
+
'window.__edrvPdfjs = { lib, viewer };\n' +
|
|
74
|
+
'document.querySelector("script[data-edrv-pdfjs-loader]")?.dispatchEvent(new Event("edrv-pdfjs-ready"));'
|
|
75
|
+
s.dataset.edrvPdfjsLoader = '1'
|
|
76
|
+
s.onload = boot
|
|
77
|
+
s.onerror = () => fail(new Error('pdf.js loader 脚本加载失败(检查 assets/vendor/pdfjs 完整 / /edrv/vendor 路由可达)'))
|
|
78
|
+
document.head.appendChild(s)
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
return pdfjsPromise
|
|
82
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* dsh-vscode-mode client — PDF 面板控制器(命令式 DOM,React 只渲染外壳 div)。
|
|
4
|
+
* 浏览:PDFViewer 组件(虚拟化渲染 + 文本层);编辑:内置注释编辑器
|
|
5
|
+
* (FreeText 文本框 / Ink 画笔 / Highlight 高亮),保存经 pdfDocument.saveDocument()
|
|
6
|
+
* → base64 → edrv.saveBinary 回写原文件。接线方式对齐官方 viewer(web/app.js)。
|
|
7
|
+
* 作者 ddj 2026-09-22
|
|
8
|
+
*/
|
|
9
|
+
import { rpc } from '../rpc.js'
|
|
10
|
+
import { bytesToBase64 } from '../pdfPreview.js'
|
|
11
|
+
import { loadPdfjs, PDFJS_CMAP_URL, PDFJS_FONT_URL } from './pdfLoader.js'
|
|
12
|
+
|
|
13
|
+
/** 无障碍占位 l10n:组件内 translate 为 fire-and-forget,stub 避免触发 locale 404。 */
|
|
14
|
+
const L10N_STUB = {
|
|
15
|
+
get: async (key) => key,
|
|
16
|
+
translate: async () => {},
|
|
17
|
+
getDirection: () => 'ltr',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* PDF 面板控制器工厂。
|
|
22
|
+
* @author ddj 2026年09月22号
|
|
23
|
+
* @param deps 依赖:sessionId/path(保存回写目标)+ 状态栏回调
|
|
24
|
+
* @returns 控制器:mount/destroy/savePdf/setMode/isDirty
|
|
25
|
+
*/
|
|
26
|
+
export function createPdfPanel(deps) {
|
|
27
|
+
const status = (t) => { try { deps.setStatus(t) } catch { /* 状态栏回调异常忽略 */ } }
|
|
28
|
+
|
|
29
|
+
let stage = null
|
|
30
|
+
let pageLabel = null
|
|
31
|
+
let eventBus = null
|
|
32
|
+
let busAbort = null
|
|
33
|
+
let editorTypes = null
|
|
34
|
+
let pdfViewer = null
|
|
35
|
+
let pdfDocument = null
|
|
36
|
+
let loadingTask = null
|
|
37
|
+
let modeButtons = null
|
|
38
|
+
let destroyed = false
|
|
39
|
+
let dirty = false
|
|
40
|
+
let saving = false
|
|
41
|
+
|
|
42
|
+
/** 脏状态上抛(tab 脏点)+ 本地记忆。 */
|
|
43
|
+
const setDirty = (value) => {
|
|
44
|
+
dirty = value
|
|
45
|
+
if (modeButtons) modeButtons.save.classList.toggle('edrv-pdf-dirty', value)
|
|
46
|
+
try { deps.onDirtyChange(value) } catch { /* 脏点回调异常忽略 */ }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 页码指示更新(pagechanging/pagesinit 事件)。 */
|
|
50
|
+
const updatePageLabel = () => {
|
|
51
|
+
if (!pageLabel || !pdfViewer) return
|
|
52
|
+
pageLabel.textContent = pdfViewer.currentPageNumber + ' / ' + pdfViewer.pagesCount
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** 编辑模式按钮高亮同步(annotationeditormodechanged 事件;evt.mode 为枚举数值)。 */
|
|
56
|
+
const syncModeButtons = (mode) => {
|
|
57
|
+
if (!modeButtons || !editorTypes) return
|
|
58
|
+
const byValue = { [editorTypes.NONE]: 'none', [editorTypes.FREETEXT]: 'freetext', [editorTypes.INK]: 'ink', [editorTypes.HIGHLIGHT]: 'highlight' }
|
|
59
|
+
const key = byValue[mode] ?? 'none'
|
|
60
|
+
for (const k of Object.keys(modeButtons.modes)) {
|
|
61
|
+
modeButtons.modes[k].classList.toggle('edrv-pdf-mode-on', k === key)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** 编辑模式符号键 → AnnotationEditorType 数值(版本间枚举值有差异,禁止硬编码)。 */
|
|
66
|
+
const modeValueOf = (key) => {
|
|
67
|
+
if (!editorTypes) return null
|
|
68
|
+
return { none: editorTypes.NONE, freetext: editorTypes.FREETEXT, ink: editorTypes.INK, highlight: editorTypes.HIGHLIGHT }[key] ?? null
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 切换编辑模式(进入注释编辑器某模式或退出回 NONE)。
|
|
73
|
+
* @author ddj 2026年09月22号
|
|
74
|
+
* @param key 模式符号键:none/freetext/ink/highlight
|
|
75
|
+
*/
|
|
76
|
+
const applyMode = (key) => {
|
|
77
|
+
if (!pdfViewer || !pdfDocument) { status('PDF 未就绪'); return }
|
|
78
|
+
const mode = modeValueOf(key)
|
|
79
|
+
if (mode === null) return
|
|
80
|
+
try {
|
|
81
|
+
pdfViewer.annotationEditorMode = { mode }
|
|
82
|
+
status(key === 'none' ? '已退出编辑' : '编辑模式:' + (key === 'freetext' ? '文本框' : key === 'ink' ? '画笔' : '高亮'))
|
|
83
|
+
} catch (error) {
|
|
84
|
+
status('切换编辑模式失败:' + String(error))
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 保存编辑:saveDocument 导出 → base64 → edrv.saveBinary 回写。
|
|
90
|
+
* @author ddj 2026年09月22号
|
|
91
|
+
* @returns Promise<boolean> 是否成功
|
|
92
|
+
*/
|
|
93
|
+
const savePdf = async () => {
|
|
94
|
+
if (saving) return false
|
|
95
|
+
if (!pdfDocument) { status('PDF 未就绪'); return false }
|
|
96
|
+
if (!dirty) { status('PDF 无未保存修改'); return true }
|
|
97
|
+
saving = true
|
|
98
|
+
status('正在保存 PDF…')
|
|
99
|
+
try {
|
|
100
|
+
const data = await pdfDocument.saveDocument()
|
|
101
|
+
const res = await rpc('edrv.saveBinary', { sessionId: deps.sessionId, path: deps.path, content: bytesToBase64(data), encoding: 'base64' })
|
|
102
|
+
if (res && res.ok) {
|
|
103
|
+
setDirty(false)
|
|
104
|
+
try { pdfDocument.annotationStorage.resetModified() } catch { /* 重置失败仅影响下次保存判定 */ }
|
|
105
|
+
status('已保存 ' + new Date().toTimeString().slice(0, 8))
|
|
106
|
+
return true
|
|
107
|
+
}
|
|
108
|
+
status('保存失败')
|
|
109
|
+
try { deps.setError(res?.error ? String(res.error) : '保存失败') } catch { /* 错误回调异常忽略 */ }
|
|
110
|
+
return false
|
|
111
|
+
} catch (error) {
|
|
112
|
+
status('保存失败')
|
|
113
|
+
try { deps.setError('PDF 保存异常:' + String(error)) } catch { /* 错误回调异常忽略 */ }
|
|
114
|
+
return false
|
|
115
|
+
} finally {
|
|
116
|
+
saving = false
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** 工具条 DOM(页码/缩放/编辑模式/保存/刷新)。 */
|
|
121
|
+
const buildToolbar = (root) => {
|
|
122
|
+
const bar = document.createElement('div')
|
|
123
|
+
bar.className = 'edrv-pdf-bar'
|
|
124
|
+
const mkBtn = (text, title, onClick, cls) => {
|
|
125
|
+
const b = document.createElement('button')
|
|
126
|
+
b.className = cls || 'edrv-pill edrv-pill-ghost'
|
|
127
|
+
b.textContent = text
|
|
128
|
+
b.title = title
|
|
129
|
+
b.addEventListener('click', onClick)
|
|
130
|
+
bar.appendChild(b)
|
|
131
|
+
return b
|
|
132
|
+
}
|
|
133
|
+
mkBtn('◀', '上一页', () => { if (pdfViewer) pdfViewer.currentPageNumber = Math.max(1, pdfViewer.currentPageNumber - 1) })
|
|
134
|
+
pageLabel = document.createElement('span')
|
|
135
|
+
pageLabel.className = 'edrv-pdf-page'
|
|
136
|
+
pageLabel.textContent = '…'
|
|
137
|
+
bar.appendChild(pageLabel)
|
|
138
|
+
mkBtn('▶', '下一页', () => { if (pdfViewer) pdfViewer.currentPageNumber = Math.min(pdfViewer.pagesCount, pdfViewer.currentPageNumber + 1) })
|
|
139
|
+
mkBtn('−', '缩小', () => { try { pdfViewer.decreaseScale() } catch { /* 缩放失败忽略 */ } }, 'edrv-pill edrv-pill-ghost edrv-pdf-zoom')
|
|
140
|
+
mkBtn('+', '放大', () => { try { pdfViewer.increaseScale() } catch { /* 缩放失败忽略 */ } }, 'edrv-pill edrv-pill-ghost edrv-pdf-zoom')
|
|
141
|
+
bar.appendChild(document.createElement('span')).className = 'edrv-pdf-sep'
|
|
142
|
+
const modes = {}
|
|
143
|
+
modes.freetext = mkBtn('✎ 文本框', '插入文本框注释', () => applyMode('freetext'))
|
|
144
|
+
modes.ink = mkBtn('🖌 画笔', '手绘画笔注释', () => applyMode('ink'))
|
|
145
|
+
modes.highlight = mkBtn('🖍 高亮', '高亮选中文本', () => applyMode('highlight'))
|
|
146
|
+
modes.none = mkBtn('☐ 选择', '退出编辑模式', () => applyMode('none'))
|
|
147
|
+
modeButtons = { modes, save: null }
|
|
148
|
+
bar.appendChild(document.createElement('span')).className = 'edrv-pdf-sep'
|
|
149
|
+
mkBtn('⟳', '重新加载文件', () => { try { deps.onReload() } catch { /* 刷新回调异常忽略 */ } })
|
|
150
|
+
modeButtons.save = mkBtn('💾 保存', '保存修改到文件 (Ctrl+S)', () => { void savePdf() })
|
|
151
|
+
root.appendChild(bar)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 挂载面板并加载 PDF 字节。
|
|
156
|
+
* @author ddj 2026年09月22号
|
|
157
|
+
* @param host React 渲染的外壳 div(挂载后保持引用稳定)
|
|
158
|
+
* @param bytes PDF 原始字节
|
|
159
|
+
* @returns Promise<void> 渲染就绪或失败(失败置 deps.setError)
|
|
160
|
+
*/
|
|
161
|
+
const mount = async (host, bytes) => {
|
|
162
|
+
try {
|
|
163
|
+
const pdfjs = await loadPdfjs()
|
|
164
|
+
if (destroyed) return
|
|
165
|
+
editorTypes = pdfjs.lib.AnnotationEditorType
|
|
166
|
+
busAbort = new AbortController()
|
|
167
|
+
const onBus = (name, fn) => eventBus.on(name, fn, { signal: busAbort.signal })
|
|
168
|
+
const root = document.createElement('div')
|
|
169
|
+
root.className = 'edrv-pdf'
|
|
170
|
+
buildToolbar(root)
|
|
171
|
+
stage = document.createElement('div')
|
|
172
|
+
stage.className = 'edrv-pdf-stage'
|
|
173
|
+
const viewerDiv = document.createElement('div')
|
|
174
|
+
viewerDiv.className = 'pdfViewer edrv-pdf-viewer'
|
|
175
|
+
stage.appendChild(viewerDiv)
|
|
176
|
+
root.appendChild(stage)
|
|
177
|
+
host.textContent = ''
|
|
178
|
+
host.appendChild(root)
|
|
179
|
+
|
|
180
|
+
eventBus = new pdfjs.viewer.EventBus()
|
|
181
|
+
const linkService = new pdfjs.viewer.PDFLinkService({ eventBus })
|
|
182
|
+
pdfViewer = new pdfjs.viewer.PDFViewer({
|
|
183
|
+
container: stage,
|
|
184
|
+
viewer: viewerDiv,
|
|
185
|
+
eventBus,
|
|
186
|
+
linkService,
|
|
187
|
+
l10n: L10N_STUB,
|
|
188
|
+
textLayerMode: 1,
|
|
189
|
+
annotationEditorMode: editorTypes.NONE,
|
|
190
|
+
})
|
|
191
|
+
linkService.setViewer(pdfViewer)
|
|
192
|
+
onBus('pagechanging', updatePageLabel)
|
|
193
|
+
onBus('pagesinit', () => {
|
|
194
|
+
try { pdfViewer.currentScaleValue = 'page-width' } catch { /* 初始缩放失败用默认值 */ }
|
|
195
|
+
updatePageLabel()
|
|
196
|
+
})
|
|
197
|
+
onBus('annotationeditormodechanged', (evt) => syncModeButtons(evt?.mode))
|
|
198
|
+
|
|
199
|
+
loadingTask = pdfjs.lib.getDocument({ data: bytes, cMapUrl: PDFJS_CMAP_URL, cMapPacked: true, standardFontDataUrl: PDFJS_FONT_URL })
|
|
200
|
+
pdfDocument = await loadingTask.promise
|
|
201
|
+
if (destroyed) { void loadingTask.destroy(); return }
|
|
202
|
+
const storage = pdfDocument.annotationStorage
|
|
203
|
+
storage.onSetModified = () => setDirty(true)
|
|
204
|
+
storage.onResetModified = () => setDirty(false)
|
|
205
|
+
pdfViewer.setDocument(pdfDocument)
|
|
206
|
+
linkService.setDocument(pdfDocument)
|
|
207
|
+
status('PDF 已就绪(' + pdfDocument.numPages + ' 页)')
|
|
208
|
+
} catch (error) {
|
|
209
|
+
if (destroyed) return
|
|
210
|
+
const message = String((error && (error.message || error.name)) || error)
|
|
211
|
+
const friendly = message.includes('password') ? '不支持加密 PDF' : 'PDF 加载失败:' + message
|
|
212
|
+
status('PDF 加载失败')
|
|
213
|
+
try { deps.setError(friendly) } catch { /* 错误回调异常忽略 */ }
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** 是否有未保存修改(EditorView Ctrl+S 分派用)。 */
|
|
218
|
+
const isDirty = () => dirty
|
|
219
|
+
|
|
220
|
+
/** 销毁:解绑事件、销毁文档与 DOM(重复调用安全)。 */
|
|
221
|
+
const destroy = () => {
|
|
222
|
+
destroyed = true
|
|
223
|
+
try { busAbort?.abort() } catch { /* 已中止 */ }
|
|
224
|
+
try { pdfViewer?.cleanup() } catch { /* 视图已销毁 */ }
|
|
225
|
+
try { void loadingTask?.destroy() } catch { /* 任务已销毁 */ }
|
|
226
|
+
try { void pdfDocument?.destroy() } catch { /* 文档已销毁 */ }
|
|
227
|
+
if (stage?.parentNode) stage.parentNode.removeChild(stage)
|
|
228
|
+
stage = null
|
|
229
|
+
eventBus = null
|
|
230
|
+
busAbort = null
|
|
231
|
+
editorTypes = null
|
|
232
|
+
pdfViewer = null
|
|
233
|
+
pdfDocument = null
|
|
234
|
+
loadingTask = null
|
|
235
|
+
modeButtons = null
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return { mount, destroy, savePdf, isDirty }
|
|
239
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode client — PDF 文件判定与 base64 编解码(编辑区 PDF 面板用)。
|
|
3
|
+
* 纯函数、无 DOM 依赖,便于单测;PDF_MIME 收敛在 shared/rpc.ts(host base64 响应同源)。
|
|
4
|
+
* @author ddj 2026年09月22号
|
|
5
|
+
*/
|
|
6
|
+
import { PDF_MIME } from '../shared/rpc.js'
|
|
7
|
+
|
|
8
|
+
export { PDF_MIME }
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 判断路径是否为 PDF 文件(取 basename 扩展名,大小写不敏感)。
|
|
12
|
+
* @author ddj 2026年09月22号
|
|
13
|
+
* @param path 文件路径(`/` 或 `\` 分隔均可)
|
|
14
|
+
* @returns 是否 PDF 文件
|
|
15
|
+
*/
|
|
16
|
+
export function isPdfPath(path: string): boolean {
|
|
17
|
+
const base = String(path || '').split(/[\\/]/).pop() || ''
|
|
18
|
+
const dot = base.lastIndexOf('.')
|
|
19
|
+
return dot > 0 && base.slice(dot + 1).toLowerCase() === 'pdf'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* base64 文本 → 二进制字节(喂给 pdf.js getDocument 的 data 入参)。
|
|
24
|
+
* @author ddj 2026年09月22号
|
|
25
|
+
* @param base64 base64 文本(不含 data: 前缀,host edrv.read base64 响应携带)
|
|
26
|
+
* @returns 原始字节
|
|
27
|
+
*/
|
|
28
|
+
export function base64ToBytes(base64: string): Uint8Array {
|
|
29
|
+
const bin = atob(String(base64 || ''))
|
|
30
|
+
const bytes = new Uint8Array(bin.length)
|
|
31
|
+
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i)
|
|
32
|
+
return bytes
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 二进制字节 → base64 文本(编辑保存回传 host 前的编码步骤)。
|
|
37
|
+
* @author ddj 2026年09月22号
|
|
38
|
+
* @param bytes 原始字节
|
|
39
|
+
* @returns base64 文本
|
|
40
|
+
*/
|
|
41
|
+
export function bytesToBase64(bytes: Uint8Array): string {
|
|
42
|
+
let bin = ''
|
|
43
|
+
const chunk = 0x8000
|
|
44
|
+
for (let i = 0; i < bytes.length; i += chunk) {
|
|
45
|
+
bin += String.fromCharCode(...bytes.subarray(i, i + chunk))
|
|
46
|
+
}
|
|
47
|
+
return btoa(bin)
|
|
48
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode client — DSH 对话文件链接的 0.1.3+ 路由(remote.session 面)。
|
|
3
|
+
* 0.1.3 起 dsh-client-ui-chat 的 openFile 直接调 ctx.remote.session.openWorkspacePath
|
|
4
|
+
* (浏览器→host RPC session/openWorkspacePath),不再经过 workspaces.openPath,
|
|
5
|
+
* 旧路由补丁因此落空;该方法是 getter-only accessor,补丁经 compat.patchAccessor
|
|
6
|
+
* 换描述符安装。行为与 openPathRouter 对齐:插件打开器优先,"."(打开工作区文件夹)、
|
|
7
|
+
* 无可用打开器或打开失败时透传原实现(系统默认应用打开)。
|
|
8
|
+
* 纯逻辑,node 环境可单测(不依赖 DOM / events 模块)。
|
|
9
|
+
* 作者 ddj 2026年09月08号
|
|
10
|
+
*/
|
|
11
|
+
import { patchAccessor } from './compat.js'
|
|
12
|
+
import { isAvailable, selectOpener, type FileOpenContext, type FileOpenerRegistry } from './fileOpeners.js'
|
|
13
|
+
|
|
14
|
+
/** session.openWorkspacePath 的结果形状(host 返回 { opened: true };客户端不做输出校验)。 */
|
|
15
|
+
export interface OpenPathResult {
|
|
16
|
+
ok: boolean
|
|
17
|
+
value?: { opened: boolean }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** openWorkspacePath 的请求形状;path 为 "." 时表示打开工作区文件夹。 */
|
|
21
|
+
export interface OpenPathRequest {
|
|
22
|
+
path?: unknown
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** 补丁前 openWorkspacePath 的实时实现形状。 */
|
|
26
|
+
type RemoteOpen = (request: OpenPathRequest) => Promise<OpenPathResult>
|
|
27
|
+
|
|
28
|
+
/** 插件打开器接管成功时返回给调用方(chat 只看 ok,产物区读 value)。 */
|
|
29
|
+
const OPENED_RESULT: OpenPathResult = { ok: true, value: { opened: true } }
|
|
30
|
+
|
|
31
|
+
export interface RemoteOpenRouterOptions {
|
|
32
|
+
registry: FileOpenerRegistry
|
|
33
|
+
selected: () => string
|
|
34
|
+
context: () => FileOpenContext
|
|
35
|
+
logger?: (message: string) => void
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 结构化探测 remote.session 命名空间服务及其 openWorkspacePath 方法。
|
|
40
|
+
* 旧版 DSH 无该 namespace、或 api-remotes 尚未挂载时返回 undefined。
|
|
41
|
+
* @author ddj 2026年09月08号
|
|
42
|
+
* @param ctx 客户端服务上下文
|
|
43
|
+
* @returns 命名空间服务实例;不可用返回 undefined
|
|
44
|
+
*/
|
|
45
|
+
export function probeRemoteOpen(ctx: { get: (name: string) => unknown }): object | undefined {
|
|
46
|
+
try {
|
|
47
|
+
const service = ctx.get('remote.session') as { openWorkspacePath?: unknown } | undefined
|
|
48
|
+
if (!service || typeof service.openWorkspacePath !== 'function') return undefined
|
|
49
|
+
return service
|
|
50
|
+
} catch {
|
|
51
|
+
return undefined
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 安装可卸载的 remote.session.openWorkspacePath 路由(0.1.3+ 会话文件链接主路径)。
|
|
57
|
+
* @author ddj 2026年09月08号
|
|
58
|
+
* @param service probeRemoteOpen 命中的命名空间服务
|
|
59
|
+
* @param options 路由依赖
|
|
60
|
+
* @returns 恢复函数;目标属性缺失时返回 null(调用方停止安装并告警)
|
|
61
|
+
*/
|
|
62
|
+
export function patchRemoteOpen(service: object, options: RemoteOpenRouterOptions): (() => void) | null {
|
|
63
|
+
return patchAccessor(service, 'openWorkspacePath', async (original, ...args) => {
|
|
64
|
+
const fallback = original as unknown as RemoteOpen
|
|
65
|
+
const request = (args[0] ?? {}) as OpenPathRequest
|
|
66
|
+
const path = typeof request.path === 'string' ? request.path : ''
|
|
67
|
+
if (!path || path === '.') return fallback(request)
|
|
68
|
+
const opener = selectOpener(options.registry, options.selected())
|
|
69
|
+
if (!opener || !isAvailable(opener)) return fallback(request)
|
|
70
|
+
try {
|
|
71
|
+
await opener.open(path, options.context())
|
|
72
|
+
return OPENED_RESULT
|
|
73
|
+
} catch (error) {
|
|
74
|
+
options.logger?.('文件打开器 ' + opener.id + ' 失败,回退系统打开: ' + String(error))
|
|
75
|
+
return fallback(request)
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
}
|
|
@@ -58,6 +58,23 @@
|
|
|
58
58
|
[data-edrv-view] .edrv-minus-item::before { content: ''; position: absolute; left: 4px; top: 50%; width: 8px; height: 2px; transform: translateY(-50%); border-radius: 2px; background: #d9534f; }
|
|
59
59
|
[data-edrv-view] .edrv-monaco-host { flex: 1 1 auto; min-width: 0; min-height: 0; position: relative; overflow: hidden; }
|
|
60
60
|
[data-edrv-view] .edrv-monaco-host > div { position: absolute; inset: 0; min-width: 0; min-height: 0; }
|
|
61
|
+
[data-edrv-view] .edrv-imgview { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
|
|
62
|
+
[data-edrv-view] .edrv-imgview-bar { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px; flex-shrink: 0; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, transparent); }
|
|
63
|
+
[data-edrv-view] .edrv-imgview-meta { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; }
|
|
64
|
+
[data-edrv-view] .edrv-imgview-stage { flex: 1 1 auto; min-height: 0; display: flex; align-items: center; justify-content: center; overflow: auto; padding: 14px; background-color: var(--dsw-alias-bg-layer-1, #f0f4f4); background-image: conic-gradient(rgba(128,128,128,.14) 25%, transparent 0 50%, rgba(128,128,128,.14) 0 75%, transparent 0); background-size: 16px 16px; }
|
|
65
|
+
[data-edrv-view] .edrv-imgview-img { max-width: 100%; max-height: 100%; object-fit: contain; border-radius: 4px; box-shadow: 0 2px 12px rgba(31,41,51,.18); }
|
|
66
|
+
[data-edrv-view] .edrv-imgview-broken { display: flex; flex-direction: column; align-items: center; gap: 10px; font-size: 13px; color: var(--dsw-alias-label-tertiary, #777); }
|
|
67
|
+
[data-edrv-view] .edrv-pdf-host { flex: 1 1 auto; min-width: 0; min-height: 0; position: relative; overflow: hidden; }
|
|
68
|
+
[data-edrv-view] .edrv-pdf-host > .edrv-pdf { position: absolute; inset: 0; }
|
|
69
|
+
[data-edrv-view] .edrv-pdf { min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
|
|
70
|
+
[data-edrv-view] .edrv-pdf-bar { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px; flex-shrink: 0; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, transparent); }
|
|
71
|
+
[data-edrv-view] .edrv-pdf-page { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; min-width: 52px; text-align: center; }
|
|
72
|
+
[data-edrv-view] .edrv-pdf-zoom { min-width: 26px; text-align: center; padding: 3px 7px; }
|
|
73
|
+
[data-edrv-view] .edrv-pdf-sep { width: 1px; height: 16px; background: var(--dsw-alias-border-l1, #e0e6e8); margin: 0 2px; }
|
|
74
|
+
[data-edrv-view] .edrv-pdf-mode-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-brand-primary, #0f9d58); border-color: var(--dsw-alias-brand-primary, #0f9d58); }
|
|
75
|
+
[data-edrv-view] .edrv-pdf-dirty { color: var(--dsw-alias-state-warn-primary, #d97706); border-color: var(--dsw-alias-state-warn-primary, #d97706); }
|
|
76
|
+
[data-edrv-view] .edrv-pdf-stage { position: absolute; top: 34px; left: 0; right: 0; bottom: 0; overflow: auto; }
|
|
77
|
+
[data-edrv-view] .edrv-pdf-stage .edrv-pdf-viewer { margin: 0 auto; }
|
|
61
78
|
[data-edrv-view] .edrv-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; height: 100%; color: var(--dsw-alias-label-tertiary, #777); font-size: 13px; }
|
|
62
79
|
[data-edrv-view] .edrv-diffbar { position: absolute; left: 0; right: 0; bottom: 10px; margin: 0 auto; z-index: 25; width: fit-content; max-width: calc(100% - 20px); border-radius: 12px; background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, #f0f4f4)); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); box-shadow: none; overflow: visible; display: flex; flex-direction: column; }
|
|
63
80
|
[data-edrv-view] .edrv-editor-area > .edrv-diffbar { max-height: calc(100% - 20px); }
|