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
|
@@ -9,6 +9,9 @@ import React from 'react'
|
|
|
9
9
|
import { dbg, rpc } from '../rpc.js'
|
|
10
10
|
import { emitRefresh } from '../events.js'
|
|
11
11
|
import { langOf, loadMonaco } from '../monaco/loader.js'
|
|
12
|
+
import { dataUrlOf, isImagePath, isSvgPath } from '../imagePreview.js'
|
|
13
|
+
import { base64ToBytes, isPdfPath } from '../pdfPreview.js'
|
|
14
|
+
import { createPdfPanel } from '../pdf/pdfPanel.js'
|
|
12
15
|
import { applyTheme, registerThemes, themeNameOf } from '../monaco/theme.js'
|
|
13
16
|
import { createDiffRenderer } from '../monaco/diffRender.js'
|
|
14
17
|
import { ST, callIdAttr, noopHunk, summarize } from '../state/records.js'
|
|
@@ -53,6 +56,14 @@ export function EditorView(props) {
|
|
|
53
56
|
const [dirtyMap, setDirtyMap] = React.useState({})
|
|
54
57
|
const [content, setContent] = React.useState(null)
|
|
55
58
|
const [contentPath, setContentPath] = React.useState(null)
|
|
59
|
+
const [imageSrc, setImageSrc] = React.useState(null) // 图片预览 data URL(图片 tab 专用,文本 tab 恒为 null)
|
|
60
|
+
const [imgSize, setImgSize] = React.useState(null) // 图片自然尺寸 { w, h }(路径栏 meta)
|
|
61
|
+
const [imgBroken, setImgBroken] = React.useState(false) // 图片解码失败(onError),显示占位与重试
|
|
62
|
+
const svgTextRef = React.useRef(new Set()) // 强制以文本打开的 SVG 路径集合(toggleSvgText 维护)
|
|
63
|
+
const [pdfBytes, setPdfBytes] = React.useState(null) // 当前 PDF tab 的原始字节(null=加载中/非 PDF)
|
|
64
|
+
const pdfB64CacheRef = React.useRef(new Map()) // path → base64(tab 切回免重读;FIFO 上限防内存膨胀)
|
|
65
|
+
const pdfCtlRef = React.useRef(new Map()) // path → PDF 面板控制器(mount 产出,关闭/卸载销毁)
|
|
66
|
+
const pdfHostRef = React.useRef(null) // PDF 面板外壳 div(命令式控制器接管)
|
|
56
67
|
const [status, setStatus] = React.useState('')
|
|
57
68
|
const [lspServers, setLspServers] = React.useState([])
|
|
58
69
|
const [error, setError] = React.useState(null)
|
|
@@ -117,6 +128,10 @@ export function EditorView(props) {
|
|
|
117
128
|
}, [records, active])
|
|
118
129
|
|
|
119
130
|
const contentReady = content !== null && contentPath === active
|
|
131
|
+
// 当前 tab 是否处于图片预览模式(与 loadContent 的分派条件同源;SVG 文本模式时为 false)
|
|
132
|
+
const isImageActive = !!active && isImagePath(active) && !svgTextRef.current.has(active)
|
|
133
|
+
// 当前 tab 是否为 PDF 面板(与 loadContent 分派条件同源)
|
|
134
|
+
const isPdfActive = !!active && isPdfPath(active)
|
|
120
135
|
const regions = React.useMemo(() => diffRegions(currentRecords, contentReady ? content : null).filter((r) => !r.superseded), [currentRecords, content, contentPath, active])
|
|
121
136
|
// useMemo 稳定引用:否则 hover 等重渲染会让 view zone effect 反复重建(- 号闪烁)
|
|
122
137
|
const pendingRegions = React.useMemo(() => regions.filter((r) => r.status === ST.PENDING && !r.stale), [regions])
|
|
@@ -267,6 +282,10 @@ export function EditorView(props) {
|
|
|
267
282
|
const closeTab = (path) => {
|
|
268
283
|
flushSave()
|
|
269
284
|
if (path === active) { saveViewState(path); recordNav() }
|
|
285
|
+
// PDF tab 关闭:销毁面板控制器并清缓存(未保存注释随之丢弃,脏点已提示)
|
|
286
|
+
const pdfCtl = pdfCtlRef.current.get(path)
|
|
287
|
+
if (pdfCtl) { pdfCtl.destroy(); pdfCtlRef.current.delete(path) }
|
|
288
|
+
pdfB64CacheRef.current.delete(path)
|
|
270
289
|
setTabs((prev) => {
|
|
271
290
|
const idx = prev.findIndex((t) => t.path === path)
|
|
272
291
|
if (idx < 0) return prev
|
|
@@ -318,6 +337,16 @@ export function EditorView(props) {
|
|
|
318
337
|
*/
|
|
319
338
|
const loadContent = (path, sid, force) => {
|
|
320
339
|
const seq = ++loadSeqRef.current
|
|
340
|
+
// 图片文件走专用通道:base64 → data URL 预览,不建 Monaco model、不进文本/差异流程
|
|
341
|
+
if (isImagePath(path) && !svgTextRef.current.has(path)) {
|
|
342
|
+
loadImage(path, sid, seq)
|
|
343
|
+
return
|
|
344
|
+
}
|
|
345
|
+
// PDF 文件走专用通道:base64 → PDF 面板(浏览 + 注释编辑),不建 Monaco model
|
|
346
|
+
if (isPdfPath(path)) {
|
|
347
|
+
loadPdf(path, sid, seq, force === true)
|
|
348
|
+
return
|
|
349
|
+
}
|
|
321
350
|
const cachedModel = force ? null : modelsRef.current.get(path)
|
|
322
351
|
if (cachedModel) {
|
|
323
352
|
// model 命中:直接显示其内容(切 tab 零等待),后台静默校验
|
|
@@ -361,6 +390,94 @@ export function EditorView(props) {
|
|
|
361
390
|
})
|
|
362
391
|
}
|
|
363
392
|
|
|
393
|
+
/**
|
|
394
|
+
* 加载图片文件为 data URL(编辑区只读预览);失败走 loadError 面板(重试经 loadContent 分派回此)。
|
|
395
|
+
* @author ddj 2026年09月08号
|
|
396
|
+
* @param path 图片文件路径
|
|
397
|
+
* @param sid 会话 id
|
|
398
|
+
* @param seq 加载序号(过期响应丢弃)
|
|
399
|
+
*/
|
|
400
|
+
const loadImage = (path, sid, seq) => {
|
|
401
|
+
setImageSrc(null)
|
|
402
|
+
setImgSize(null)
|
|
403
|
+
setImgBroken(false)
|
|
404
|
+
setLoadStage({ progress: monaco ? 72 : 12, message: '读取图片…' })
|
|
405
|
+
rpc('edrv.read', { sessionId: sid, path, encoding: 'base64' }).then((res) => {
|
|
406
|
+
if (seq !== loadSeqRef.current || path !== active) return
|
|
407
|
+
if (res && res.ok && res.encoding === 'base64') {
|
|
408
|
+
if (!res.mime) {
|
|
409
|
+
setLoadError('host 版本过旧:图片响应缺少 mime')
|
|
410
|
+
setError('host 版本过旧:图片响应缺少 mime')
|
|
411
|
+
setStatus('读取失败')
|
|
412
|
+
return
|
|
413
|
+
}
|
|
414
|
+
setImageSrc(dataUrlOf(res.content, res.mime))
|
|
415
|
+
setLoadStage({ progress: 100, message: '图片已就绪' })
|
|
416
|
+
setStatus('已加载')
|
|
417
|
+
return
|
|
418
|
+
}
|
|
419
|
+
const base = res?.error ? String(res.error) : '读取失败'
|
|
420
|
+
// 带出 host 解析后的真实路径:跳转失败时一眼看出是路径解析错还是目标不存在
|
|
421
|
+
const message = res?.resolvedPath ? base + ':' + String(res.resolvedPath) : base
|
|
422
|
+
setLoadError(message)
|
|
423
|
+
setError(message)
|
|
424
|
+
setStatus('读取失败')
|
|
425
|
+
}).catch((e) => {
|
|
426
|
+
if (seq !== loadSeqRef.current) return
|
|
427
|
+
const message = 'read异常:' + String(e)
|
|
428
|
+
setLoadError(message)
|
|
429
|
+
setError(message)
|
|
430
|
+
setStatus('读取失败')
|
|
431
|
+
})
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* 加载 PDF 文件为原始字节(编辑区 PDF 面板);命中 base64 缓存秒切,force 绕过缓存重读。
|
|
436
|
+
* 面板控制器由 pdf host mount effect 产出(per-path,关闭/卸载销毁)。
|
|
437
|
+
* @author ddj 2026年09月22号
|
|
438
|
+
* @param path PDF 文件路径
|
|
439
|
+
* @param sid 会话 id
|
|
440
|
+
* @param seq 加载序号(过期响应丢弃)
|
|
441
|
+
* @param force true=绕过缓存强制 RPC 重读(刷新按钮)
|
|
442
|
+
*/
|
|
443
|
+
const loadPdf = (path, sid, seq, force) => {
|
|
444
|
+
setPdfBytes(null)
|
|
445
|
+
setLoadStage({ progress: monaco ? 72 : 12, message: '读取 PDF…' })
|
|
446
|
+
const cache = pdfB64CacheRef.current
|
|
447
|
+
const hit = !force ? cache.get(path) : undefined
|
|
448
|
+
if (typeof hit === 'string' && hit) {
|
|
449
|
+
// 刷新插入序(Map 按插入序 FIFO 驱逐)后秒切
|
|
450
|
+
cache.delete(path)
|
|
451
|
+
cache.set(path, hit)
|
|
452
|
+
setPdfBytes(base64ToBytes(hit))
|
|
453
|
+
setLoadStage({ progress: 100, message: 'PDF 已就绪' })
|
|
454
|
+
setStatus('已加载')
|
|
455
|
+
return
|
|
456
|
+
}
|
|
457
|
+
rpc('edrv.read', { sessionId: sid, path, encoding: 'base64' }).then((res) => {
|
|
458
|
+
if (seq !== loadSeqRef.current || path !== active) return
|
|
459
|
+
if (res && res.ok && res.encoding === 'base64') {
|
|
460
|
+
cache.set(path, res.content)
|
|
461
|
+
while (cache.size > 6) cache.delete(cache.keys().next().value)
|
|
462
|
+
setPdfBytes(base64ToBytes(res.content))
|
|
463
|
+
setLoadStage({ progress: 100, message: 'PDF 已就绪' })
|
|
464
|
+
setStatus('已加载')
|
|
465
|
+
return
|
|
466
|
+
}
|
|
467
|
+
const base = res?.error ? String(res.error) : '读取失败'
|
|
468
|
+
const message = res?.resolvedPath ? base + ':' + String(res.resolvedPath) : base
|
|
469
|
+
setLoadError(message)
|
|
470
|
+
setError(message)
|
|
471
|
+
setStatus('读取失败')
|
|
472
|
+
}).catch((e) => {
|
|
473
|
+
if (seq !== loadSeqRef.current) return
|
|
474
|
+
const message = 'read异常:' + String(e)
|
|
475
|
+
setLoadError(message)
|
|
476
|
+
setError(message)
|
|
477
|
+
setStatus('读取失败')
|
|
478
|
+
})
|
|
479
|
+
}
|
|
480
|
+
|
|
364
481
|
// 挂载轮询 + 事件订阅
|
|
365
482
|
React.useEffect(() => {
|
|
366
483
|
if (!sessionId) return
|
|
@@ -633,6 +750,10 @@ export function EditorView(props) {
|
|
|
633
750
|
if (!active) return
|
|
634
751
|
setContent(null)
|
|
635
752
|
setContentPath(null)
|
|
753
|
+
setImageSrc(null)
|
|
754
|
+
setImgSize(null)
|
|
755
|
+
setImgBroken(false)
|
|
756
|
+
setPdfBytes(null)
|
|
636
757
|
setLoadError(null)
|
|
637
758
|
setLoadStage({ progress: 10, message: '准备读取文件…' })
|
|
638
759
|
setStatus('加载中…')
|
|
@@ -701,8 +822,18 @@ export function EditorView(props) {
|
|
|
701
822
|
}
|
|
702
823
|
|
|
703
824
|
const doSave = (silent) => {
|
|
825
|
+
if (!active) return
|
|
826
|
+
// PDF tab:保存委托给面板控制器(saveDocument → base64 → edrv.saveBinary)
|
|
827
|
+
if (isPdfPath(active)) {
|
|
828
|
+
const ctl = pdfCtlRef.current.get(active)
|
|
829
|
+
if (!ctl) { if (!silent) setStatus('PDF 未就绪'); return }
|
|
830
|
+
if (!ctl.isDirty()) { if (!silent) setStatus('PDF 无未保存修改'); return }
|
|
831
|
+
void ctl.savePdf()
|
|
832
|
+
return
|
|
833
|
+
}
|
|
704
834
|
const ed = editorRef.current
|
|
705
|
-
if (!
|
|
835
|
+
if (!ed) return
|
|
836
|
+
if (isImageActive) { setStatus('图片只读预览'); return }
|
|
706
837
|
const text = ed.getValue()
|
|
707
838
|
if (!silent) setStatus('保存中…')
|
|
708
839
|
rpc('edrv.save', { sessionId, path: active, content: text }).then((res) => {
|
|
@@ -737,6 +868,30 @@ export function EditorView(props) {
|
|
|
737
868
|
setLoadStage((prev) => ({ progress: Math.max(96, prev.progress), message: '创建编辑器视图…' }))
|
|
738
869
|
}, [monaco, active, content])
|
|
739
870
|
|
|
871
|
+
// PDF 面板外壳 ref 回调(div 仅在 PDF 分支渲染,refs 先于 effect 就绪)
|
|
872
|
+
const ensurePdfHost = (node) => { pdfHostRef.current = node }
|
|
873
|
+
|
|
874
|
+
// PDF 面板装配:控制器挂进外壳 div,切换 tab/卸载即销毁(重开重挂,base64 缓存兜底秒切)
|
|
875
|
+
React.useEffect(() => {
|
|
876
|
+
const host = pdfHostRef.current
|
|
877
|
+
if (!isPdfActive || !pdfBytes || !host) return
|
|
878
|
+
const path = active
|
|
879
|
+
const ctl = createPdfPanel({
|
|
880
|
+
sessionId,
|
|
881
|
+
path,
|
|
882
|
+
setStatus,
|
|
883
|
+
setError,
|
|
884
|
+
onDirtyChange: (d) => setDirtyMap((prev) => Object.assign({}, prev, { [path]: d })),
|
|
885
|
+
onReload: () => loadContent(path, sessionId, true),
|
|
886
|
+
})
|
|
887
|
+
pdfCtlRef.current.set(path, ctl)
|
|
888
|
+
void ctl.mount(host, pdfBytes)
|
|
889
|
+
return () => {
|
|
890
|
+
ctl.destroy()
|
|
891
|
+
if (pdfCtlRef.current.get(path) === ctl) pdfCtlRef.current.delete(path)
|
|
892
|
+
}
|
|
893
|
+
}, [isPdfActive, pdfBytes, active])
|
|
894
|
+
|
|
740
895
|
// 行内差异自绘(decorations / view zones / minus overlay)→ diffRenderer
|
|
741
896
|
React.useEffect(() => {
|
|
742
897
|
if (!monaco || !editorRef.current || !active || content === null) return
|
|
@@ -752,6 +907,8 @@ export function EditorView(props) {
|
|
|
752
907
|
if (editorRef.current) { editorRef.current.dispose(); editorRef.current = null }
|
|
753
908
|
for (const m of modelsRef.current.values()) m.dispose()
|
|
754
909
|
modelsRef.current.clear()
|
|
910
|
+
for (const ctl of pdfCtlRef.current.values()) ctl.destroy()
|
|
911
|
+
pdfCtlRef.current.clear()
|
|
755
912
|
const root = editorRef.current && editorRef.current.getDomNode ? editorRef.current.getDomNode() : null
|
|
756
913
|
if (root) {
|
|
757
914
|
const ov = root.querySelector('.edrv-minus-overlay')
|
|
@@ -975,6 +1132,53 @@ export function EditorView(props) {
|
|
|
975
1132
|
refreshRecords(skipStale === true)
|
|
976
1133
|
}
|
|
977
1134
|
|
|
1135
|
+
/**
|
|
1136
|
+
* SVG 在图片预览与文本编辑间切换(按路径记忆;重载分派随集合状态自动路由)。
|
|
1137
|
+
* @author ddj 2026年09月08号
|
|
1138
|
+
* @param path SVG 文件路径
|
|
1139
|
+
*/
|
|
1140
|
+
const toggleSvgText = (path) => {
|
|
1141
|
+
const next = new Set(svgTextRef.current)
|
|
1142
|
+
if (next.has(path)) next.delete(path)
|
|
1143
|
+
else next.add(path)
|
|
1144
|
+
svgTextRef.current = next
|
|
1145
|
+
setContent(null)
|
|
1146
|
+
setContentPath(null)
|
|
1147
|
+
setImageSrc(null)
|
|
1148
|
+
setImgSize(null)
|
|
1149
|
+
setImgBroken(false)
|
|
1150
|
+
setLoadError(null)
|
|
1151
|
+
setStatus(next.has(path) ? '已切换为文本模式' : '已切换为图片预览')
|
|
1152
|
+
loadContent(path, sessionId, true)
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
/**
|
|
1156
|
+
* 图片预览面板:工具条(尺寸 meta / SVG 文本切换 / 刷新)+ 棋盘底自适应图片。
|
|
1157
|
+
* @author ddj 2026年09月08号
|
|
1158
|
+
*/
|
|
1159
|
+
const imagePanel = () => React.createElement('div', { className: 'edrv-imgview' },
|
|
1160
|
+
React.createElement('div', { className: 'edrv-imgview-bar' },
|
|
1161
|
+
React.createElement('span', { className: 'edrv-imgview-meta' },
|
|
1162
|
+
imgBroken ? '图片无法显示' : (imgSize ? imgSize.w + '×' + imgSize.h : '…')),
|
|
1163
|
+
React.createElement('span', { style: { flex: 1 } }),
|
|
1164
|
+
(isSvgPath(active) ? React.createElement('button', {
|
|
1165
|
+
className: 'edrv-pill edrv-pill-ghost', title: '以文本方式查看/编辑(SVG 为文本格式)',
|
|
1166
|
+
onClick: () => toggleSvgText(active),
|
|
1167
|
+
}, '以文本打开') : null),
|
|
1168
|
+
React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', title: '重新加载图片', onClick: () => reloadFile() }, '⟳ 刷新')),
|
|
1169
|
+
React.createElement('div', { className: 'edrv-imgview-stage' },
|
|
1170
|
+
(imgBroken
|
|
1171
|
+
? React.createElement('div', { className: 'edrv-imgview-broken' },
|
|
1172
|
+
React.createElement('div', null, '图片解码失败,文件可能已损坏'),
|
|
1173
|
+
React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', onClick: () => reloadFile() }, '重试'))
|
|
1174
|
+
: React.createElement('img', {
|
|
1175
|
+
className: 'edrv-imgview-img',
|
|
1176
|
+
src: imageSrc,
|
|
1177
|
+
alt: active || '',
|
|
1178
|
+
onLoad: (e) => { setImgBroken(false); setImgSize({ w: e.target.naturalWidth, h: e.target.naturalHeight }) },
|
|
1179
|
+
onError: () => setImgBroken(true),
|
|
1180
|
+
}))))
|
|
1181
|
+
|
|
978
1182
|
/**
|
|
979
1183
|
* 关闭当前差异浮窗并清理 hover 锚点。
|
|
980
1184
|
* @author ddj 2026年08月21号
|
|
@@ -1183,6 +1387,12 @@ export function EditorView(props) {
|
|
|
1183
1387
|
const openPath = () => {
|
|
1184
1388
|
const p = (pathDraft || '').trim()
|
|
1185
1389
|
if (!p) return
|
|
1390
|
+
// 图片/PDF 文件跳过文本读探测(二进制会被 host 拒绝),直接进 tab 由专用预览接管
|
|
1391
|
+
if (isImagePath(p) || isPdfPath(p)) {
|
|
1392
|
+
openFile(p, false)
|
|
1393
|
+
setOpenInput(false); setPathDraft(''); setStatus('已打开'); setError(null)
|
|
1394
|
+
return
|
|
1395
|
+
}
|
|
1186
1396
|
setStatus('打开中…')
|
|
1187
1397
|
rpc('edrv.read', { sessionId, path: p }).then((res) => {
|
|
1188
1398
|
if (res && res.ok) {
|
|
@@ -1214,7 +1424,8 @@ export function EditorView(props) {
|
|
|
1214
1424
|
const pathBar = React.createElement('div', { className: 'edrv-pathbar', title: active || '' },
|
|
1215
1425
|
React.createElement('span', { className: 'edrv-pb-name' }, active ? String(active).split(/[\\/]/).pop() : '未打开文件'),
|
|
1216
1426
|
React.createElement('span', { className: 'edrv-pb-full' }, active || '使用右上搜索框 (' + (chordOf('edrv.quickOpen') ?? 'Ctrl+P') + ') 打开文件'),
|
|
1217
|
-
(active && langOf(active) ? React.createElement('span', { className: 'edrv-pb-meta' }, langOf(active)) : null),
|
|
1427
|
+
(active && !isImageActive && langOf(active) ? React.createElement('span', { className: 'edrv-pb-meta' }, langOf(active)) : null),
|
|
1428
|
+
(isImageActive && imgSize ? React.createElement('span', { className: 'edrv-pb-meta' }, imgSize.w + '×' + imgSize.h) : null),
|
|
1218
1429
|
(cursor ? React.createElement('span', { className: 'edrv-pb-meta' }, cursor) : null),
|
|
1219
1430
|
(status ? React.createElement('span', { className: 'edrv-pb-meta edrv-pb-status' }, status) : null))
|
|
1220
1431
|
|
|
@@ -1304,6 +1515,14 @@ export function EditorView(props) {
|
|
|
1304
1515
|
setLoadStage({ progress: 10, message: '重新读取文件…' })
|
|
1305
1516
|
loadContent(active, sessionId)
|
|
1306
1517
|
})
|
|
1518
|
+
} else if (isImageActive && imageSrc) {
|
|
1519
|
+
body = imagePanel()
|
|
1520
|
+
} else if (isImageActive) {
|
|
1521
|
+
body = loadingBody(loadStage.message || '读取图片…', loadStage.progress)
|
|
1522
|
+
} else if (isPdfActive && pdfBytes) {
|
|
1523
|
+
body = React.createElement('div', { className: 'edrv-pdf-host', ref: ensurePdfHost, key: active })
|
|
1524
|
+
} else if (isPdfActive) {
|
|
1525
|
+
body = loadingBody(loadStage.message || '读取 PDF…', loadStage.progress)
|
|
1307
1526
|
} else if (content === null) {
|
|
1308
1527
|
body = loadingBody(loadStage.message || '读取文件内容…', loadStage.progress)
|
|
1309
1528
|
} else {
|
package/src/compat.ts
CHANGED
|
@@ -129,7 +129,7 @@ export function detectGuards(ctx: Ctx): CompatAdapter[] {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
/** 已实测覆盖的最高 DSH 版本(适配矩阵上界,超过则提示,见 buildReport)。 */
|
|
132
|
-
const TESTED_DSH_MAX = '0.1.
|
|
132
|
+
const TESTED_DSH_MAX = '0.1.3-alpha.2'
|
|
133
133
|
|
|
134
134
|
/** 版本适配机制状态行:DSH 版本探测 + 设置 section 安装策略。 */
|
|
135
135
|
export function versionAdapters(dshVersion: string): CompatAdapter[] {
|
package/src/dshVersion.ts
CHANGED
|
@@ -86,6 +86,7 @@ export function inDshRange(version: DshVersion | null | undefined, range: DshRan
|
|
|
86
86
|
export function familyLabel(input: string): string {
|
|
87
87
|
const version = parseDshVersion(input)
|
|
88
88
|
if (!version) return '未知'
|
|
89
|
+
if (inDshRange(version, { from: '0.1.3-alpha.1' })) return '0.1.3-alpha 及更新(对话文件链接=remote.session.openWorkspacePath)'
|
|
89
90
|
if (inDshRange(version, { from: '0.1.2-alpha.1' })) return '0.1.2-alpha 及更新(设置 API=settings.installSection)'
|
|
90
91
|
return '0.1.0/0.1.1 rc 线(设置 API=installSettingsSection)'
|
|
91
92
|
}
|
package/src/routes.ts
CHANGED
|
@@ -19,6 +19,7 @@ const IMG_ROUTES = [
|
|
|
19
19
|
/** Monaco AMD 构建静态资源(随包发布,离线可用;前缀路由 /edrv/vendor/*)。 */
|
|
20
20
|
const VENDOR_MIME: Record<string, string> = {
|
|
21
21
|
'.js': 'text/javascript; charset=utf-8',
|
|
22
|
+
'.mjs': 'text/javascript; charset=utf-8',
|
|
22
23
|
'.css': 'text/css; charset=utf-8',
|
|
23
24
|
'.json': 'application/json',
|
|
24
25
|
'.map': 'application/json',
|
|
@@ -32,6 +33,8 @@ const VENDOR_MIME: Record<string, string> = {
|
|
|
32
33
|
'.ttf': 'font/ttf',
|
|
33
34
|
'.html': 'text/html; charset=utf-8',
|
|
34
35
|
'.md': 'text/markdown',
|
|
36
|
+
'.bcmap': 'application/octet-stream',
|
|
37
|
+
'.pfb': 'application/octet-stream',
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
/** 图标目录(paths.ts 统一解析:config.imageDir 覆盖优先,否则插件包 assets/)。 */
|
package/src/rpc.ts
CHANGED
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
* 作者 ddj 2026-08-20
|
|
6
6
|
*/
|
|
7
7
|
import type { DecideItem, DecideResult, RpcHandlerMap, RpcMethod, RpcRequestMap, RpcResult, RpcScope } from './shared/rpc.js'
|
|
8
|
+
import { binaryMimeOf } from './shared/rpc.js'
|
|
8
9
|
import type { DiffRecord, RecordView } from './shared/types.js'
|
|
9
10
|
import type { Ctx, Session } from './store.js'
|
|
10
11
|
import {
|
|
12
|
+
BINARY_READ_CAP,
|
|
11
13
|
READ_CAP,
|
|
12
14
|
appendArchiveEntries,
|
|
13
15
|
archiveRecords,
|
|
@@ -152,6 +154,36 @@ async function requireSession(ctx: Ctx, sessionId: string | undefined): Promise<
|
|
|
152
154
|
return { session, cwd }
|
|
153
155
|
}
|
|
154
156
|
|
|
157
|
+
/**
|
|
158
|
+
* 手动保存收尾(edrv.save / edrv.saveBinary 共用):目录树失效 + 该路径 pending
|
|
159
|
+
* 文本差异记录标记 superseded 并归档(旧文本 diff 不得再应用到新内容上)。
|
|
160
|
+
* @author ddj 2026年09月22号
|
|
161
|
+
* @param ctx DSH 上下文
|
|
162
|
+
* @param registry 差异记录桶注册表
|
|
163
|
+
* @param sc requireSession 成功结果(session + cwd)
|
|
164
|
+
* @param path 保存的文件路径
|
|
165
|
+
*/
|
|
166
|
+
async function afterManualSave(
|
|
167
|
+
ctx: Ctx,
|
|
168
|
+
registry: Registry,
|
|
169
|
+
sc: { session: Session; cwd: string },
|
|
170
|
+
path: string,
|
|
171
|
+
): Promise<void> {
|
|
172
|
+
// 手动保存后目录树可能变化(新建/删除文件):父目录+祖先进失效,后台自愈。
|
|
173
|
+
invalidateIndex(ctx, sc.cwd, path)
|
|
174
|
+
const bucket = await bucketOf(registry, ctx, sc.cwd)
|
|
175
|
+
let changed = false
|
|
176
|
+
for (const rec of bucket.values()) {
|
|
177
|
+
if (rec.path === path && rec.superseded !== true) { rec.superseded = true; rec.at = new Date().toISOString(); changed = true }
|
|
178
|
+
}
|
|
179
|
+
if (changed) {
|
|
180
|
+
const done: DiffRecord[] = []
|
|
181
|
+
for (const rec of bucket.values()) if (rec.path === path && rec.superseded) done.push(rec)
|
|
182
|
+
if (done.length) await archiveRecords(ctx, sc.session, sc.cwd, bucket, done, '被手动编辑覆盖')
|
|
183
|
+
else await saveBucket(ctx, sc.cwd, bucket, sc.session)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
155
187
|
/**
|
|
156
188
|
* 批量决策核心:一次会话/桶解析 + 逐项处理 + 统一落盘(accept/reject 单条与 decideBatch 共用)。
|
|
157
189
|
* rejected 项先做回滚,失败记入该项 error 且不改决策;本批次新增"已解决"记录一次性归档,
|
|
@@ -302,6 +334,13 @@ export function buildHandlers(
|
|
|
302
334
|
// 带上解析后的真实路径:跳转失败时可直接看出是路径解析错还是目标本身不存在
|
|
303
335
|
return { ok: false, error: '文件不存在', resolvedPath: fs.processPath(target) }
|
|
304
336
|
}
|
|
337
|
+
if ((info.size ?? 0) > BINARY_READ_CAP) return { ok: false, error: '文件过大(>32MB),不支持整文件预览' }
|
|
338
|
+
if (args.encoding === 'base64') {
|
|
339
|
+
// 图片/PDF 等二进制预览:readBytes 无解码、无二进制拒绝;超上限已由上方 stat 拦截
|
|
340
|
+
const bytes = await fs.readBytes(target, undefined, BINARY_READ_CAP)
|
|
341
|
+
const content = Buffer.from(bytes).toString('base64')
|
|
342
|
+
return { ok: true, content, size: bytes.byteLength, encoding: 'base64', mime: binaryMimeOf(args.path) }
|
|
343
|
+
}
|
|
305
344
|
if ((info.size ?? 0) > READ_CAP) return { ok: false, error: '文件过大(>8MB),不支持整文件预览' }
|
|
306
345
|
const content = await fs.readText(target)
|
|
307
346
|
return { ok: true, content, size: content.length }
|
|
@@ -342,24 +381,37 @@ export function buildHandlers(
|
|
|
342
381
|
try {
|
|
343
382
|
const target = await resolveTarget(ctx, sc.session, args.path)
|
|
344
383
|
await fs.writeText(target, args.content, void 0, void 0, policyOf(ctx, sc.session))
|
|
345
|
-
|
|
346
|
-
invalidateIndex(ctx, sc.cwd, args.path)
|
|
347
|
-
const bucket = await bucketOf(registry, ctx, sc.cwd)
|
|
348
|
-
let changed = false
|
|
349
|
-
for (const rec of bucket.values()) {
|
|
350
|
-
if (rec.path === args.path && rec.superseded !== true) { rec.superseded = true; rec.at = new Date().toISOString(); changed = true }
|
|
351
|
-
}
|
|
352
|
-
if (changed) {
|
|
353
|
-
const done: DiffRecord[] = []
|
|
354
|
-
for (const rec of bucket.values()) if (rec.path === args.path && rec.superseded) done.push(rec)
|
|
355
|
-
if (done.length) await archiveRecords(ctx, sc.session, sc.cwd, bucket, done, '被手动编辑覆盖')
|
|
356
|
-
else await saveBucket(ctx, sc.cwd, bucket, sc.session)
|
|
357
|
-
}
|
|
384
|
+
await afterManualSave(ctx, registry, sc, args.path)
|
|
358
385
|
return { ok: true }
|
|
359
386
|
} catch (error) {
|
|
360
387
|
return { ok: false, error: '保存失败:' + String(error) }
|
|
361
388
|
}
|
|
362
389
|
},
|
|
390
|
+
'edrv.saveBinary': async (args) => {
|
|
391
|
+
if (args.encoding !== 'base64') return { ok: false, error: '仅支持 base64 编码' }
|
|
392
|
+
const sc = await requireSession(ctx, args.sessionId)
|
|
393
|
+
if ('err' in sc) return { ok: false, error: sc.err }
|
|
394
|
+
const fs = ctx.get('fs')
|
|
395
|
+
if (!fs) return { ok: false, error: '缺少 fs' }
|
|
396
|
+
try {
|
|
397
|
+
const target = await resolveTarget(ctx, sc.session, args.path)
|
|
398
|
+
const info = await fs.stat(target)
|
|
399
|
+
if (!info || info.type !== 'file') return { ok: false, error: '文件不存在' }
|
|
400
|
+
// fs 服务仅提供文本写(writeText/editText),二进制回写经 node:fs 直写解析后的
|
|
401
|
+
// 真实路径;以工作区边界 + 大小上限 + 用户显式保存动作三重约束兜底(对齐
|
|
402
|
+
// revert.ts deleteCreated 的 contains 用法)。
|
|
403
|
+
const rootTarget = await fs.resolve(policyOf(ctx, sc.session)?.workspaceRoot ?? '.', {})
|
|
404
|
+
if (!fs.contains(rootTarget, target)) return { ok: false, error: '拒绝保存:目标不在会话工作区内' }
|
|
405
|
+
const bytes = Buffer.from(args.content, 'base64')
|
|
406
|
+
if (bytes.byteLength === 0) return { ok: false, error: '内容为空,拒绝写回' }
|
|
407
|
+
if (bytes.byteLength > BINARY_READ_CAP) return { ok: false, error: '内容过大(>32MB),拒绝写回' }
|
|
408
|
+
await writeFile(fs.processPath(target), bytes)
|
|
409
|
+
await afterManualSave(ctx, registry, sc, args.path)
|
|
410
|
+
return { ok: true }
|
|
411
|
+
} catch (error) {
|
|
412
|
+
return { ok: false, error: '二进制保存失败:' + String(error) }
|
|
413
|
+
}
|
|
414
|
+
},
|
|
363
415
|
'edrv.archiveList': async (args) => {
|
|
364
416
|
const sc = await requireSession(ctx, args.sessionId)
|
|
365
417
|
if ('err' in sc) return { ok: false, error: sc.err }
|
package/src/shared/rpc.ts
CHANGED
|
@@ -21,6 +21,52 @@ import type { LspEnvInstallState, LspExtInfo, LspExtUpdate, LspHover, LspLocatio
|
|
|
21
21
|
/** webServer 精确路由。 */
|
|
22
22
|
export const RPC_PATH = '/edrv/rpc'
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* 图片扩展名(小写、无点)→ MIME。edrv.read base64 响应与 client 图片预览共用,
|
|
26
|
+
* 收敛在此避免 host/client 两处漂移。
|
|
27
|
+
* @author ddj 2026年09月08号
|
|
28
|
+
*/
|
|
29
|
+
export const IMAGE_MIME: Record<string, string> = {
|
|
30
|
+
png: 'image/png',
|
|
31
|
+
jpg: 'image/jpeg',
|
|
32
|
+
jpeg: 'image/jpeg',
|
|
33
|
+
gif: 'image/gif',
|
|
34
|
+
webp: 'image/webp',
|
|
35
|
+
bmp: 'image/bmp',
|
|
36
|
+
ico: 'image/x-icon',
|
|
37
|
+
avif: 'image/avif',
|
|
38
|
+
svg: 'image/svg+xml',
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 路径 → 图片 MIME:按 basename 扩展名小写查 {@link IMAGE_MIME},未命中回退 application/octet-stream。
|
|
43
|
+
* @author ddj 2026年09月08号
|
|
44
|
+
* @param path 文件路径(`/` 或 `\` 分隔均可)
|
|
45
|
+
* @returns MIME 字符串
|
|
46
|
+
*/
|
|
47
|
+
export function imageMimeOf(path: string): string {
|
|
48
|
+
const base = String(path || '').split(/[\\/]/).pop() || ''
|
|
49
|
+
const dot = base.lastIndexOf('.')
|
|
50
|
+
const ext = dot > 0 ? base.slice(dot + 1).toLowerCase() : ''
|
|
51
|
+
return IMAGE_MIME[ext] ?? 'application/octet-stream'
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** PDF 统一 MIME(host base64 响应与 client PDF 面板同源,避免两处漂移)。 */
|
|
55
|
+
export const PDF_MIME = 'application/pdf'
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 路径 → 二进制预览 MIME:PDF 特判 {@link PDF_MIME},其余走图片表回退 octet-stream。
|
|
59
|
+
* @author ddj 2026年09月22号
|
|
60
|
+
* @param path 文件路径(`/` 或 `\` 分隔均可)
|
|
61
|
+
* @returns MIME 字符串
|
|
62
|
+
*/
|
|
63
|
+
export function binaryMimeOf(path: string): string {
|
|
64
|
+
const base = String(path || '').split(/[\\/]/).pop() || ''
|
|
65
|
+
const dot = base.lastIndexOf('.')
|
|
66
|
+
if (dot > 0 && base.slice(dot + 1).toLowerCase() === 'pdf') return PDF_MIME
|
|
67
|
+
return imageMimeOf(path)
|
|
68
|
+
}
|
|
69
|
+
|
|
24
70
|
/** 决策作用域:call=整条记录,hunk=单个差异块。 */
|
|
25
71
|
export type RpcScope = 'call' | 'hunk'
|
|
26
72
|
|
|
@@ -111,9 +157,10 @@ export interface RpcRequestMap {
|
|
|
111
157
|
'edrv.accept': { sessionId?: string; callId: string; scope?: RpcScope; hunkIndex?: number }
|
|
112
158
|
'edrv.reject': { sessionId?: string; callId: string; scope?: RpcScope; hunkIndex?: number }
|
|
113
159
|
'edrv.decideBatch': { sessionId?: string; items: DecideItem[] }
|
|
114
|
-
'edrv.read': { sessionId?: string; path: string }
|
|
160
|
+
'edrv.read': { sessionId?: string; path: string; encoding?: 'base64' }
|
|
115
161
|
'edrv.original': { sessionId?: string; path: string }
|
|
116
162
|
'edrv.save': { sessionId?: string; path: string; content: string }
|
|
163
|
+
'edrv.saveBinary': { sessionId?: string; path: string; content: string; encoding: 'base64' }
|
|
117
164
|
'edrv.archiveList': { sessionId?: string }
|
|
118
165
|
'edrv.archiveRead': { sessionId?: string; path?: string }
|
|
119
166
|
'edrv.rollback': { sessionId?: string; path: string; batch?: number }
|
|
@@ -194,9 +241,10 @@ export interface RpcOkMap {
|
|
|
194
241
|
'edrv.accept': { record: RecordView }
|
|
195
242
|
'edrv.reject': { record: RecordView }
|
|
196
243
|
'edrv.decideBatch': { results: DecideResult[] }
|
|
197
|
-
'edrv.read': { content: string; size: number }
|
|
244
|
+
'edrv.read': { content: string; size: number; encoding?: 'base64'; mime?: string }
|
|
198
245
|
'edrv.original': { content: string; size: number; stale: StaleHunk[]; fallback: boolean }
|
|
199
246
|
'edrv.save': object
|
|
247
|
+
'edrv.saveBinary': object
|
|
200
248
|
'edrv.archiveList': { entries: ArchiveEntry[] }
|
|
201
249
|
'edrv.archiveRead': { batches: ArchiveBatch[] }
|
|
202
250
|
'edrv.rollback': { path: string; batch: number | null }
|
package/src/store.ts
CHANGED
|
@@ -11,6 +11,8 @@ import { SIDECAR, SIDECAR_ARCHIVE } from './paths.js'
|
|
|
11
11
|
|
|
12
12
|
export { SIDECAR, SIDECAR_ARCHIVE }
|
|
13
13
|
export const READ_CAP = 8 * 1024 * 1024
|
|
14
|
+
/** 二进制预览/写回上限(PDF 等整文件 base64 传输;文本侧仍用 READ_CAP)。 */
|
|
15
|
+
export const BINARY_READ_CAP = 32 * 1024 * 1024
|
|
14
16
|
|
|
15
17
|
/** DSH 上下文与会话类型较宽松:本地无 dsh 类型声明,显式 any + 文档约束。 */
|
|
16
18
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|