@lyhue1991/dsh-soup 0.5.2 → 0.5.3
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 +34406 -0
- package/lib/client.entry.js +2 -0
- package/lib/client.js +454 -41
- 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);}' +
|
|
@@ -310,6 +365,22 @@ window.__ModuleLoader__.load({
|
|
|
310
365
|
'files.imageTooLarge': '图片过大({size},上限 {limit})',
|
|
311
366
|
'files.pdfTooLarge': 'PDF 过大({size},上限 {limit}),请用系统打开',
|
|
312
367
|
'files.truncated': '文件超过 8 MB,仅预览前 8 MB;完整内容请用系统编辑器打开。',
|
|
368
|
+
'files.edit': '编辑',
|
|
369
|
+
'files.editTitle': '编辑当前文本文件',
|
|
370
|
+
'files.preview': '预览',
|
|
371
|
+
'files.previewTitle': '返回预览模式',
|
|
372
|
+
'files.save': '保存',
|
|
373
|
+
'files.saveTitle': '保存修改(⌘S / Ctrl+S)',
|
|
374
|
+
'files.saving': '保存中…',
|
|
375
|
+
'files.saved': '已保存',
|
|
376
|
+
'files.cancelEdit': '放弃修改',
|
|
377
|
+
'files.unsaved': '有未保存修改',
|
|
378
|
+
'files.unsavedConfirm': '当前文件有未保存修改,确认放弃?',
|
|
379
|
+
'files.conflictShort': '文件在外部被修改',
|
|
380
|
+
'files.conflict': '其他程序修改了磁盘文件;当前草稿仍保留在编辑器中,下次自动保存会覆盖磁盘版本。',
|
|
381
|
+
'files.overwriteConfirm': '文件已被其他程序修改。要用当前编辑内容覆盖磁盘上的最新版本吗?',
|
|
382
|
+
'files.notEditable': '当前文件类型不支持编辑',
|
|
383
|
+
'files.truncatedEdit': '文件超过 8 MB,已截断,不能编辑保存。',
|
|
313
384
|
'files.jsonFail': 'JSON 解析失败,按纯文本显示',
|
|
314
385
|
'files.nbFail': 'ipynb 解析失败,按纯文本显示',
|
|
315
386
|
'files.nbEmpty': '(空笔记本)',
|
|
@@ -381,6 +452,22 @@ window.__ModuleLoader__.load({
|
|
|
381
452
|
'files.imageTooLarge': 'Image too large ({size}, limit {limit})',
|
|
382
453
|
'files.pdfTooLarge': 'PDF too large ({size}, limit {limit}) — open with the system viewer',
|
|
383
454
|
'files.truncated': 'File exceeds 8 MB — showing the first 8 MB; open in a system editor for the full content.',
|
|
455
|
+
'files.edit': 'Edit',
|
|
456
|
+
'files.editTitle': 'Edit this text file',
|
|
457
|
+
'files.preview': 'Preview',
|
|
458
|
+
'files.previewTitle': 'Back to preview mode',
|
|
459
|
+
'files.save': 'Save',
|
|
460
|
+
'files.saveTitle': 'Save changes (⌘S / Ctrl+S)',
|
|
461
|
+
'files.saving': 'Saving…',
|
|
462
|
+
'files.saved': 'Saved',
|
|
463
|
+
'files.cancelEdit': 'Discard changes',
|
|
464
|
+
'files.unsaved': 'Unsaved changes',
|
|
465
|
+
'files.unsavedConfirm': 'This file has unsaved changes. Discard them?',
|
|
466
|
+
'files.conflictShort': 'Changed outside the editor',
|
|
467
|
+
'files.conflict': 'Another program changed the file on disk. Your draft is preserved here, and the next auto-save will overwrite the disk version.',
|
|
468
|
+
'files.overwriteConfirm': 'The file was changed externally. Overwrite the latest disk version with your current editor content?',
|
|
469
|
+
'files.notEditable': 'Editing is not supported for this file type',
|
|
470
|
+
'files.truncatedEdit': 'The file exceeds 8 MB and was truncated, so it cannot be safely edited.',
|
|
384
471
|
'files.jsonFail': 'JSON parse failed — shown as plain text',
|
|
385
472
|
'files.nbFail': 'ipynb parse failed — shown as plain text',
|
|
386
473
|
'files.nbEmpty': '(empty notebook)',
|
|
@@ -417,6 +504,11 @@ window.__ModuleLoader__.load({
|
|
|
417
504
|
// while a blank conversation is mounted. Keep late async responses from an
|
|
418
505
|
// older caller from overwriting the current session's shared store.
|
|
419
506
|
var sessionTrackGeneration = 0
|
|
507
|
+
var autoSaveTimers = new Map()
|
|
508
|
+
var autoSaveSavedTimers = new Map()
|
|
509
|
+
var manualSavePaths = new Set()
|
|
510
|
+
var AUTO_SAVE_DELAY_MS = 5000
|
|
511
|
+
var SAVED_INDICATOR_MS = 1500
|
|
420
512
|
|
|
421
513
|
// 目录展开状态按 cwd 隔离。刷新/删除/改名/移动后先重拉根目录,
|
|
422
514
|
// 再按这些路径递归重拉仍存在的已展开目录;切换项目时才清空。
|
|
@@ -620,7 +712,21 @@ window.__ModuleLoader__.load({
|
|
|
620
712
|
if (!res || !res.ok) return
|
|
621
713
|
entry.kind = res.kind
|
|
622
714
|
entry.size = res.size || 0
|
|
623
|
-
if (res.kind === 'text') {
|
|
715
|
+
if (res.kind === 'text') {
|
|
716
|
+
entry.truncated = !!res.truncated
|
|
717
|
+
if (!entry.dirty && !entry.editing) {
|
|
718
|
+
entry.content = res.content
|
|
719
|
+
entry.baseContent = res.content
|
|
720
|
+
entry.draft = res.content
|
|
721
|
+
entry.baseMtime = res.mtime
|
|
722
|
+
entry.baseSize = res.size
|
|
723
|
+
} else {
|
|
724
|
+
entry.conflict = true
|
|
725
|
+
entry.diskMtime = res.mtime
|
|
726
|
+
entry.diskSize = res.size
|
|
727
|
+
scheduleAutoSave(entry.path)
|
|
728
|
+
}
|
|
729
|
+
}
|
|
624
730
|
else if (res.kind === 'image') { entry.dataUrl = 'data:' + res.mime + ';base64,' + res.data }
|
|
625
731
|
else if (res.kind === 'pdf') { entry.data = res.data }
|
|
626
732
|
setFiles({ list: state.files.list.slice() })
|
|
@@ -672,7 +778,12 @@ window.__ModuleLoader__.load({
|
|
|
672
778
|
}
|
|
673
779
|
for (var f = 0; f < dirtyFiles.length; f++) {
|
|
674
780
|
var entry = findFileEntry(dirtyFiles[f])
|
|
675
|
-
if (entry
|
|
781
|
+
if (!entry || !entry.loaded) continue
|
|
782
|
+
var currentSignature = mtimes[dirtyFiles[f]]
|
|
783
|
+
if (currentSignature
|
|
784
|
+
&& currentSignature.m === entry.baseMtime
|
|
785
|
+
&& currentSignature.s === entry.baseSize) continue
|
|
786
|
+
await silentReloadEntry(entry)
|
|
676
787
|
}
|
|
677
788
|
return true
|
|
678
789
|
} finally {
|
|
@@ -1844,6 +1955,9 @@ window.__ModuleLoader__.load({
|
|
|
1844
1955
|
loading: true, loaded: false, error: '',
|
|
1845
1956
|
kind: null, content: '', dataUrl: '', data: '',
|
|
1846
1957
|
truncated: false, size: 0,
|
|
1958
|
+
baseContent: '', baseMtime: undefined, baseSize: undefined,
|
|
1959
|
+
draft: '', dirty: false, editing: false, saving: false, justSaved: false,
|
|
1960
|
+
saveError: '', conflict: false,
|
|
1847
1961
|
}
|
|
1848
1962
|
// FIFO:最多同时 5 个预览,新开的挤掉最早打开的
|
|
1849
1963
|
var list = state.files.list.slice()
|
|
@@ -1874,7 +1988,21 @@ window.__ModuleLoader__.load({
|
|
|
1874
1988
|
cur.loaded = true
|
|
1875
1989
|
cur.kind = res.kind
|
|
1876
1990
|
cur.size = res.size || 0
|
|
1877
|
-
if (res.kind === 'text') {
|
|
1991
|
+
if (res.kind === 'text') {
|
|
1992
|
+
cur.truncated = !!res.truncated
|
|
1993
|
+
if (!cur.dirty && !cur.editing) {
|
|
1994
|
+
cur.content = res.content
|
|
1995
|
+
cur.baseContent = res.content
|
|
1996
|
+
cur.draft = res.content
|
|
1997
|
+
cur.baseMtime = res.mtime
|
|
1998
|
+
cur.baseSize = res.size
|
|
1999
|
+
} else {
|
|
2000
|
+
cur.conflict = true
|
|
2001
|
+
cur.diskMtime = res.mtime
|
|
2002
|
+
cur.diskSize = res.size
|
|
2003
|
+
scheduleAutoSave(path)
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
1878
2006
|
else if (res.kind === 'image') { cur.dataUrl = 'data:' + res.mime + ';base64,' + res.data }
|
|
1879
2007
|
else if (res.kind === 'pdf') { cur.data = res.data }
|
|
1880
2008
|
}
|
|
@@ -1889,11 +2017,145 @@ window.__ModuleLoader__.load({
|
|
|
1889
2017
|
await openFileInTab(path)
|
|
1890
2018
|
}
|
|
1891
2019
|
|
|
2020
|
+
function isEditableTextFile(path) {
|
|
2021
|
+
var kind = previewKind(path)
|
|
2022
|
+
var ext = extOfName(path)
|
|
2023
|
+
return kind === 'markdown' || kind === 'html' || kind === 'json'
|
|
2024
|
+
|| Object.prototype.hasOwnProperty.call(CODE_LANG_BY_EXT, ext)
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
function startEditing(path) {
|
|
2028
|
+
var entry = findFileEntry(path)
|
|
2029
|
+
if (!entry || !entry.loaded || entry.kind !== 'text') return
|
|
2030
|
+
if (!isEditableTextFile(path)) {
|
|
2031
|
+
entry.saveError = T('files.notEditable')
|
|
2032
|
+
setFiles({ list: state.files.list.slice() })
|
|
2033
|
+
return
|
|
2034
|
+
}
|
|
2035
|
+
if (entry.truncated) {
|
|
2036
|
+
entry.saveError = T('files.truncatedEdit')
|
|
2037
|
+
setFiles({ list: state.files.list.slice() })
|
|
2038
|
+
return
|
|
2039
|
+
}
|
|
2040
|
+
if (!entry.editing) {
|
|
2041
|
+
entry.draft = entry.dirty ? entry.draft : entry.content
|
|
2042
|
+
entry.editing = true
|
|
2043
|
+
}
|
|
2044
|
+
entry.saveError = ''
|
|
2045
|
+
setFiles({ list: state.files.list.slice() })
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
function scheduleAutoSave(path) {
|
|
2049
|
+
clearTimeout(autoSaveTimers.get(path))
|
|
2050
|
+
autoSaveTimers.set(path, setTimeout(function () {
|
|
2051
|
+
autoSaveTimers.delete(path)
|
|
2052
|
+
saveActiveFile(path)
|
|
2053
|
+
}, AUTO_SAVE_DELAY_MS))
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
function showSavedIndicator(path) {
|
|
2057
|
+
clearTimeout(autoSaveSavedTimers.get(path))
|
|
2058
|
+
autoSaveSavedTimers.set(path, setTimeout(function () {
|
|
2059
|
+
autoSaveSavedTimers.delete(path)
|
|
2060
|
+
var entry = findFileEntry(path)
|
|
2061
|
+
if (!entry || entry.saving || entry.dirty) return
|
|
2062
|
+
entry.justSaved = false
|
|
2063
|
+
setFiles({ list: state.files.list.slice() })
|
|
2064
|
+
}, SAVED_INDICATOR_MS))
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
function manuallySaveFile(path) {
|
|
2068
|
+
manualSavePaths.add(path)
|
|
2069
|
+
saveActiveFile(path)
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
function stopEditing(path) {
|
|
2073
|
+
var entry = findFileEntry(path)
|
|
2074
|
+
if (!entry || !entry.editing) return
|
|
2075
|
+
clearTimeout(autoSaveTimers.get(path))
|
|
2076
|
+
autoSaveTimers.delete(path)
|
|
2077
|
+
clearTimeout(autoSaveSavedTimers.get(path))
|
|
2078
|
+
autoSaveSavedTimers.delete(path)
|
|
2079
|
+
if (entry.dirty) {
|
|
2080
|
+
if (entry.saving) entry.saveOnExit = true
|
|
2081
|
+
else saveActiveFile(path)
|
|
2082
|
+
}
|
|
2083
|
+
entry.editing = false
|
|
2084
|
+
setFiles({ list: state.files.list.slice() })
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
function updateDraft(path, value) {
|
|
2088
|
+
var entry = findFileEntry(path)
|
|
2089
|
+
if (!entry || !entry.editing) return
|
|
2090
|
+
entry.draft = value
|
|
2091
|
+
entry.dirty = value !== entry.baseContent
|
|
2092
|
+
setFiles({ list: state.files.list.slice() })
|
|
2093
|
+
scheduleAutoSave(path)
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
async function saveActiveFile(path) {
|
|
2097
|
+
var entry = findFileEntry(path)
|
|
2098
|
+
if (!entry || (!entry.editing && !entry.saveOnExit) || entry.saving || entry.truncated) return
|
|
2099
|
+
if (!entry.dirty) return
|
|
2100
|
+
entry.saving = true
|
|
2101
|
+
entry.saveError = ''
|
|
2102
|
+
setFiles({ list: state.files.list.slice() })
|
|
2103
|
+
if (entry.conflict) {
|
|
2104
|
+
entry.baseMtime = entry.diskMtime
|
|
2105
|
+
entry.baseSize = entry.diskSize
|
|
2106
|
+
entry.conflict = false
|
|
2107
|
+
}
|
|
2108
|
+
var savedDraft = entry.draft
|
|
2109
|
+
var wasManualSave = manualSavePaths.has(path)
|
|
2110
|
+
manualSavePaths.delete(path)
|
|
2111
|
+
var res = await rpc('write', {
|
|
2112
|
+
path: path,
|
|
2113
|
+
content: entry.draft,
|
|
2114
|
+
expectedMtime: entry.baseMtime,
|
|
2115
|
+
expectedSize: entry.baseSize,
|
|
2116
|
+
sessionId: activeSessionId,
|
|
2117
|
+
})
|
|
2118
|
+
var cur = findFileEntry(path)
|
|
2119
|
+
if (!cur) return
|
|
2120
|
+
cur.saving = false
|
|
2121
|
+
if (!res || !res.ok) {
|
|
2122
|
+
cur.saveError = (res && res.error) || T('files.saveTitle')
|
|
2123
|
+
if (res && res.conflict) {
|
|
2124
|
+
cur.conflict = true
|
|
2125
|
+
cur.diskMtime = res.mtime
|
|
2126
|
+
cur.diskSize = res.size
|
|
2127
|
+
}
|
|
2128
|
+
if (cur.editing || cur.saveOnExit) scheduleAutoSave(path)
|
|
2129
|
+
setFiles({ list: state.files.list.slice() })
|
|
2130
|
+
return
|
|
2131
|
+
}
|
|
2132
|
+
cur.content = savedDraft
|
|
2133
|
+
cur.baseContent = savedDraft
|
|
2134
|
+
cur.dirty = cur.draft !== savedDraft
|
|
2135
|
+
if (!cur.dirty) cur.saveOnExit = false
|
|
2136
|
+
else if (cur.editing || cur.saveOnExit) scheduleAutoSave(path)
|
|
2137
|
+
cur.conflict = false
|
|
2138
|
+
cur.saveError = ''
|
|
2139
|
+
cur.justSaved = wasManualSave
|
|
2140
|
+
cur.size = res.size
|
|
2141
|
+
cur.baseMtime = res.mtime
|
|
2142
|
+
cur.baseSize = res.size
|
|
2143
|
+
setFiles({ list: state.files.list.slice() })
|
|
2144
|
+
refresh()
|
|
2145
|
+
if (wasManualSave) showSavedIndicator(path)
|
|
2146
|
+
}
|
|
2147
|
+
|
|
1892
2148
|
function closeFileTab(path) {
|
|
1893
2149
|
var list = state.files.list
|
|
1894
2150
|
var idx = -1
|
|
1895
2151
|
for (var i = 0; i < list.length; i++) if (list[i].path === path) { idx = i; break }
|
|
1896
2152
|
if (idx < 0) return
|
|
2153
|
+
clearTimeout(autoSaveTimers.get(path))
|
|
2154
|
+
autoSaveTimers.delete(path)
|
|
2155
|
+
clearTimeout(autoSaveSavedTimers.get(path))
|
|
2156
|
+
autoSaveSavedTimers.delete(path)
|
|
2157
|
+
var closing = list[idx]
|
|
2158
|
+
if (closing.dirty && typeof window !== 'undefined' && !window.confirm(T('files.unsavedConfirm'))) return
|
|
1897
2159
|
var next = list.slice(0, idx).concat(list.slice(idx + 1))
|
|
1898
2160
|
var active = state.files.active
|
|
1899
2161
|
if (active === path) {
|
|
@@ -1936,6 +2198,126 @@ window.__ModuleLoader__.load({
|
|
|
1936
2198
|
return 'text'
|
|
1937
2199
|
}
|
|
1938
2200
|
|
|
2201
|
+
function codeMirrorLanguage(path) {
|
|
2202
|
+
var ext = extOfName(path)
|
|
2203
|
+
var factory = ext === 'py' ? CMLangs.python && CMLangs.python.python
|
|
2204
|
+
: ext === 'js' || ext === 'mjs' || ext === 'cjs' ? CMLangs.javascript && CMLangs.javascript.javascript
|
|
2205
|
+
: ext === 'jsx' ? CMLangs.javascript && CMLangs.javascript.javascript
|
|
2206
|
+
: ext === 'ts' || ext === 'mts' || ext === 'cts' ? CMLangs.javascript && CMLangs.javascript.javascript
|
|
2207
|
+
: ext === 'tsx' ? CMLangs.javascript && CMLangs.javascript.javascript
|
|
2208
|
+
: ext === 'json' || ext === 'jsonc' ? CMLangs.json && CMLangs.json.json
|
|
2209
|
+
: ext === 'md' || ext === 'markdown' || ext === 'mdx' ? CMLangs.markdown && CMLangs.markdown.markdown
|
|
2210
|
+
: ext === 'html' || ext === 'htm' ? CMLangs.html && CMLangs.html.html
|
|
2211
|
+
: ext === 'css' || ext === 'scss' || ext === 'less' ? CMLangs.css && CMLangs.css.css
|
|
2212
|
+
: ext === 'sql' ? CMLangs.sql && CMLangs.sql.sql
|
|
2213
|
+
: ext === 'xml' ? CMLangs.xml && CMLangs.xml.xml
|
|
2214
|
+
: null
|
|
2215
|
+
if (!factory) return null
|
|
2216
|
+
try {
|
|
2217
|
+
if (ext === 'js' || ext === 'mjs' || ext === 'cjs' || ext === 'jsx') return factory({ jsx: true })
|
|
2218
|
+
if (ext === 'ts' || ext === 'mts' || ext === 'cts' || ext === 'tsx') return factory({ typescript: true, jsx: ext === 'tsx' })
|
|
2219
|
+
return factory()
|
|
2220
|
+
} catch (err) { return null }
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
var PREVIEW_HIGHLIGHT_STYLE = (CMLanguage && CMLanguage.HighlightStyle && CMHighlightTags)
|
|
2224
|
+
? CMLanguage.HighlightStyle.define([
|
|
2225
|
+
{ tag: CMHighlightTags.comment, color: 'var(--dsh-soup-code-comment)' },
|
|
2226
|
+
{ tag: CMHighlightTags.keyword, color: 'var(--dsh-soup-code-keyword)' },
|
|
2227
|
+
{ tag: CMHighlightTags.controlKeyword, color: 'var(--dsh-soup-code-keyword)' },
|
|
2228
|
+
{ tag: CMHighlightTags.definitionKeyword, color: 'var(--dsh-soup-code-keyword)' },
|
|
2229
|
+
{ tag: CMHighlightTags.moduleKeyword, color: 'var(--dsh-soup-code-keyword)' },
|
|
2230
|
+
{ tag: CMHighlightTags.operator, color: 'var(--dsh-soup-code-keyword)' },
|
|
2231
|
+
{ tag: CMHighlightTags.operatorKeyword, color: 'var(--dsh-soup-code-keyword)' },
|
|
2232
|
+
{ tag: CMHighlightTags.string, color: 'var(--dsh-soup-code-string)' },
|
|
2233
|
+
{ tag: CMHighlightTags.escape, color: 'var(--dsh-soup-code-string)' },
|
|
2234
|
+
{ tag: CMHighlightTags.regexp, color: 'var(--dsh-soup-code-string)' },
|
|
2235
|
+
{ tag: CMHighlightTags.number, color: 'var(--dsh-soup-code-number)' },
|
|
2236
|
+
{ tag: CMHighlightTags.bool, color: 'var(--dsh-soup-code-keyword)' },
|
|
2237
|
+
{ tag: CMHighlightTags.null, color: 'var(--dsh-soup-code-keyword)' },
|
|
2238
|
+
{ tag: CMHighlightTags.typeName, color: 'var(--dsh-soup-code-type)' },
|
|
2239
|
+
{ tag: CMHighlightTags.namespace, color: 'var(--dsh-soup-code-type)' },
|
|
2240
|
+
{ tag: CMHighlightTags.variableName, color: 'var(--dsh-soup-code-variable)' },
|
|
2241
|
+
{ tag: CMHighlightTags.function(CMHighlightTags.variableName), color: 'var(--dsh-soup-code-function)' },
|
|
2242
|
+
{ tag: CMHighlightTags.className, color: 'var(--dsh-soup-code-function)' },
|
|
2243
|
+
{ tag: CMHighlightTags.macroName, color: 'var(--dsh-soup-code-function)' },
|
|
2244
|
+
{ tag: CMHighlightTags.propertyName, color: 'var(--dsh-soup-code-property)' },
|
|
2245
|
+
{ tag: CMHighlightTags.punctuation, color: 'var(--dsh-soup-code-punctuation)' },
|
|
2246
|
+
{ tag: CMHighlightTags.bracket, color: 'var(--dsh-soup-code-punctuation)' },
|
|
2247
|
+
{ tag: CMHighlightTags.invalid, color: 'var(--dsh-soup-code-invalid)' },
|
|
2248
|
+
])
|
|
2249
|
+
: null
|
|
2250
|
+
|
|
2251
|
+
var PREVIEW_JSON_HIGHLIGHT_STYLE = (CMLanguage && CMLanguage.HighlightStyle && CMHighlightTags)
|
|
2252
|
+
? CMLanguage.HighlightStyle.define([
|
|
2253
|
+
{ tag: CMHighlightTags.propertyName, color: 'var(--dsh-soup-json-property)' },
|
|
2254
|
+
{ tag: CMHighlightTags.string, color: 'var(--dsh-soup-json-string)' },
|
|
2255
|
+
{ tag: CMHighlightTags.number, color: 'var(--dsh-soup-json-number)' },
|
|
2256
|
+
{ tag: CMHighlightTags.bool, color: 'var(--dsh-soup-json-literal)' },
|
|
2257
|
+
{ tag: CMHighlightTags.null, color: 'var(--dsh-soup-json-literal)' },
|
|
2258
|
+
{ tag: CMHighlightTags.punctuation, color: 'var(--dsh-soup-json-punctuation)' },
|
|
2259
|
+
{ tag: CMHighlightTags.bracket, color: 'var(--dsh-soup-json-punctuation)' },
|
|
2260
|
+
{ tag: CMHighlightTags.invalid, color: 'var(--dsh-soup-code-invalid)' },
|
|
2261
|
+
])
|
|
2262
|
+
: null
|
|
2263
|
+
|
|
2264
|
+
/** CodeMirror 6 编辑器:文本、行号、语法、高亮、光标和滚动由同一 EditorView 管理。 */
|
|
2265
|
+
function TextEditor(props) {
|
|
2266
|
+
var entry = props.entry
|
|
2267
|
+
var hostRef = React.useRef(null)
|
|
2268
|
+
var viewRef = React.useRef(null)
|
|
2269
|
+
React.useEffect(function () {
|
|
2270
|
+
if (!hostRef.current || !CMEditorView || !CMEditorState) return
|
|
2271
|
+
var EditorView = CMEditorView.EditorView
|
|
2272
|
+
var EditorState = CMEditorState.EditorState
|
|
2273
|
+
var extensions = [
|
|
2274
|
+
EditorView.updateListener.of(function (update) {
|
|
2275
|
+
if (update.docChanged) updateDraft(entry.path, update.state.doc.toString())
|
|
2276
|
+
}),
|
|
2277
|
+
]
|
|
2278
|
+
if (CMKeymap && CMKeymap.keymap) extensions.push(CMKeymap.keymap.of([
|
|
2279
|
+
{ key: 'Mod-s', run: function () { manuallySaveFile(entry.path); return true } },
|
|
2280
|
+
]))
|
|
2281
|
+
if (CMCommands) {
|
|
2282
|
+
if (CMCommands.history) extensions.push(CMCommands.history())
|
|
2283
|
+
if (CMCommands.historyKeymap && CMKeymap && CMKeymap.keymap) extensions.push(CMKeymap.keymap.of(CMCommands.historyKeymap))
|
|
2284
|
+
if (CMCommands.defaultKeymap && CMKeymap && CMKeymap.keymap) extensions.push(CMKeymap.keymap.of(CMCommands.defaultKeymap))
|
|
2285
|
+
if (CMCommands.indentWithTab && CMKeymap && CMKeymap.keymap) extensions.push(CMKeymap.keymap.of([CMCommands.indentWithTab]))
|
|
2286
|
+
}
|
|
2287
|
+
if (CMLanguage) {
|
|
2288
|
+
if (CMLanguage.bracketMatching) extensions.push(CMLanguage.bracketMatching())
|
|
2289
|
+
if (CMLanguage.indentOnInput) extensions.push(CMLanguage.indentOnInput())
|
|
2290
|
+
}
|
|
2291
|
+
if (CMEditorView.lineNumbers) extensions.push(CMEditorView.lineNumbers())
|
|
2292
|
+
if (CMEditorView.drawSelection) extensions.push(CMEditorView.drawSelection())
|
|
2293
|
+
if (CMEditorView.highlightActiveLine) extensions.push(CMEditorView.highlightActiveLine())
|
|
2294
|
+
if (CMEditorView.highlightSpecialChars) extensions.push(CMEditorView.highlightSpecialChars())
|
|
2295
|
+
var language = codeMirrorLanguage(entry.path)
|
|
2296
|
+
if (language) extensions.push(language)
|
|
2297
|
+
var ext = extOfName(entry.path)
|
|
2298
|
+
var highlightStyle = (ext === 'json' || ext === 'jsonc')
|
|
2299
|
+
? PREVIEW_JSON_HIGHLIGHT_STYLE
|
|
2300
|
+
: PREVIEW_HIGHLIGHT_STYLE
|
|
2301
|
+
if (highlightStyle) extensions.push(CMLanguage.syntaxHighlighting(highlightStyle))
|
|
2302
|
+
viewRef.current = new EditorView({
|
|
2303
|
+
state: EditorState.create({ doc: entry.draft, extensions: extensions }),
|
|
2304
|
+
parent: hostRef.current,
|
|
2305
|
+
})
|
|
2306
|
+
viewRef.current.focus()
|
|
2307
|
+
return function () {
|
|
2308
|
+
if (viewRef.current) { viewRef.current.destroy(); viewRef.current = null }
|
|
2309
|
+
}
|
|
2310
|
+
}, [entry.path])
|
|
2311
|
+
if (!CMEditorView || !CMEditorState) {
|
|
2312
|
+
return create('textarea', {
|
|
2313
|
+
className: 'dfv-editor', value: entry.draft, spellCheck: false,
|
|
2314
|
+
'aria-label': entry.name,
|
|
2315
|
+
onChange: function (e) { updateDraft(entry.path, e.target.value) },
|
|
2316
|
+
})
|
|
2317
|
+
}
|
|
2318
|
+
return create('div', { className: 'dfv-cm-editor', ref: hostRef, 'aria-label': entry.name })
|
|
2319
|
+
}
|
|
2320
|
+
|
|
1939
2321
|
// 扩展名 → Shiki 语言提示。对齐 DSH 读卡片的 LANG_BY_EXTENSION
|
|
1940
2322
|
// (packages/fs/tool-fs read-render.ts),json/md/markdown/html/htm 除外——
|
|
1941
2323
|
// 它们走上方专属渲染器。
|
|
@@ -2490,24 +2872,26 @@ window.__ModuleLoader__.load({
|
|
|
2490
2872
|
if (entry.kind === 'pdf') {
|
|
2491
2873
|
return create(PdfPreview, { data: entry.data })
|
|
2492
2874
|
}
|
|
2493
|
-
|
|
2875
|
+
if (entry.editing) return create(TextEditor, { entry: entry })
|
|
2876
|
+
// 文本类:按扩展名选预览渲染器;编辑态返回预览时展示当前草稿
|
|
2877
|
+
var text = entry.dirty ? entry.draft : entry.content
|
|
2494
2878
|
var pk = previewKind(entry.path)
|
|
2495
2879
|
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:
|
|
2880
|
+
if (pk === 'markdown') view = create(MarkdownPreview, { text: text, baseDir: parentOf(entry.path) })
|
|
2881
|
+
else if (pk === 'html') view = create(HtmlPreview, { text: text, title: entry.name })
|
|
2882
|
+
else if (pk === 'json') view = create(CodeWithLines, { text: text }, create(JsonPreview, { text: text }))
|
|
2883
|
+
else if (pk === 'notebook') view = create(NotebookPreview, { text: text })
|
|
2884
|
+
else if (pk === 'csv' || pk === 'tsv') view = create(DelimitedPreview, { text: text, delim: pk === 'tsv' ? '\t' : ',' })
|
|
2501
2885
|
else {
|
|
2502
2886
|
// 代码文件:DSH CodeBlock(Shiki 高亮,语法包懒加载);无高亮条件回退纯文本
|
|
2503
2887
|
// 都包在 CodeWithLines 里加左侧行号
|
|
2504
2888
|
var lang = Object.prototype.hasOwnProperty.call(CODE_LANG_BY_EXT, extOfName(entry.path))
|
|
2505
2889
|
? CODE_LANG_BY_EXT[extOfName(entry.path)]
|
|
2506
2890
|
: undefined
|
|
2507
|
-
var codeEl = (CodeBlock && lang &&
|
|
2508
|
-
? create(CodeBlock, { code:
|
|
2509
|
-
: create('pre', { className: 'dfv-code' },
|
|
2510
|
-
view = create(CodeWithLines, { text:
|
|
2891
|
+
var codeEl = (CodeBlock && lang && text.length <= CODE_HIGHLIGHT_MAX_CHARS)
|
|
2892
|
+
? create(CodeBlock, { code: text, lang: lang })
|
|
2893
|
+
: create('pre', { className: 'dfv-code' }, text)
|
|
2894
|
+
view = create(CodeWithLines, { text: text }, codeEl)
|
|
2511
2895
|
}
|
|
2512
2896
|
return create(React.Fragment, null,
|
|
2513
2897
|
entry.truncated ? create('div', { className: 'dfv-cap' },
|
|
@@ -2516,6 +2900,53 @@ window.__ModuleLoader__.load({
|
|
|
2516
2900
|
)
|
|
2517
2901
|
}
|
|
2518
2902
|
|
|
2903
|
+
function FileToolbar(props) {
|
|
2904
|
+
var active = props.entry
|
|
2905
|
+
var buttons = []
|
|
2906
|
+
if (active.loaded && active.kind === 'text' && isEditableTextFile(active.path)) {
|
|
2907
|
+
if (!active.editing) {
|
|
2908
|
+
buttons.push(create('button', {
|
|
2909
|
+
type: 'button',
|
|
2910
|
+
className: 'dfv-btn',
|
|
2911
|
+
title: T('files.editTitle'),
|
|
2912
|
+
disabled: active.truncated,
|
|
2913
|
+
onClick: function () { startEditing(active.path) },
|
|
2914
|
+
},
|
|
2915
|
+
create('span', { className: 'expl-icon', dangerouslySetInnerHTML: { __html: FILE_ICON_EDIT } }),
|
|
2916
|
+
T('files.edit'),
|
|
2917
|
+
))
|
|
2918
|
+
} else {
|
|
2919
|
+
buttons.push(create('button', {
|
|
2920
|
+
type: 'button',
|
|
2921
|
+
className: 'dfv-btn',
|
|
2922
|
+
title: T('files.previewTitle'),
|
|
2923
|
+
onClick: function () { stopEditing(active.path) },
|
|
2924
|
+
},
|
|
2925
|
+
create('span', { className: 'expl-icon', dangerouslySetInnerHTML: { __html: FILE_ICON_PREVIEW } }),
|
|
2926
|
+
T('files.preview'),
|
|
2927
|
+
))
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
buttons.push(active.loaded ? create('button', {
|
|
2931
|
+
type: 'button',
|
|
2932
|
+
className: 'dfv-btn',
|
|
2933
|
+
title: T('files.reloadTitle'),
|
|
2934
|
+
onClick: function () { reloadFile(active.path) },
|
|
2935
|
+
},
|
|
2936
|
+
create('span', { className: 'expl-icon', dangerouslySetInnerHTML: { __html: ICON_REFRESH } }),
|
|
2937
|
+
T('files.reload'),
|
|
2938
|
+
) : null)
|
|
2939
|
+
return create(React.Fragment, null,
|
|
2940
|
+
create('span', { className: 'dfv-toolbar-path', title: active.path }, active.path),
|
|
2941
|
+
typeof active.size === 'number' && active.size > 0 ? create('span', null, formatBytes(active.size)) : null,
|
|
2942
|
+
active.saving ? create('span', { className: 'dfv-status' }, T('files.saving')) : null,
|
|
2943
|
+
active.justSaved && !active.saving && !active.dirty ? create('span', { className: 'dfv-status dfv-saved' }, T('files.saved')) : null,
|
|
2944
|
+
active.conflict ? create('span', { className: 'dfv-status', title: T('files.conflict') }, T('files.conflictShort')) : null,
|
|
2945
|
+
active.saveError ? create('span', { className: 'dfv-save-error', title: active.saveError }, active.saveError) : null,
|
|
2946
|
+
buttons,
|
|
2947
|
+
)
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2519
2950
|
/**
|
|
2520
2951
|
* 纯阅读模式标记:在会话根(含 composerSeat 的最近祖先,即 DSH
|
|
2521
2952
|
* ConversationRoot 的 root 节点)上打 data-dsh-soup-preview,由上面
|
|
@@ -2565,6 +2996,7 @@ window.__ModuleLoader__.load({
|
|
|
2565
2996
|
title: entry.path,
|
|
2566
2997
|
onClick: function () { setActiveFile(entry.path) },
|
|
2567
2998
|
},
|
|
2999
|
+
entry.dirty ? create('span', { className: 'dfv-dirty', title: T('files.unsaved') }) : null,
|
|
2568
3000
|
create('span', { className: 'dfv-tab-name' }, entry.name),
|
|
2569
3001
|
create('span', {
|
|
2570
3002
|
className: 'dfv-close',
|
|
@@ -2584,17 +3016,7 @@ window.__ModuleLoader__.load({
|
|
|
2584
3016
|
)
|
|
2585
3017
|
} else {
|
|
2586
3018
|
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,
|
|
3019
|
+
create(FileToolbar, { entry: active }),
|
|
2598
3020
|
create('button', {
|
|
2599
3021
|
type: 'button',
|
|
2600
3022
|
className: 'dfv-btn',
|
|
@@ -2626,6 +3048,7 @@ window.__ModuleLoader__.load({
|
|
|
2626
3048
|
if (!active) return null
|
|
2627
3049
|
var close = function () { setFiles({ overlay: false, overlayMax: false, overlayReturn: null }) }
|
|
2628
3050
|
var closeAndCloseTab = function () {
|
|
3051
|
+
if (active.dirty && typeof window !== 'undefined' && !window.confirm(T('files.unsavedConfirm'))) return
|
|
2629
3052
|
close()
|
|
2630
3053
|
closeFileTab(active.path)
|
|
2631
3054
|
}
|
|
@@ -2643,17 +3066,7 @@ window.__ModuleLoader__.load({
|
|
|
2643
3066
|
},
|
|
2644
3067
|
create('div', { className: 'dfv-overlay-panel' + (files.overlayMax ? ' dfv-overlay-max' : '') },
|
|
2645
3068
|
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,
|
|
3069
|
+
create(FileToolbar, { entry: active }),
|
|
2657
3070
|
create('button', {
|
|
2658
3071
|
type: 'button', className: 'dfv-overlay-close',
|
|
2659
3072
|
title: files.overlayMax ? '还原' : '最大化', 'aria-label': files.overlayMax ? '还原' : '最大化',
|