@lyhue1991/dsh-soup 0.5.2 → 0.5.4
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 +4 -2
- package/lib/client.bundle.js +34531 -0
- package/lib/client.entry.js +2 -0
- package/lib/client.js +590 -42
- package/lib/codemirror-modules.js +33 -0
- package/lib/index.js +54 -3
- package/package.json +25 -2
package/lib/client.js
CHANGED
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* 与宿主通信:走同源 HTTP 路由 `/api/dsh-soup`(POST JSON),
|
|
11
11
|
* 即永久插件(profile bundle)规范桥梁,而非 dynamic 半区的 host.call。
|
|
12
|
-
*
|
|
13
|
-
* 其余按纯文本;不提供编辑与保存。
|
|
12
|
+
* 文件「预览」按类型渲染;常见文本文件可编辑并用 mtime/size 防覆盖保存。
|
|
14
13
|
*/
|
|
15
14
|
window.__ModuleLoader__.load({
|
|
16
15
|
id: '@lyhue1991/dsh-soup',
|
|
@@ -22,6 +21,40 @@ window.__ModuleLoader__.load({
|
|
|
22
21
|
var React = require('react')
|
|
23
22
|
var create = React.createElement
|
|
24
23
|
|
|
24
|
+
// CodeMirror 6 follows the same single-editor model used by JupyterLab:
|
|
25
|
+
// text, syntax decorations, cursor, gutter and scrolling share one view.
|
|
26
|
+
var CMEditorView = null
|
|
27
|
+
var CMEditorState = null
|
|
28
|
+
var CMKeymap = null
|
|
29
|
+
var CMCommands = null
|
|
30
|
+
var CMLanguage = null
|
|
31
|
+
var CMHighlightTags = null
|
|
32
|
+
var CMLangs = {}
|
|
33
|
+
try {
|
|
34
|
+
CMEditorView = require('@codemirror/view')
|
|
35
|
+
CMEditorState = require('@codemirror/state')
|
|
36
|
+
CMKeymap = require('@codemirror/view')
|
|
37
|
+
CMCommands = require('@codemirror/commands')
|
|
38
|
+
CMLanguage = require('@codemirror/language')
|
|
39
|
+
CMHighlightTags = require('@lezer/highlight').tags
|
|
40
|
+
CMLangs.css = require('@codemirror/lang-css')
|
|
41
|
+
CMLangs.html = require('@codemirror/lang-html')
|
|
42
|
+
CMLangs.javascript = require('@codemirror/lang-javascript')
|
|
43
|
+
CMLangs.json = require('@codemirror/lang-json')
|
|
44
|
+
CMLangs.markdown = require('@codemirror/lang-markdown')
|
|
45
|
+
CMLangs.python = require('@codemirror/lang-python')
|
|
46
|
+
CMLangs.sql = require('@codemirror/lang-sql')
|
|
47
|
+
CMLangs.xml = require('@codemirror/lang-xml')
|
|
48
|
+
} catch (err) {
|
|
49
|
+
CMEditorView = null
|
|
50
|
+
CMEditorState = null
|
|
51
|
+
CMKeymap = null
|
|
52
|
+
CMCommands = null
|
|
53
|
+
CMLanguage = null
|
|
54
|
+
CMHighlightTags = null
|
|
55
|
+
CMLangs = {}
|
|
56
|
+
}
|
|
57
|
+
|
|
25
58
|
// 可选依赖:DSH 自带的不可信 Markdown 渲染器(micromark 管线,与聊天
|
|
26
59
|
// 消息同款;不安全协议与相对链接被禁用)与 Shiki 代码高亮块(CodeBlock,
|
|
27
60
|
// 懒加载语法包 + 复制按钮)。旧版宿主缺该模块时回退纯文本预览。
|
|
@@ -128,6 +161,8 @@ window.__ModuleLoader__.load({
|
|
|
128
161
|
var ICON_REFRESH = '<svg xmlns="http://www.w3.org/2000/svg" width="16" viewBox="0 0 18 18"><path fill="currentColor" d="M9 13.5c-2.49 0-4.5-2.01-4.5-4.5S6.51 4.5 9 4.5c1.24 0 2.36.52 3.17 1.33L10 8h5V3l-1.76 1.76A6 6 0 0 0 9 3C5.69 3 3.01 5.69 3.01 9S5.69 15 9 15a5.98 5.98 0 0 0 5.9-5h-1.52c-.46 2-2.24 3.5-4.38 3.5"/></svg>'
|
|
129
162
|
var ICON_NEW_FOLDER = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="M20 6h-8l-2-2H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm-2 7h-3v3h-2v-3h-3v-2h3V8h2v3h3v2z"/></svg>'
|
|
130
163
|
var ICON_MORE = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><circle cx="3" cy="8" r="1.3" fill="currentColor"/><circle cx="8" cy="8" r="1.3" fill="currentColor"/><circle cx="13" cy="8" r="1.3" fill="currentColor"/></svg>'
|
|
164
|
+
var FILE_ICON_EDIT = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>'
|
|
165
|
+
var FILE_ICON_PREVIEW = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></svg>'
|
|
131
166
|
var EXPAND_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/></svg>'
|
|
132
167
|
var SHRINK_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"/></svg>'
|
|
133
168
|
|
|
@@ -156,8 +191,8 @@ window.__ModuleLoader__.load({
|
|
|
156
191
|
document.head.appendChild(goalTag)
|
|
157
192
|
}
|
|
158
193
|
|
|
159
|
-
// 文件标签页视图(conversation.view 'dsh-soup-files'):子 tab 条 +
|
|
160
|
-
//
|
|
194
|
+
// 文件标签页视图(conversation.view 'dsh-soup-files'):子 tab 条 + 预览/编辑区。
|
|
195
|
+
// markdown/html/json/csv 有专属渲染器;常见文本文件支持安全编辑保存。
|
|
161
196
|
var FILEVIEW_CSS =
|
|
162
197
|
'.dfv-root{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--dsw-specific-sidebar-fill);color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);user-select:text;-webkit-user-select:text;}' +
|
|
163
198
|
'.dfv-header{position:sticky;top:0;z-index:2;flex:none;background:var(--dsw-specific-sidebar-fill);}' +
|
|
@@ -175,20 +210,40 @@ window.__ModuleLoader__.load({
|
|
|
175
210
|
'.dfv-btn{flex:none;display:inline-flex;align-items:center;gap:5px;height:26px;box-sizing:border-box;background:transparent;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);font-size:12px;line-height:1;padding:0 10px;border-radius:7px;cursor:pointer;}' +
|
|
176
211
|
'.dfv-btn:hover{background:var(--dsw-alias-interactive-bg-hover);}' +
|
|
177
212
|
'.dfv-btn svg{width:14px;height:14px;display:block;flex:none;}' +
|
|
213
|
+
'.dfv-btn:disabled{opacity:.45;cursor:default;}' +
|
|
214
|
+
'.dfv-btn-primary{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary);}' +
|
|
215
|
+
'.dfv-status{flex:none;max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-state-warning-primary);}' +
|
|
216
|
+
'.dfv-save-error{flex:none;max-width:360px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-state-error-primary);}' +
|
|
217
|
+
'.dfv-saved{color:var(--dsw-alias-state-success-primary);}' +
|
|
218
|
+
'.dfv-cm-editor{--dsh-soup-code-cursor:#1f2328;--dsh-soup-code-plain:#1f2328;--dsh-soup-code-comment:#57606a;--dsh-soup-code-keyword:#cf3476;--dsh-soup-code-string:#116329;--dsh-soup-code-number:#953800;--dsh-soup-code-type:#0969da;--dsh-soup-code-variable:#1f2328;--dsh-soup-code-function:#8250df;--dsh-soup-code-property:#0550ae;--dsh-soup-code-punctuation:#57606a;--dsh-soup-code-invalid:#cf222e;--dsh-soup-json-property:#0550ae;--dsh-soup-json-string:#116329;--dsh-soup-json-number:#953800;--dsh-soup-json-literal:#8250df;--dsh-soup-json-punctuation:#57606a;flex:1;min-height:0;min-width:0;overflow:hidden;background:var(--dsw-alias-bg-layer-1);}' +
|
|
219
|
+
'body[data-ds-dark-theme] .dfv-cm-editor{--dsh-soup-code-cursor:#f9fafb;--dsh-soup-code-plain:#f9fafb;--dsh-soup-code-comment:#7d8590;--dsh-soup-code-keyword:#faa2c1;--dsh-soup-code-string:#8ce99a;--dsh-soup-code-number:#ffa94d;--dsh-soup-code-type:#4dabf7;--dsh-soup-code-variable:#f9fafb;--dsh-soup-code-function:#b197fc;--dsh-soup-code-property:#82aaff;--dsh-soup-code-punctuation:#ced4da;--dsh-soup-code-invalid:#ff6b6b;--dsh-soup-json-property:#82aaff;--dsh-soup-json-string:#9ece8f;--dsh-soup-json-number:#e0a96d;--dsh-soup-json-literal:#c792ea;--dsh-soup-json-punctuation:#ced4da;}' +
|
|
220
|
+
'.dfv-cm-editor .cm-editor{height:100%;color:var(--dsh-soup-code-plain);background:transparent;font:12.5px/1.55 var(--ds-font-family-code);}' +
|
|
221
|
+
'.dfv-cm-editor .cm-scroller{overflow:auto;font:inherit;}' +
|
|
222
|
+
'.dfv-cm-editor .cm-content{padding:10px 14px;min-height:calc(100% - 20px);caret-color:var(--dsh-soup-code-cursor);}' +
|
|
223
|
+
'.dfv-cm-editor .cm-cursor{border-left-color:var(--dsh-soup-code-cursor)!important;}' +
|
|
224
|
+
'.dfv-cm-editor .cm-line{padding:0;}' +
|
|
225
|
+
'.dfv-cm-editor .cm-gutters{border-right:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-caption);font:12.5px/1.55 var(--ds-font-family-code);}' +
|
|
226
|
+
'.dfv-cm-editor .cm-gutterElement{box-sizing:content-box;padding:0 8px 0 12px!important;min-width:0!important;}' +
|
|
227
|
+
'.dfv-cm-editor .cm-activeLine,.dfv-cm-editor .cm-activeLineGutter{background:transparent;}' +
|
|
228
|
+
'.dfv-cm-editor .cm-selectionBackground{background:var(--dsw-alias-state-business-primary-hover);}' +
|
|
229
|
+
'.dfv-cm-editor .cm-focused{outline:none;}' +
|
|
230
|
+
'.dfv-editor{flex:1;min-height:0;width:100%;box-sizing:border-box;margin:0;padding:10px 14px;border:none;outline:none;resize:none;overflow:auto;background:transparent;color:var(--dsh-soup-code-plain);caret-color:var(--dsh-soup-code-cursor);font:12.5px/1.55 var(--ds-font-family-code);letter-spacing:normal;text-align:left;text-indent:0;text-transform:none;direction:ltr;white-space:pre;overflow-wrap:normal;word-break:normal;tab-size:2;}' +
|
|
231
|
+
'.dfv-editor::selection{background:var(--dsw-alias-state-business-primary-hover);}' +
|
|
232
|
+
'.dfv-dirty{flex:none;width:7px;height:7px;border-radius:999px;background:var(--dsw-alias-state-warning-primary);}' +
|
|
178
233
|
'.dfv-error{padding:8px 14px;font-size:12px;color:var(--dsw-alias-state-error-primary);flex:none;}' +
|
|
179
234
|
'.dfv-muted{padding:24px 14px;font-size:13px;color:var(--dsw-alias-label-tertiary);text-align:center;}' +
|
|
180
235
|
'.dfv-cap{padding:5px 14px;font-size:11px;color:var(--dsw-alias-label-caption);border-bottom:1px solid var(--dsw-alias-border-l1);flex:none;}' +
|
|
181
236
|
'.dfv-code{flex:1;min-height:0;width:100%;box-sizing:border-box;margin:0;padding:10px 14px;overflow:auto;background:transparent;color:var(--dsw-alias-label-primary);font-family:var(--ds-font-family-code);font-size:12.5px;line-height:1.55;white-space:pre;tab-size:2;user-select:text;}' +
|
|
182
237
|
'.dfv-code-wrap{flex:1;min-height:0;overflow:auto;padding:8px 10px;}' +
|
|
183
238
|
'.dfv-code-lines{flex:1;min-height:0;display:flex;overflow:auto;}' +
|
|
184
|
-
'.dfv-code-gutter{position:sticky;left:0;z-index:1;flex:none;padding:10px 8px 10px 12px;text-align:right;color:var(--dsw-alias-label-caption);font-family:var(--ds-font-family-code);font-size:12.5px;line-height:1.55;user-select:none;background:var(--dsw-alias-bg-layer-1);border-right:1px solid var(--dsw-alias-border-l1);}' +
|
|
239
|
+
'.dfv-code-gutter{position:sticky;left:0;z-index:1;flex:none;padding:10px 8px 10px 12px;box-sizing:border-box;text-align:right;color:var(--dsw-alias-label-caption);font-family:var(--ds-font-family-code);font-size:12.5px;line-height:1.55;user-select:none;background:var(--dsw-alias-bg-layer-1);border-right:1px solid var(--dsw-alias-border-l1);}' +
|
|
185
240
|
'.dfv-code-main{flex:1;min-width:0;}' +
|
|
186
241
|
'.dfv-code-main .dfv-code{overflow:visible;width:auto;}' +
|
|
187
242
|
// CodeBlock(md-code-block)在行号布局内的适配:
|
|
188
243
|
// 隐藏 banner(js 标签 + 复制按钮),统一 padding/字体与 gutter 对齐。
|
|
189
244
|
'.dfv-code-main .md-code-block{margin:0;border-radius:0;background:transparent;}' +
|
|
190
245
|
'.dfv-code-main .md-code-block>div:first-child{display:none!important;}' +
|
|
191
|
-
'.dfv-code-main .md-code-block pre{padding:10px 14px!important;margin:0!important;font:12.5px/1.55 var(--ds-font-family-code)!important;background:transparent!important;border-radius:0!important;white-space:pre;word-break:normal;}' +
|
|
246
|
+
'.dfv-code-main .md-code-block pre{padding:10px 14px!important;margin:0!important;font:12.5px/1.55 var(--ds-font-family-code)!important;background:transparent!important;border-radius:0!important;white-space:pre!important;word-break:normal!important;}' +
|
|
192
247
|
'.dfv-nb{flex:1;min-height:0;overflow:auto;background:var(--dsw-alias-bg-base);}' +
|
|
193
248
|
'.dfv-nb-cell{display:flex;border-bottom:1px solid var(--dsw-alias-border-l1);}' +
|
|
194
249
|
'.dfv-nb-gutter{flex:none;width:44px;padding:8px 6px;text-align:right;font-family:var(--ds-font-family-code);font-size:11px;color:var(--dsw-alias-label-caption);border-right:1px solid var(--dsw-alias-border-l1);user-select:none;background:var(--dsw-specific-sidebar-fill);}' +
|
|
@@ -230,7 +285,17 @@ window.__ModuleLoader__.load({
|
|
|
230
285
|
// 优先用稳定选择器;旧 hash 规则保留以兼容更老的宿主版本。
|
|
231
286
|
'[data-dsh-soup-preview="1"] [data-composer-seat]{display:none !important;}' +
|
|
232
287
|
'[data-dsh-soup-preview="1"] .wSkVaW_composerSeat{display:none !important;}' +
|
|
233
|
-
'[data-dsh-soup-preview="1"] .FJxK0a_root{display:none !important;}'
|
|
288
|
+
'[data-dsh-soup-preview="1"] .FJxK0a_root{display:none !important;}' +
|
|
289
|
+
// 宿主会话文本宽度调整手柄(DSH 0.1.5-rc 起带稳定属性 data-width-handle,
|
|
290
|
+
// left/right 各一个;旧版 hash 类名兜底)。它是给对话正文调宽用的,
|
|
291
|
+
// 预览页里没有对应内容,悬停边缘时不应出现。
|
|
292
|
+
'[data-dsh-soup-preview="1"] [data-width-handle]{display:none !important;}' +
|
|
293
|
+
'[data-dsh-soup-preview="1"] .wSkVaW_widthHandle{display:none !important;}' +
|
|
294
|
+
// Desktop 版的宽度手柄在部分版本中是会话根的相邻节点而非其后代,
|
|
295
|
+
// 上面的作用域选择器覆盖不到。FilesView 存活时会在 body 上设置该状态,
|
|
296
|
+
// 以稳定属性为主全局禁用手柄和它的 hover 指示线。
|
|
297
|
+
'body.dsh-soup-preview-active [data-width-handle]{display:none !important;pointer-events:none !important;}' +
|
|
298
|
+
'body.dsh-soup-preview-active .wSkVaW_widthHandle{display:none !important;pointer-events:none !important;}'
|
|
234
299
|
if (typeof document !== 'undefined' && document.querySelector('style[data-plugin-css="@lyhue1991/dsh-soup/fileview.css"]') === null) {
|
|
235
300
|
var fileviewTag = document.createElement('style')
|
|
236
301
|
fileviewTag.dataset.plugin = '@lyhue1991/dsh-soup'
|
|
@@ -240,6 +305,9 @@ window.__ModuleLoader__.load({
|
|
|
240
305
|
}
|
|
241
306
|
|
|
242
307
|
/** 需要的客户端服务:slots、layout、timer、sessions、locale(i18n 词典)+ remote.goals(goal 动作动词)。 */
|
|
308
|
+
// 注意:sidebarRight 不能进 inject——DSH Desktop (0.1.5-rc.2) 宿主里该服务
|
|
309
|
+
// 永远不就绪,可选注入也会让插件一直 pending("waiting for service"),
|
|
310
|
+
// 被 desktop 判为阻挡启动。桥接改为运行时轮询解析(见 resolveSidebarRight)。
|
|
243
311
|
var inject = ['slots', 'layout', 'timer', 'sessions', 'locale', 'remote', 'remote.goals']
|
|
244
312
|
|
|
245
313
|
// ------------------------------------------------------------------
|
|
@@ -310,6 +378,22 @@ window.__ModuleLoader__.load({
|
|
|
310
378
|
'files.imageTooLarge': '图片过大({size},上限 {limit})',
|
|
311
379
|
'files.pdfTooLarge': 'PDF 过大({size},上限 {limit}),请用系统打开',
|
|
312
380
|
'files.truncated': '文件超过 8 MB,仅预览前 8 MB;完整内容请用系统编辑器打开。',
|
|
381
|
+
'files.edit': '编辑',
|
|
382
|
+
'files.editTitle': '编辑当前文本文件',
|
|
383
|
+
'files.preview': '预览',
|
|
384
|
+
'files.previewTitle': '返回预览模式',
|
|
385
|
+
'files.save': '保存',
|
|
386
|
+
'files.saveTitle': '保存修改(⌘S / Ctrl+S)',
|
|
387
|
+
'files.saving': '保存中…',
|
|
388
|
+
'files.saved': '已保存',
|
|
389
|
+
'files.cancelEdit': '放弃修改',
|
|
390
|
+
'files.unsaved': '有未保存修改',
|
|
391
|
+
'files.unsavedConfirm': '当前文件有未保存修改,确认放弃?',
|
|
392
|
+
'files.conflictShort': '文件在外部被修改',
|
|
393
|
+
'files.conflict': '其他程序修改了磁盘文件;当前草稿仍保留在编辑器中,下次自动保存会覆盖磁盘版本。',
|
|
394
|
+
'files.overwriteConfirm': '文件已被其他程序修改。要用当前编辑内容覆盖磁盘上的最新版本吗?',
|
|
395
|
+
'files.notEditable': '当前文件类型不支持编辑',
|
|
396
|
+
'files.truncatedEdit': '文件超过 8 MB,已截断,不能编辑保存。',
|
|
313
397
|
'files.jsonFail': 'JSON 解析失败,按纯文本显示',
|
|
314
398
|
'files.nbFail': 'ipynb 解析失败,按纯文本显示',
|
|
315
399
|
'files.nbEmpty': '(空笔记本)',
|
|
@@ -381,6 +465,22 @@ window.__ModuleLoader__.load({
|
|
|
381
465
|
'files.imageTooLarge': 'Image too large ({size}, limit {limit})',
|
|
382
466
|
'files.pdfTooLarge': 'PDF too large ({size}, limit {limit}) — open with the system viewer',
|
|
383
467
|
'files.truncated': 'File exceeds 8 MB — showing the first 8 MB; open in a system editor for the full content.',
|
|
468
|
+
'files.edit': 'Edit',
|
|
469
|
+
'files.editTitle': 'Edit this text file',
|
|
470
|
+
'files.preview': 'Preview',
|
|
471
|
+
'files.previewTitle': 'Back to preview mode',
|
|
472
|
+
'files.save': 'Save',
|
|
473
|
+
'files.saveTitle': 'Save changes (⌘S / Ctrl+S)',
|
|
474
|
+
'files.saving': 'Saving…',
|
|
475
|
+
'files.saved': 'Saved',
|
|
476
|
+
'files.cancelEdit': 'Discard changes',
|
|
477
|
+
'files.unsaved': 'Unsaved changes',
|
|
478
|
+
'files.unsavedConfirm': 'This file has unsaved changes. Discard them?',
|
|
479
|
+
'files.conflictShort': 'Changed outside the editor',
|
|
480
|
+
'files.conflict': 'Another program changed the file on disk. Your draft is preserved here, and the next auto-save will overwrite the disk version.',
|
|
481
|
+
'files.overwriteConfirm': 'The file was changed externally. Overwrite the latest disk version with your current editor content?',
|
|
482
|
+
'files.notEditable': 'Editing is not supported for this file type',
|
|
483
|
+
'files.truncatedEdit': 'The file exceeds 8 MB and was truncated, so it cannot be safely edited.',
|
|
384
484
|
'files.jsonFail': 'JSON parse failed — shown as plain text',
|
|
385
485
|
'files.nbFail': 'ipynb parse failed — shown as plain text',
|
|
386
486
|
'files.nbEmpty': '(empty notebook)',
|
|
@@ -417,6 +517,11 @@ window.__ModuleLoader__.load({
|
|
|
417
517
|
// while a blank conversation is mounted. Keep late async responses from an
|
|
418
518
|
// older caller from overwriting the current session's shared store.
|
|
419
519
|
var sessionTrackGeneration = 0
|
|
520
|
+
var autoSaveTimers = new Map()
|
|
521
|
+
var autoSaveSavedTimers = new Map()
|
|
522
|
+
var manualSavePaths = new Set()
|
|
523
|
+
var AUTO_SAVE_DELAY_MS = 5000
|
|
524
|
+
var SAVED_INDICATOR_MS = 1500
|
|
420
525
|
|
|
421
526
|
// 目录展开状态按 cwd 隔离。刷新/删除/改名/移动后先重拉根目录,
|
|
422
527
|
// 再按这些路径递归重拉仍存在的已展开目录;切换项目时才清空。
|
|
@@ -620,7 +725,21 @@ window.__ModuleLoader__.load({
|
|
|
620
725
|
if (!res || !res.ok) return
|
|
621
726
|
entry.kind = res.kind
|
|
622
727
|
entry.size = res.size || 0
|
|
623
|
-
if (res.kind === 'text') {
|
|
728
|
+
if (res.kind === 'text') {
|
|
729
|
+
entry.truncated = !!res.truncated
|
|
730
|
+
if (!entry.dirty && !entry.editing) {
|
|
731
|
+
entry.content = res.content
|
|
732
|
+
entry.baseContent = res.content
|
|
733
|
+
entry.draft = res.content
|
|
734
|
+
entry.baseMtime = res.mtime
|
|
735
|
+
entry.baseSize = res.size
|
|
736
|
+
} else {
|
|
737
|
+
entry.conflict = true
|
|
738
|
+
entry.diskMtime = res.mtime
|
|
739
|
+
entry.diskSize = res.size
|
|
740
|
+
scheduleAutoSave(entry.path)
|
|
741
|
+
}
|
|
742
|
+
}
|
|
624
743
|
else if (res.kind === 'image') { entry.dataUrl = 'data:' + res.mime + ';base64,' + res.data }
|
|
625
744
|
else if (res.kind === 'pdf') { entry.data = res.data }
|
|
626
745
|
setFiles({ list: state.files.list.slice() })
|
|
@@ -672,7 +791,12 @@ window.__ModuleLoader__.load({
|
|
|
672
791
|
}
|
|
673
792
|
for (var f = 0; f < dirtyFiles.length; f++) {
|
|
674
793
|
var entry = findFileEntry(dirtyFiles[f])
|
|
675
|
-
if (entry
|
|
794
|
+
if (!entry || !entry.loaded) continue
|
|
795
|
+
var currentSignature = mtimes[dirtyFiles[f]]
|
|
796
|
+
if (currentSignature
|
|
797
|
+
&& currentSignature.m === entry.baseMtime
|
|
798
|
+
&& currentSignature.s === entry.baseSize) continue
|
|
799
|
+
await silentReloadEntry(entry)
|
|
676
800
|
}
|
|
677
801
|
return true
|
|
678
802
|
} finally {
|
|
@@ -1844,6 +1968,9 @@ window.__ModuleLoader__.load({
|
|
|
1844
1968
|
loading: true, loaded: false, error: '',
|
|
1845
1969
|
kind: null, content: '', dataUrl: '', data: '',
|
|
1846
1970
|
truncated: false, size: 0,
|
|
1971
|
+
baseContent: '', baseMtime: undefined, baseSize: undefined,
|
|
1972
|
+
draft: '', dirty: false, editing: false, saving: false, justSaved: false,
|
|
1973
|
+
saveError: '', conflict: false,
|
|
1847
1974
|
}
|
|
1848
1975
|
// FIFO:最多同时 5 个预览,新开的挤掉最早打开的
|
|
1849
1976
|
var list = state.files.list.slice()
|
|
@@ -1874,13 +2001,96 @@ window.__ModuleLoader__.load({
|
|
|
1874
2001
|
cur.loaded = true
|
|
1875
2002
|
cur.kind = res.kind
|
|
1876
2003
|
cur.size = res.size || 0
|
|
1877
|
-
if (res.kind === 'text') {
|
|
2004
|
+
if (res.kind === 'text') {
|
|
2005
|
+
cur.truncated = !!res.truncated
|
|
2006
|
+
if (!cur.dirty && !cur.editing) {
|
|
2007
|
+
cur.content = res.content
|
|
2008
|
+
cur.baseContent = res.content
|
|
2009
|
+
cur.draft = res.content
|
|
2010
|
+
cur.baseMtime = res.mtime
|
|
2011
|
+
cur.baseSize = res.size
|
|
2012
|
+
} else {
|
|
2013
|
+
cur.conflict = true
|
|
2014
|
+
cur.diskMtime = res.mtime
|
|
2015
|
+
cur.diskSize = res.size
|
|
2016
|
+
scheduleAutoSave(path)
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
1878
2019
|
else if (res.kind === 'image') { cur.dataUrl = 'data:' + res.mime + ';base64,' + res.data }
|
|
1879
2020
|
else if (res.kind === 'pdf') { cur.data = res.data }
|
|
1880
2021
|
}
|
|
1881
2022
|
setFiles({ list: state.files.list.slice(), active: path })
|
|
1882
2023
|
}
|
|
1883
2024
|
|
|
2025
|
+
// ------------------------------------------------------------------
|
|
2026
|
+
// 官方 file 地址桥接:dsh-soup 遮蔽 rightbar 槽位后,官方 SidebarRight
|
|
2027
|
+
// 面板组件永不挂载,会话里的文件产物卡片 / 内联文件链接调用
|
|
2028
|
+
// ctx.sidebarRight.openResource('dsh-resource://file/…') 会抛
|
|
2029
|
+
// "sidebarRight: no session surface is mounted"。这里把 file 地址解析
|
|
2030
|
+
// 回路径,转投 dsh-soup 自己的预览 tab(openFileInTab)。
|
|
2031
|
+
// ------------------------------------------------------------------
|
|
2032
|
+
var FILE_ADDRESS_PREFIX = 'dsh-resource://file/'
|
|
2033
|
+
|
|
2034
|
+
function decodeSegmentSafe(segment) {
|
|
2035
|
+
try { return decodeURIComponent(segment) } catch (e) { return null }
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
/** 解析官方 file 地址;非 file 地址或编码非法返回 null(与 workspace-path 同规则)。 */
|
|
2039
|
+
function parseFileAddress(address) {
|
|
2040
|
+
if (typeof address !== 'string' || address.indexOf(FILE_ADDRESS_PREFIX) !== 0) return null
|
|
2041
|
+
var end = address.search(/[?#]/)
|
|
2042
|
+
var body = address.slice(FILE_ADDRESS_PREFIX.length, end === -1 ? undefined : end)
|
|
2043
|
+
var parts = body.split('/')
|
|
2044
|
+
if (parts[0] === 'session') {
|
|
2045
|
+
var sessionId = decodeSegmentSafe(parts[1] || '')
|
|
2046
|
+
if (!sessionId) return null
|
|
2047
|
+
var segments = []
|
|
2048
|
+
for (var i = 2; i < parts.length; i++) {
|
|
2049
|
+
var seg = decodeSegmentSafe(parts[i])
|
|
2050
|
+
if (seg === null) return null
|
|
2051
|
+
segments.push(seg)
|
|
2052
|
+
}
|
|
2053
|
+
if (!segments.length) return null
|
|
2054
|
+
return { scope: 'session', sessionId: sessionId, path: segments.join('/') }
|
|
2055
|
+
}
|
|
2056
|
+
if (parts[0] === 'absolute') {
|
|
2057
|
+
var absolute = []
|
|
2058
|
+
for (var j = 1; j < parts.length; j++) {
|
|
2059
|
+
var abs = decodeSegmentSafe(parts[j])
|
|
2060
|
+
if (abs === null) return null
|
|
2061
|
+
absolute.push(abs)
|
|
2062
|
+
}
|
|
2063
|
+
if (!absolute.length) return null
|
|
2064
|
+
return { scope: 'absolute', path: '/' + absolute.join('/') }
|
|
2065
|
+
}
|
|
2066
|
+
return null
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
/** 同步判断:该 file 地址是否应由 dsh-soup 接住(当前会话或绝对路径)。 */
|
|
2070
|
+
function fileAddressMatches(address, sessionIdHint) {
|
|
2071
|
+
var parsed = parseFileAddress(address)
|
|
2072
|
+
if (!parsed) return false
|
|
2073
|
+
if (parsed.scope === 'session' && (sessionIdHint || parsed.sessionId) !== activeSessionId) return false
|
|
2074
|
+
return true
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
/** file 地址 → dsh-soup 预览。session 作用域的路径相对会话 cwd,
|
|
2078
|
+
* 必须先解析成绝对路径再读——宿主 read 对相对路径按服务进程 cwd
|
|
2079
|
+
* (桌面端为 launch-root)解析,直接透传会 ENOENT。 */
|
|
2080
|
+
async function openFileAddressInSoup(address, sessionIdHint) {
|
|
2081
|
+
var parsed = parseFileAddress(address)
|
|
2082
|
+
if (!parsed) return false
|
|
2083
|
+
var sid = sessionIdHint || parsed.sessionId
|
|
2084
|
+
if (parsed.scope === 'session' && sid !== activeSessionId) return false
|
|
2085
|
+
var target = parsed.path
|
|
2086
|
+
if (parsed.scope === 'session' && target.charAt(0) !== '/') {
|
|
2087
|
+
var cwd = state.cwd || await defaultCwd(parsed.sessionId)
|
|
2088
|
+
if (cwd) target = String(cwd).replace(/\/+$/, '') + '/' + target
|
|
2089
|
+
}
|
|
2090
|
+
openFileInTab(target)
|
|
2091
|
+
return true
|
|
2092
|
+
}
|
|
2093
|
+
|
|
1884
2094
|
async function reloadFile(path) {
|
|
1885
2095
|
var entry = findFileEntry(path)
|
|
1886
2096
|
if (!entry) return
|
|
@@ -1889,11 +2099,145 @@ window.__ModuleLoader__.load({
|
|
|
1889
2099
|
await openFileInTab(path)
|
|
1890
2100
|
}
|
|
1891
2101
|
|
|
2102
|
+
function isEditableTextFile(path) {
|
|
2103
|
+
var kind = previewKind(path)
|
|
2104
|
+
var ext = extOfName(path)
|
|
2105
|
+
return kind === 'markdown' || kind === 'html' || kind === 'json'
|
|
2106
|
+
|| Object.prototype.hasOwnProperty.call(CODE_LANG_BY_EXT, ext)
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
function startEditing(path) {
|
|
2110
|
+
var entry = findFileEntry(path)
|
|
2111
|
+
if (!entry || !entry.loaded || entry.kind !== 'text') return
|
|
2112
|
+
if (!isEditableTextFile(path)) {
|
|
2113
|
+
entry.saveError = T('files.notEditable')
|
|
2114
|
+
setFiles({ list: state.files.list.slice() })
|
|
2115
|
+
return
|
|
2116
|
+
}
|
|
2117
|
+
if (entry.truncated) {
|
|
2118
|
+
entry.saveError = T('files.truncatedEdit')
|
|
2119
|
+
setFiles({ list: state.files.list.slice() })
|
|
2120
|
+
return
|
|
2121
|
+
}
|
|
2122
|
+
if (!entry.editing) {
|
|
2123
|
+
entry.draft = entry.dirty ? entry.draft : entry.content
|
|
2124
|
+
entry.editing = true
|
|
2125
|
+
}
|
|
2126
|
+
entry.saveError = ''
|
|
2127
|
+
setFiles({ list: state.files.list.slice() })
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
function scheduleAutoSave(path) {
|
|
2131
|
+
clearTimeout(autoSaveTimers.get(path))
|
|
2132
|
+
autoSaveTimers.set(path, setTimeout(function () {
|
|
2133
|
+
autoSaveTimers.delete(path)
|
|
2134
|
+
saveActiveFile(path)
|
|
2135
|
+
}, AUTO_SAVE_DELAY_MS))
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
function showSavedIndicator(path) {
|
|
2139
|
+
clearTimeout(autoSaveSavedTimers.get(path))
|
|
2140
|
+
autoSaveSavedTimers.set(path, setTimeout(function () {
|
|
2141
|
+
autoSaveSavedTimers.delete(path)
|
|
2142
|
+
var entry = findFileEntry(path)
|
|
2143
|
+
if (!entry || entry.saving || entry.dirty) return
|
|
2144
|
+
entry.justSaved = false
|
|
2145
|
+
setFiles({ list: state.files.list.slice() })
|
|
2146
|
+
}, SAVED_INDICATOR_MS))
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
function manuallySaveFile(path) {
|
|
2150
|
+
manualSavePaths.add(path)
|
|
2151
|
+
saveActiveFile(path)
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
function stopEditing(path) {
|
|
2155
|
+
var entry = findFileEntry(path)
|
|
2156
|
+
if (!entry || !entry.editing) return
|
|
2157
|
+
clearTimeout(autoSaveTimers.get(path))
|
|
2158
|
+
autoSaveTimers.delete(path)
|
|
2159
|
+
clearTimeout(autoSaveSavedTimers.get(path))
|
|
2160
|
+
autoSaveSavedTimers.delete(path)
|
|
2161
|
+
if (entry.dirty) {
|
|
2162
|
+
if (entry.saving) entry.saveOnExit = true
|
|
2163
|
+
else saveActiveFile(path)
|
|
2164
|
+
}
|
|
2165
|
+
entry.editing = false
|
|
2166
|
+
setFiles({ list: state.files.list.slice() })
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
function updateDraft(path, value) {
|
|
2170
|
+
var entry = findFileEntry(path)
|
|
2171
|
+
if (!entry || !entry.editing) return
|
|
2172
|
+
entry.draft = value
|
|
2173
|
+
entry.dirty = value !== entry.baseContent
|
|
2174
|
+
setFiles({ list: state.files.list.slice() })
|
|
2175
|
+
scheduleAutoSave(path)
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
async function saveActiveFile(path) {
|
|
2179
|
+
var entry = findFileEntry(path)
|
|
2180
|
+
if (!entry || (!entry.editing && !entry.saveOnExit) || entry.saving || entry.truncated) return
|
|
2181
|
+
if (!entry.dirty) return
|
|
2182
|
+
entry.saving = true
|
|
2183
|
+
entry.saveError = ''
|
|
2184
|
+
setFiles({ list: state.files.list.slice() })
|
|
2185
|
+
if (entry.conflict) {
|
|
2186
|
+
entry.baseMtime = entry.diskMtime
|
|
2187
|
+
entry.baseSize = entry.diskSize
|
|
2188
|
+
entry.conflict = false
|
|
2189
|
+
}
|
|
2190
|
+
var savedDraft = entry.draft
|
|
2191
|
+
var wasManualSave = manualSavePaths.has(path)
|
|
2192
|
+
manualSavePaths.delete(path)
|
|
2193
|
+
var res = await rpc('write', {
|
|
2194
|
+
path: path,
|
|
2195
|
+
content: entry.draft,
|
|
2196
|
+
expectedMtime: entry.baseMtime,
|
|
2197
|
+
expectedSize: entry.baseSize,
|
|
2198
|
+
sessionId: activeSessionId,
|
|
2199
|
+
})
|
|
2200
|
+
var cur = findFileEntry(path)
|
|
2201
|
+
if (!cur) return
|
|
2202
|
+
cur.saving = false
|
|
2203
|
+
if (!res || !res.ok) {
|
|
2204
|
+
cur.saveError = (res && res.error) || T('files.saveTitle')
|
|
2205
|
+
if (res && res.conflict) {
|
|
2206
|
+
cur.conflict = true
|
|
2207
|
+
cur.diskMtime = res.mtime
|
|
2208
|
+
cur.diskSize = res.size
|
|
2209
|
+
}
|
|
2210
|
+
if (cur.editing || cur.saveOnExit) scheduleAutoSave(path)
|
|
2211
|
+
setFiles({ list: state.files.list.slice() })
|
|
2212
|
+
return
|
|
2213
|
+
}
|
|
2214
|
+
cur.content = savedDraft
|
|
2215
|
+
cur.baseContent = savedDraft
|
|
2216
|
+
cur.dirty = cur.draft !== savedDraft
|
|
2217
|
+
if (!cur.dirty) cur.saveOnExit = false
|
|
2218
|
+
else if (cur.editing || cur.saveOnExit) scheduleAutoSave(path)
|
|
2219
|
+
cur.conflict = false
|
|
2220
|
+
cur.saveError = ''
|
|
2221
|
+
cur.justSaved = wasManualSave
|
|
2222
|
+
cur.size = res.size
|
|
2223
|
+
cur.baseMtime = res.mtime
|
|
2224
|
+
cur.baseSize = res.size
|
|
2225
|
+
setFiles({ list: state.files.list.slice() })
|
|
2226
|
+
refresh()
|
|
2227
|
+
if (wasManualSave) showSavedIndicator(path)
|
|
2228
|
+
}
|
|
2229
|
+
|
|
1892
2230
|
function closeFileTab(path) {
|
|
1893
2231
|
var list = state.files.list
|
|
1894
2232
|
var idx = -1
|
|
1895
2233
|
for (var i = 0; i < list.length; i++) if (list[i].path === path) { idx = i; break }
|
|
1896
2234
|
if (idx < 0) return
|
|
2235
|
+
clearTimeout(autoSaveTimers.get(path))
|
|
2236
|
+
autoSaveTimers.delete(path)
|
|
2237
|
+
clearTimeout(autoSaveSavedTimers.get(path))
|
|
2238
|
+
autoSaveSavedTimers.delete(path)
|
|
2239
|
+
var closing = list[idx]
|
|
2240
|
+
if (closing.dirty && typeof window !== 'undefined' && !window.confirm(T('files.unsavedConfirm'))) return
|
|
1897
2241
|
var next = list.slice(0, idx).concat(list.slice(idx + 1))
|
|
1898
2242
|
var active = state.files.active
|
|
1899
2243
|
if (active === path) {
|
|
@@ -1936,6 +2280,126 @@ window.__ModuleLoader__.load({
|
|
|
1936
2280
|
return 'text'
|
|
1937
2281
|
}
|
|
1938
2282
|
|
|
2283
|
+
function codeMirrorLanguage(path) {
|
|
2284
|
+
var ext = extOfName(path)
|
|
2285
|
+
var factory = ext === 'py' ? CMLangs.python && CMLangs.python.python
|
|
2286
|
+
: ext === 'js' || ext === 'mjs' || ext === 'cjs' ? CMLangs.javascript && CMLangs.javascript.javascript
|
|
2287
|
+
: ext === 'jsx' ? CMLangs.javascript && CMLangs.javascript.javascript
|
|
2288
|
+
: ext === 'ts' || ext === 'mts' || ext === 'cts' ? CMLangs.javascript && CMLangs.javascript.javascript
|
|
2289
|
+
: ext === 'tsx' ? CMLangs.javascript && CMLangs.javascript.javascript
|
|
2290
|
+
: ext === 'json' || ext === 'jsonc' ? CMLangs.json && CMLangs.json.json
|
|
2291
|
+
: ext === 'md' || ext === 'markdown' || ext === 'mdx' ? CMLangs.markdown && CMLangs.markdown.markdown
|
|
2292
|
+
: ext === 'html' || ext === 'htm' ? CMLangs.html && CMLangs.html.html
|
|
2293
|
+
: ext === 'css' || ext === 'scss' || ext === 'less' ? CMLangs.css && CMLangs.css.css
|
|
2294
|
+
: ext === 'sql' ? CMLangs.sql && CMLangs.sql.sql
|
|
2295
|
+
: ext === 'xml' ? CMLangs.xml && CMLangs.xml.xml
|
|
2296
|
+
: null
|
|
2297
|
+
if (!factory) return null
|
|
2298
|
+
try {
|
|
2299
|
+
if (ext === 'js' || ext === 'mjs' || ext === 'cjs' || ext === 'jsx') return factory({ jsx: true })
|
|
2300
|
+
if (ext === 'ts' || ext === 'mts' || ext === 'cts' || ext === 'tsx') return factory({ typescript: true, jsx: ext === 'tsx' })
|
|
2301
|
+
return factory()
|
|
2302
|
+
} catch (err) { return null }
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
var PREVIEW_HIGHLIGHT_STYLE = (CMLanguage && CMLanguage.HighlightStyle && CMHighlightTags)
|
|
2306
|
+
? CMLanguage.HighlightStyle.define([
|
|
2307
|
+
{ tag: CMHighlightTags.comment, color: 'var(--dsh-soup-code-comment)' },
|
|
2308
|
+
{ tag: CMHighlightTags.keyword, color: 'var(--dsh-soup-code-keyword)' },
|
|
2309
|
+
{ tag: CMHighlightTags.controlKeyword, color: 'var(--dsh-soup-code-keyword)' },
|
|
2310
|
+
{ tag: CMHighlightTags.definitionKeyword, color: 'var(--dsh-soup-code-keyword)' },
|
|
2311
|
+
{ tag: CMHighlightTags.moduleKeyword, color: 'var(--dsh-soup-code-keyword)' },
|
|
2312
|
+
{ tag: CMHighlightTags.operator, color: 'var(--dsh-soup-code-keyword)' },
|
|
2313
|
+
{ tag: CMHighlightTags.operatorKeyword, color: 'var(--dsh-soup-code-keyword)' },
|
|
2314
|
+
{ tag: CMHighlightTags.string, color: 'var(--dsh-soup-code-string)' },
|
|
2315
|
+
{ tag: CMHighlightTags.escape, color: 'var(--dsh-soup-code-string)' },
|
|
2316
|
+
{ tag: CMHighlightTags.regexp, color: 'var(--dsh-soup-code-string)' },
|
|
2317
|
+
{ tag: CMHighlightTags.number, color: 'var(--dsh-soup-code-number)' },
|
|
2318
|
+
{ tag: CMHighlightTags.bool, color: 'var(--dsh-soup-code-keyword)' },
|
|
2319
|
+
{ tag: CMHighlightTags.null, color: 'var(--dsh-soup-code-keyword)' },
|
|
2320
|
+
{ tag: CMHighlightTags.typeName, color: 'var(--dsh-soup-code-type)' },
|
|
2321
|
+
{ tag: CMHighlightTags.namespace, color: 'var(--dsh-soup-code-type)' },
|
|
2322
|
+
{ tag: CMHighlightTags.variableName, color: 'var(--dsh-soup-code-variable)' },
|
|
2323
|
+
{ tag: CMHighlightTags.function(CMHighlightTags.variableName), color: 'var(--dsh-soup-code-function)' },
|
|
2324
|
+
{ tag: CMHighlightTags.className, color: 'var(--dsh-soup-code-function)' },
|
|
2325
|
+
{ tag: CMHighlightTags.macroName, color: 'var(--dsh-soup-code-function)' },
|
|
2326
|
+
{ tag: CMHighlightTags.propertyName, color: 'var(--dsh-soup-code-property)' },
|
|
2327
|
+
{ tag: CMHighlightTags.punctuation, color: 'var(--dsh-soup-code-punctuation)' },
|
|
2328
|
+
{ tag: CMHighlightTags.bracket, color: 'var(--dsh-soup-code-punctuation)' },
|
|
2329
|
+
{ tag: CMHighlightTags.invalid, color: 'var(--dsh-soup-code-invalid)' },
|
|
2330
|
+
])
|
|
2331
|
+
: null
|
|
2332
|
+
|
|
2333
|
+
var PREVIEW_JSON_HIGHLIGHT_STYLE = (CMLanguage && CMLanguage.HighlightStyle && CMHighlightTags)
|
|
2334
|
+
? CMLanguage.HighlightStyle.define([
|
|
2335
|
+
{ tag: CMHighlightTags.propertyName, color: 'var(--dsh-soup-json-property)' },
|
|
2336
|
+
{ tag: CMHighlightTags.string, color: 'var(--dsh-soup-json-string)' },
|
|
2337
|
+
{ tag: CMHighlightTags.number, color: 'var(--dsh-soup-json-number)' },
|
|
2338
|
+
{ tag: CMHighlightTags.bool, color: 'var(--dsh-soup-json-literal)' },
|
|
2339
|
+
{ tag: CMHighlightTags.null, color: 'var(--dsh-soup-json-literal)' },
|
|
2340
|
+
{ tag: CMHighlightTags.punctuation, color: 'var(--dsh-soup-json-punctuation)' },
|
|
2341
|
+
{ tag: CMHighlightTags.bracket, color: 'var(--dsh-soup-json-punctuation)' },
|
|
2342
|
+
{ tag: CMHighlightTags.invalid, color: 'var(--dsh-soup-code-invalid)' },
|
|
2343
|
+
])
|
|
2344
|
+
: null
|
|
2345
|
+
|
|
2346
|
+
/** CodeMirror 6 编辑器:文本、行号、语法、高亮、光标和滚动由同一 EditorView 管理。 */
|
|
2347
|
+
function TextEditor(props) {
|
|
2348
|
+
var entry = props.entry
|
|
2349
|
+
var hostRef = React.useRef(null)
|
|
2350
|
+
var viewRef = React.useRef(null)
|
|
2351
|
+
React.useEffect(function () {
|
|
2352
|
+
if (!hostRef.current || !CMEditorView || !CMEditorState) return
|
|
2353
|
+
var EditorView = CMEditorView.EditorView
|
|
2354
|
+
var EditorState = CMEditorState.EditorState
|
|
2355
|
+
var extensions = [
|
|
2356
|
+
EditorView.updateListener.of(function (update) {
|
|
2357
|
+
if (update.docChanged) updateDraft(entry.path, update.state.doc.toString())
|
|
2358
|
+
}),
|
|
2359
|
+
]
|
|
2360
|
+
if (CMKeymap && CMKeymap.keymap) extensions.push(CMKeymap.keymap.of([
|
|
2361
|
+
{ key: 'Mod-s', run: function () { manuallySaveFile(entry.path); return true } },
|
|
2362
|
+
]))
|
|
2363
|
+
if (CMCommands) {
|
|
2364
|
+
if (CMCommands.history) extensions.push(CMCommands.history())
|
|
2365
|
+
if (CMCommands.historyKeymap && CMKeymap && CMKeymap.keymap) extensions.push(CMKeymap.keymap.of(CMCommands.historyKeymap))
|
|
2366
|
+
if (CMCommands.defaultKeymap && CMKeymap && CMKeymap.keymap) extensions.push(CMKeymap.keymap.of(CMCommands.defaultKeymap))
|
|
2367
|
+
if (CMCommands.indentWithTab && CMKeymap && CMKeymap.keymap) extensions.push(CMKeymap.keymap.of([CMCommands.indentWithTab]))
|
|
2368
|
+
}
|
|
2369
|
+
if (CMLanguage) {
|
|
2370
|
+
if (CMLanguage.bracketMatching) extensions.push(CMLanguage.bracketMatching())
|
|
2371
|
+
if (CMLanguage.indentOnInput) extensions.push(CMLanguage.indentOnInput())
|
|
2372
|
+
}
|
|
2373
|
+
if (CMEditorView.lineNumbers) extensions.push(CMEditorView.lineNumbers())
|
|
2374
|
+
if (CMEditorView.drawSelection) extensions.push(CMEditorView.drawSelection())
|
|
2375
|
+
if (CMEditorView.highlightActiveLine) extensions.push(CMEditorView.highlightActiveLine())
|
|
2376
|
+
if (CMEditorView.highlightSpecialChars) extensions.push(CMEditorView.highlightSpecialChars())
|
|
2377
|
+
var language = codeMirrorLanguage(entry.path)
|
|
2378
|
+
if (language) extensions.push(language)
|
|
2379
|
+
var ext = extOfName(entry.path)
|
|
2380
|
+
var highlightStyle = (ext === 'json' || ext === 'jsonc')
|
|
2381
|
+
? PREVIEW_JSON_HIGHLIGHT_STYLE
|
|
2382
|
+
: PREVIEW_HIGHLIGHT_STYLE
|
|
2383
|
+
if (highlightStyle) extensions.push(CMLanguage.syntaxHighlighting(highlightStyle))
|
|
2384
|
+
viewRef.current = new EditorView({
|
|
2385
|
+
state: EditorState.create({ doc: entry.draft, extensions: extensions }),
|
|
2386
|
+
parent: hostRef.current,
|
|
2387
|
+
})
|
|
2388
|
+
viewRef.current.focus()
|
|
2389
|
+
return function () {
|
|
2390
|
+
if (viewRef.current) { viewRef.current.destroy(); viewRef.current = null }
|
|
2391
|
+
}
|
|
2392
|
+
}, [entry.path])
|
|
2393
|
+
if (!CMEditorView || !CMEditorState) {
|
|
2394
|
+
return create('textarea', {
|
|
2395
|
+
className: 'dfv-editor', value: entry.draft, spellCheck: false,
|
|
2396
|
+
'aria-label': entry.name,
|
|
2397
|
+
onChange: function (e) { updateDraft(entry.path, e.target.value) },
|
|
2398
|
+
})
|
|
2399
|
+
}
|
|
2400
|
+
return create('div', { className: 'dfv-cm-editor', ref: hostRef, 'aria-label': entry.name })
|
|
2401
|
+
}
|
|
2402
|
+
|
|
1939
2403
|
// 扩展名 → Shiki 语言提示。对齐 DSH 读卡片的 LANG_BY_EXTENSION
|
|
1940
2404
|
// (packages/fs/tool-fs read-render.ts),json/md/markdown/html/htm 除外——
|
|
1941
2405
|
// 它们走上方专属渲染器。
|
|
@@ -2490,24 +2954,26 @@ window.__ModuleLoader__.load({
|
|
|
2490
2954
|
if (entry.kind === 'pdf') {
|
|
2491
2955
|
return create(PdfPreview, { data: entry.data })
|
|
2492
2956
|
}
|
|
2493
|
-
|
|
2957
|
+
if (entry.editing) return create(TextEditor, { entry: entry })
|
|
2958
|
+
// 文本类:按扩展名选预览渲染器;编辑态返回预览时展示当前草稿
|
|
2959
|
+
var text = entry.dirty ? entry.draft : entry.content
|
|
2494
2960
|
var pk = previewKind(entry.path)
|
|
2495
2961
|
var view = null
|
|
2496
|
-
if (pk === 'markdown') view = create(MarkdownPreview, { text:
|
|
2497
|
-
else if (pk === 'html') view = create(HtmlPreview, { text:
|
|
2498
|
-
else if (pk === 'json') view = create(JsonPreview, { text:
|
|
2499
|
-
else if (pk === 'notebook') view = create(NotebookPreview, { text:
|
|
2500
|
-
else if (pk === 'csv' || pk === 'tsv') view = create(DelimitedPreview, { text:
|
|
2962
|
+
if (pk === 'markdown') view = create(MarkdownPreview, { text: text, baseDir: parentOf(entry.path) })
|
|
2963
|
+
else if (pk === 'html') view = create(HtmlPreview, { text: text, title: entry.name })
|
|
2964
|
+
else if (pk === 'json') view = create(CodeWithLines, { text: text }, create(JsonPreview, { text: text }))
|
|
2965
|
+
else if (pk === 'notebook') view = create(NotebookPreview, { text: text })
|
|
2966
|
+
else if (pk === 'csv' || pk === 'tsv') view = create(DelimitedPreview, { text: text, delim: pk === 'tsv' ? '\t' : ',' })
|
|
2501
2967
|
else {
|
|
2502
2968
|
// 代码文件:DSH CodeBlock(Shiki 高亮,语法包懒加载);无高亮条件回退纯文本
|
|
2503
2969
|
// 都包在 CodeWithLines 里加左侧行号
|
|
2504
2970
|
var lang = Object.prototype.hasOwnProperty.call(CODE_LANG_BY_EXT, extOfName(entry.path))
|
|
2505
2971
|
? CODE_LANG_BY_EXT[extOfName(entry.path)]
|
|
2506
2972
|
: undefined
|
|
2507
|
-
var codeEl = (CodeBlock && lang &&
|
|
2508
|
-
? create(CodeBlock, { code:
|
|
2509
|
-
: create('pre', { className: 'dfv-code' },
|
|
2510
|
-
view = create(CodeWithLines, { text:
|
|
2973
|
+
var codeEl = (CodeBlock && lang && text.length <= CODE_HIGHLIGHT_MAX_CHARS)
|
|
2974
|
+
? create(CodeBlock, { code: text, lang: lang })
|
|
2975
|
+
: create('pre', { className: 'dfv-code' }, text)
|
|
2976
|
+
view = create(CodeWithLines, { text: text }, codeEl)
|
|
2511
2977
|
}
|
|
2512
2978
|
return create(React.Fragment, null,
|
|
2513
2979
|
entry.truncated ? create('div', { className: 'dfv-cap' },
|
|
@@ -2516,6 +2982,53 @@ window.__ModuleLoader__.load({
|
|
|
2516
2982
|
)
|
|
2517
2983
|
}
|
|
2518
2984
|
|
|
2985
|
+
function FileToolbar(props) {
|
|
2986
|
+
var active = props.entry
|
|
2987
|
+
var buttons = []
|
|
2988
|
+
if (active.loaded && active.kind === 'text' && isEditableTextFile(active.path)) {
|
|
2989
|
+
if (!active.editing) {
|
|
2990
|
+
buttons.push(create('button', {
|
|
2991
|
+
type: 'button',
|
|
2992
|
+
className: 'dfv-btn',
|
|
2993
|
+
title: T('files.editTitle'),
|
|
2994
|
+
disabled: active.truncated,
|
|
2995
|
+
onClick: function () { startEditing(active.path) },
|
|
2996
|
+
},
|
|
2997
|
+
create('span', { className: 'expl-icon', dangerouslySetInnerHTML: { __html: FILE_ICON_EDIT } }),
|
|
2998
|
+
T('files.edit'),
|
|
2999
|
+
))
|
|
3000
|
+
} else {
|
|
3001
|
+
buttons.push(create('button', {
|
|
3002
|
+
type: 'button',
|
|
3003
|
+
className: 'dfv-btn',
|
|
3004
|
+
title: T('files.previewTitle'),
|
|
3005
|
+
onClick: function () { stopEditing(active.path) },
|
|
3006
|
+
},
|
|
3007
|
+
create('span', { className: 'expl-icon', dangerouslySetInnerHTML: { __html: FILE_ICON_PREVIEW } }),
|
|
3008
|
+
T('files.preview'),
|
|
3009
|
+
))
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
buttons.push(active.loaded ? create('button', {
|
|
3013
|
+
type: 'button',
|
|
3014
|
+
className: 'dfv-btn',
|
|
3015
|
+
title: T('files.reloadTitle'),
|
|
3016
|
+
onClick: function () { reloadFile(active.path) },
|
|
3017
|
+
},
|
|
3018
|
+
create('span', { className: 'expl-icon', dangerouslySetInnerHTML: { __html: ICON_REFRESH } }),
|
|
3019
|
+
T('files.reload'),
|
|
3020
|
+
) : null)
|
|
3021
|
+
return create(React.Fragment, null,
|
|
3022
|
+
create('span', { className: 'dfv-toolbar-path', title: active.path }, active.path),
|
|
3023
|
+
typeof active.size === 'number' && active.size > 0 ? create('span', null, formatBytes(active.size)) : null,
|
|
3024
|
+
active.saving ? create('span', { className: 'dfv-status' }, T('files.saving')) : null,
|
|
3025
|
+
active.justSaved && !active.saving && !active.dirty ? create('span', { className: 'dfv-status dfv-saved' }, T('files.saved')) : null,
|
|
3026
|
+
active.conflict ? create('span', { className: 'dfv-status', title: T('files.conflict') }, T('files.conflictShort')) : null,
|
|
3027
|
+
active.saveError ? create('span', { className: 'dfv-save-error', title: active.saveError }, active.saveError) : null,
|
|
3028
|
+
buttons,
|
|
3029
|
+
)
|
|
3030
|
+
}
|
|
3031
|
+
|
|
2519
3032
|
/**
|
|
2520
3033
|
* 纯阅读模式标记:在会话根(含 composerSeat 的最近祖先,即 DSH
|
|
2521
3034
|
* ConversationRoot 的 root 节点)上打 data-dsh-soup-preview,由上面
|
|
@@ -2526,6 +3039,7 @@ window.__ModuleLoader__.load({
|
|
|
2526
3039
|
React.useEffect(function () {
|
|
2527
3040
|
var observer = null
|
|
2528
3041
|
var marked = null
|
|
3042
|
+
document.body.classList.add('dsh-soup-preview-active')
|
|
2529
3043
|
function apply() {
|
|
2530
3044
|
var el = rootRef.current
|
|
2531
3045
|
if (!el) return
|
|
@@ -2545,6 +3059,7 @@ window.__ModuleLoader__.load({
|
|
|
2545
3059
|
return function () {
|
|
2546
3060
|
if (observer) observer.disconnect()
|
|
2547
3061
|
if (marked) marked.removeAttribute('data-dsh-soup-preview')
|
|
3062
|
+
document.body.classList.remove('dsh-soup-preview-active')
|
|
2548
3063
|
marked = null
|
|
2549
3064
|
}
|
|
2550
3065
|
}, [])
|
|
@@ -2565,6 +3080,7 @@ window.__ModuleLoader__.load({
|
|
|
2565
3080
|
title: entry.path,
|
|
2566
3081
|
onClick: function () { setActiveFile(entry.path) },
|
|
2567
3082
|
},
|
|
3083
|
+
entry.dirty ? create('span', { className: 'dfv-dirty', title: T('files.unsaved') }) : null,
|
|
2568
3084
|
create('span', { className: 'dfv-tab-name' }, entry.name),
|
|
2569
3085
|
create('span', {
|
|
2570
3086
|
className: 'dfv-close',
|
|
@@ -2584,17 +3100,7 @@ window.__ModuleLoader__.load({
|
|
|
2584
3100
|
)
|
|
2585
3101
|
} else {
|
|
2586
3102
|
toolbar = create('div', { className: 'dfv-toolbar' },
|
|
2587
|
-
create(
|
|
2588
|
-
typeof active.size === 'number' && active.size > 0 ? create('span', null, formatBytes(active.size)) : null,
|
|
2589
|
-
active.loaded ? create('button', {
|
|
2590
|
-
type: 'button',
|
|
2591
|
-
className: 'dfv-btn',
|
|
2592
|
-
title: T('files.reloadTitle'),
|
|
2593
|
-
onClick: function () { reloadFile(active.path) },
|
|
2594
|
-
},
|
|
2595
|
-
create('span', { className: 'expl-icon', dangerouslySetInnerHTML: { __html: ICON_REFRESH } }),
|
|
2596
|
-
T('files.reload'),
|
|
2597
|
-
) : null,
|
|
3103
|
+
create(FileToolbar, { entry: active }),
|
|
2598
3104
|
create('button', {
|
|
2599
3105
|
type: 'button',
|
|
2600
3106
|
className: 'dfv-btn',
|
|
@@ -2626,6 +3132,7 @@ window.__ModuleLoader__.load({
|
|
|
2626
3132
|
if (!active) return null
|
|
2627
3133
|
var close = function () { setFiles({ overlay: false, overlayMax: false, overlayReturn: null }) }
|
|
2628
3134
|
var closeAndCloseTab = function () {
|
|
3135
|
+
if (active.dirty && typeof window !== 'undefined' && !window.confirm(T('files.unsavedConfirm'))) return
|
|
2629
3136
|
close()
|
|
2630
3137
|
closeFileTab(active.path)
|
|
2631
3138
|
}
|
|
@@ -2643,17 +3150,7 @@ window.__ModuleLoader__.load({
|
|
|
2643
3150
|
},
|
|
2644
3151
|
create('div', { className: 'dfv-overlay-panel' + (files.overlayMax ? ' dfv-overlay-max' : '') },
|
|
2645
3152
|
create('div', { className: 'dfv-toolbar' },
|
|
2646
|
-
create(
|
|
2647
|
-
typeof active.size === 'number' && active.size > 0 ? create('span', null, formatBytes(active.size)) : null,
|
|
2648
|
-
active.loaded ? create('button', {
|
|
2649
|
-
type: 'button',
|
|
2650
|
-
className: 'dfv-btn',
|
|
2651
|
-
title: T('files.reloadTitle'),
|
|
2652
|
-
onClick: function () { reloadFile(active.path) },
|
|
2653
|
-
},
|
|
2654
|
-
create('span', { className: 'expl-icon', dangerouslySetInnerHTML: { __html: ICON_REFRESH } }),
|
|
2655
|
-
T('files.reload'),
|
|
2656
|
-
) : null,
|
|
3153
|
+
create(FileToolbar, { entry: active }),
|
|
2657
3154
|
create('button', {
|
|
2658
3155
|
type: 'button', className: 'dfv-overlay-close',
|
|
2659
3156
|
title: files.overlayMax ? '还原' : '最大化', 'aria-label': files.overlayMax ? '还原' : '最大化',
|
|
@@ -3092,6 +3589,57 @@ window.__ModuleLoader__.load({
|
|
|
3092
3589
|
document.addEventListener('click', onOfficialExpand, true)
|
|
3093
3590
|
disposers.push(function () { document.removeEventListener('click', onOfficialExpand, true) })
|
|
3094
3591
|
}
|
|
3592
|
+
// 官方产物链接桥接:包装 sidebarRight 服务(见 parseFileAddress 处注释)。
|
|
3593
|
+
// 优先走可选注入的 ctx.sidebarRight(框架保证就绪),并保留
|
|
3594
|
+
// ctx.get / ctx.reflect.get 多条解析路径与短轮询兜底。
|
|
3595
|
+
var bridgePatched = false
|
|
3596
|
+
function resolveSidebarRight() {
|
|
3597
|
+
try { if (ctx.sidebarRight) return ctx.sidebarRight } catch (e) {}
|
|
3598
|
+
try {
|
|
3599
|
+
if (typeof ctx.get === 'function') {
|
|
3600
|
+
var viaGet = ctx.get('sidebarRight', false)
|
|
3601
|
+
if (viaGet) return viaGet
|
|
3602
|
+
}
|
|
3603
|
+
} catch (e) {}
|
|
3604
|
+
try {
|
|
3605
|
+
if (ctx.reflect && typeof ctx.reflect.get === 'function') {
|
|
3606
|
+
var viaReflect = ctx.reflect.get('sidebarRight', false)
|
|
3607
|
+
if (viaReflect) return viaReflect
|
|
3608
|
+
}
|
|
3609
|
+
} catch (e) {}
|
|
3610
|
+
return null
|
|
3611
|
+
}
|
|
3612
|
+
function patchSidebarRightBridge() {
|
|
3613
|
+
if (bridgePatched) return true
|
|
3614
|
+
var service = resolveSidebarRight()
|
|
3615
|
+
if (!service || typeof service.openResource !== 'function') return false
|
|
3616
|
+
if (!service.__dshSoupFileBridge) {
|
|
3617
|
+
var origResource = service.openResource
|
|
3618
|
+
var origResourceIn = service.openResourceIn
|
|
3619
|
+
service.openResource = function (address, options) {
|
|
3620
|
+
if (fileAddressMatches(address, null)) { openFileAddressInSoup(address, null); return }
|
|
3621
|
+
return origResource.call(service, address, options)
|
|
3622
|
+
}
|
|
3623
|
+
if (typeof origResourceIn === 'function') {
|
|
3624
|
+
service.openResourceIn = function (sessionId, address, options) {
|
|
3625
|
+
if (fileAddressMatches(address, sessionId)) { openFileAddressInSoup(address, sessionId); return }
|
|
3626
|
+
return origResourceIn.call(service, sessionId, address, options)
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3629
|
+
try { Object.defineProperty(service, '__dshSoupFileBridge', { value: true }) }
|
|
3630
|
+
catch (e) { service.__dshSoupFileBridge = true }
|
|
3631
|
+
}
|
|
3632
|
+
bridgePatched = true
|
|
3633
|
+
try { if (typeof window !== 'undefined') window.__DSH_SOUP_FILE_BRIDGE = true } catch (e) {}
|
|
3634
|
+
return true
|
|
3635
|
+
}
|
|
3636
|
+
patchSidebarRightBridge()
|
|
3637
|
+
var bridgeTries = 0
|
|
3638
|
+
var bridgeTimer = setInterval(function () {
|
|
3639
|
+
bridgeTries += 1
|
|
3640
|
+
if (patchSidebarRightBridge() || bridgeTries >= 10) clearInterval(bridgeTimer)
|
|
3641
|
+
}, 1000)
|
|
3642
|
+
disposers.push(function () { clearInterval(bridgeTimer) })
|
|
3095
3643
|
// i18n:注册双语词典;模块级 T 供组件外回调(菜单/错误横幅)取当前语言文案。
|
|
3096
3644
|
if (ctx.locale && typeof ctx.locale.register === 'function') {
|
|
3097
3645
|
ctx.effect(function () { return ctx.locale.register(NS, DICT) }, 'dsh-soup: dictionaries')
|