dsh-linked-workspace 0.6.2 → 0.8.0
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/CHANGELOG.md +15 -0
- package/README.en.md +11 -3
- package/README.md +7 -3
- package/docs/API.md +8 -3
- package/docs/VERIFY.md +22 -8
- package/lib/client.js +719 -61
- package/lib/index.js +241 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -1848,6 +1848,13 @@ function fileIconSpec(name, isDir) {
|
|
|
1848
1848
|
return String(cwd || '').replace(/\\/g, '/').replace(/\/+$/, '').toLowerCase();
|
|
1849
1849
|
}
|
|
1850
1850
|
|
|
1851
|
+
function pathInsideOf(root, target) {
|
|
1852
|
+
var base = linkedScopeKey(root);
|
|
1853
|
+
var value = linkedScopeKey(target);
|
|
1854
|
+
if (!base) return true;
|
|
1855
|
+
return value === base || value.indexOf(base + '/') === 0;
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1851
1858
|
function isLinkedWorkspaceId(workspaceId) {
|
|
1852
1859
|
return Boolean(workspaceId) && linkedWorkspaceKeys.has(String(workspaceId));
|
|
1853
1860
|
}
|
|
@@ -1998,6 +2005,19 @@ function fileIconSpec(name, isDir) {
|
|
|
1998
2005
|
tooLarge: '文件过大({{size}}),暂不支持在侧边栏内编辑。',
|
|
1999
2006
|
truncated: '内容已截断',
|
|
2000
2007
|
fileLoadError: '无法打开文件',
|
|
2008
|
+
imageLoadError: '无法预览图片',
|
|
2009
|
+
previewOnly: '图片预览',
|
|
2010
|
+
csvTable: '表格',
|
|
2011
|
+
csvSource: '源码',
|
|
2012
|
+
csvEmpty: '没有可显示的数据',
|
|
2013
|
+
csvTruncated: '仅显示前 1000 行',
|
|
2014
|
+
moveTitle: '移动到…',
|
|
2015
|
+
moveMessage: '选择「{{name}}」要移动到的目录',
|
|
2016
|
+
moveDone: '已移动',
|
|
2017
|
+
moveDirty: '该文件有未保存修改,请先保存或关闭后再移动',
|
|
2018
|
+
moveFailed: '移动失败',
|
|
2019
|
+
moveHint: '进入目标文件夹后点击「使用此目录」',
|
|
2020
|
+
ctxMove: '移动到…',
|
|
2001
2021
|
noWorkspace: '远程工作区不存在,请重新添加。',
|
|
2002
2022
|
noTarget: '没有可用的远程目录。先在设置 → 连接中添加,或点击左侧「添加工作区」。',
|
|
2003
2023
|
treeEmpty: '空目录',
|
|
@@ -2148,6 +2168,26 @@ function fileIconSpec(name, isDir) {
|
|
|
2148
2168
|
openWithSystem: '系统默认应用',
|
|
2149
2169
|
openWithCopy: '复制路径',
|
|
2150
2170
|
openModeSaved: '打开方式已保存',
|
|
2171
|
+
autoSaveTitle: '自动保存',
|
|
2172
|
+
autoSaveHint: '编辑停顿约 1 秒后自动保存;关闭后请按 Ctrl+S 手动保存。',
|
|
2173
|
+
autoSaveLabel: '开启自动保存',
|
|
2174
|
+
autoSaveSaved: '自动保存设置已保存',
|
|
2175
|
+
shortcutTitle: '常用快捷键',
|
|
2176
|
+
shortcutHint: '与 VS Code 类似的默认快捷键;macOS 上把 Ctrl 换成 ⌘。',
|
|
2177
|
+
shortcutSave: '保存当前文件',
|
|
2178
|
+
shortcutClose: '关闭当前文件',
|
|
2179
|
+
shortcutSwitch: '切换文件标签',
|
|
2180
|
+
shortcutDirectory: '显示/隐藏目录面板',
|
|
2181
|
+
shortcutSearch: '在文件中搜索',
|
|
2182
|
+
terminalRemote: '打开远程终端',
|
|
2183
|
+
terminalLocal: '打开本地终端',
|
|
2184
|
+
terminalOpened: '已打开终端',
|
|
2185
|
+
terminalOpenFailed: '打开终端失败',
|
|
2186
|
+
dirtyCloseTitle: '文件尚未保存',
|
|
2187
|
+
dirtyCloseMessage: '「{{name}}」有未保存的修改。关闭前要保存吗?',
|
|
2188
|
+
dirtyCloseSave: '保存并关闭',
|
|
2189
|
+
dirtyCloseDiscard: '不保存关闭',
|
|
2190
|
+
saveFailed: '保存失败,文件未关闭',
|
|
2151
2191
|
noChanges: '没有检测到更改',
|
|
2152
2192
|
notGitRepo: '该目录不是 Git 仓库',
|
|
2153
2193
|
changesRemote: '远端更改',
|
|
@@ -2288,6 +2328,19 @@ function fileIconSpec(name, isDir) {
|
|
|
2288
2328
|
tooLarge: 'File too large ({{size}}); editing it in the sidebar is not supported yet.',
|
|
2289
2329
|
truncated: 'Content truncated',
|
|
2290
2330
|
fileLoadError: 'Cannot open the file',
|
|
2331
|
+
imageLoadError: 'Cannot preview this image',
|
|
2332
|
+
previewOnly: 'Image preview',
|
|
2333
|
+
csvTable: 'Table',
|
|
2334
|
+
csvSource: 'Source',
|
|
2335
|
+
csvEmpty: 'No rows to show',
|
|
2336
|
+
csvTruncated: 'Showing the first 1000 rows',
|
|
2337
|
+
moveTitle: 'Move to…',
|
|
2338
|
+
moveMessage: 'Choose the destination folder for "{{name}}"',
|
|
2339
|
+
moveDone: 'Moved',
|
|
2340
|
+
moveDirty: 'This file has unsaved changes; save or close it before moving',
|
|
2341
|
+
moveFailed: 'Move failed',
|
|
2342
|
+
moveHint: 'Open the destination folder, then click “Use this directory”',
|
|
2343
|
+
ctxMove: 'Move to…',
|
|
2291
2344
|
noWorkspace: 'This remote workspace no longer exists. Add it again.',
|
|
2292
2345
|
noTarget: 'No remote directory is available. Add one in Settings → Connections, or click “Add workspace” on the left.',
|
|
2293
2346
|
treeEmpty: 'Empty directory',
|
|
@@ -2438,6 +2491,26 @@ function fileIconSpec(name, isDir) {
|
|
|
2438
2491
|
openWithSystem: 'System default app',
|
|
2439
2492
|
openWithCopy: 'Copy path',
|
|
2440
2493
|
openModeSaved: 'Open mode saved',
|
|
2494
|
+
autoSaveTitle: 'Auto save',
|
|
2495
|
+
autoSaveHint: 'Saves about one second after you stop typing. When off, press Ctrl+S to save.',
|
|
2496
|
+
autoSaveLabel: 'Enable auto save',
|
|
2497
|
+
autoSaveSaved: 'Auto-save setting saved',
|
|
2498
|
+
shortcutTitle: 'Keyboard shortcuts',
|
|
2499
|
+
shortcutHint: 'VS Code-like defaults; use ⌘ instead of Ctrl on macOS.',
|
|
2500
|
+
shortcutSave: 'Save the current file',
|
|
2501
|
+
shortcutClose: 'Close the current file',
|
|
2502
|
+
shortcutSwitch: 'Switch file tabs',
|
|
2503
|
+
shortcutDirectory: 'Show/hide the directory pane',
|
|
2504
|
+
shortcutSearch: 'Search in files',
|
|
2505
|
+
terminalRemote: 'Open remote terminal',
|
|
2506
|
+
terminalLocal: 'Open local terminal',
|
|
2507
|
+
terminalOpened: 'Terminal opened',
|
|
2508
|
+
terminalOpenFailed: 'Could not open terminal',
|
|
2509
|
+
dirtyCloseTitle: 'Unsaved changes',
|
|
2510
|
+
dirtyCloseMessage: '“{{name}}” has unsaved changes. Save before closing?',
|
|
2511
|
+
dirtyCloseSave: 'Save and close',
|
|
2512
|
+
dirtyCloseDiscard: 'Close without saving',
|
|
2513
|
+
saveFailed: 'Save failed; the file was left open',
|
|
2441
2514
|
noChanges: 'No changes detected',
|
|
2442
2515
|
notGitRepo: 'Not a Git repository',
|
|
2443
2516
|
changesRemote: 'Remote changes',
|
|
@@ -2639,9 +2712,58 @@ function fileIconSpec(name, isDir) {
|
|
|
2639
2712
|
if (value === '.docx') return 'docx';
|
|
2640
2713
|
if (value === '.xlsx') return 'xlsx';
|
|
2641
2714
|
if (value === '.pptx') return 'pptx';
|
|
2715
|
+
if (value === '.csv' || value === '.tsv') return 'csv';
|
|
2716
|
+
if (value === '.png' || value === '.jpg' || value === '.jpeg' || value === '.gif'
|
|
2717
|
+
|| value === '.webp' || value === '.bmp' || value === '.ico' || value === '.avif'
|
|
2718
|
+
|| value === '.svg') return 'image';
|
|
2642
2719
|
return '';
|
|
2643
2720
|
}
|
|
2644
2721
|
|
|
2722
|
+
function isCsvPath(path) {
|
|
2723
|
+
var ext = String(path || '').toLowerCase().match(/\.[^./\\]+$/);
|
|
2724
|
+
return Boolean(ext && (ext[0] === '.csv' || ext[0] === '.tsv'));
|
|
2725
|
+
}
|
|
2726
|
+
|
|
2727
|
+
function parseDelimited(text, delimiter) {
|
|
2728
|
+
var rows = [];
|
|
2729
|
+
var row = [];
|
|
2730
|
+
var field = '';
|
|
2731
|
+
var quoted = false;
|
|
2732
|
+
var index = 0;
|
|
2733
|
+
var source = String(text || '');
|
|
2734
|
+
function pushField() { row.push(field); field = ''; }
|
|
2735
|
+
function pushRow() {
|
|
2736
|
+
pushField();
|
|
2737
|
+
rows.push(row);
|
|
2738
|
+
row = [];
|
|
2739
|
+
}
|
|
2740
|
+
while (index < source.length) {
|
|
2741
|
+
var ch = source[index];
|
|
2742
|
+
if (quoted) {
|
|
2743
|
+
if (ch === '"') {
|
|
2744
|
+
if (source[index + 1] === '"') { field += '"'; index += 2; continue; }
|
|
2745
|
+
quoted = false; index += 1; continue;
|
|
2746
|
+
}
|
|
2747
|
+
field += ch; index += 1; continue;
|
|
2748
|
+
}
|
|
2749
|
+
if (ch === '"') { quoted = true; index += 1; continue; }
|
|
2750
|
+
if (ch === delimiter) { pushField(); index += 1; continue; }
|
|
2751
|
+
if (ch === '\r') { index += 1; continue; }
|
|
2752
|
+
if (ch === '\n') { pushRow(); index += 1; continue; }
|
|
2753
|
+
field += ch; index += 1;
|
|
2754
|
+
}
|
|
2755
|
+
if (field !== '' || row.length > 0) pushRow();
|
|
2756
|
+
return rows;
|
|
2757
|
+
}
|
|
2758
|
+
|
|
2759
|
+
function csvDelimiterOf(path, text) {
|
|
2760
|
+
if (/\.tsv$/i.test(String(path || ''))) return '\t';
|
|
2761
|
+
var firstLine = String(text || '').split(/\r?\n/, 1)[0] || '';
|
|
2762
|
+
var commas = (firstLine.match(/,/g) || []).length;
|
|
2763
|
+
var semicolons = (firstLine.match(/;/g) || []).length;
|
|
2764
|
+
return semicolons > commas ? ';' : ',';
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2645
2767
|
function sanitizeDocumentHtml(html) {
|
|
2646
2768
|
try {
|
|
2647
2769
|
var parsed = new DOMParser().parseFromString(String(html || ''), 'text/html');
|
|
@@ -2718,7 +2840,7 @@ function fileIconSpec(name, isDir) {
|
|
|
2718
2840
|
};
|
|
2719
2841
|
|
|
2720
2842
|
var runtimeSettings = {
|
|
2721
|
-
value: { editorTheme: DEFAULT_THEME_ID, editorThemeFamily: DEFAULT_THEME_FAMILY, editorThemeMode: 'system', appThemeDark: false, fileOpenMode: 'editor' },
|
|
2843
|
+
value: { editorTheme: DEFAULT_THEME_ID, editorThemeFamily: DEFAULT_THEME_FAMILY, editorThemeMode: 'system', appThemeDark: false, fileOpenMode: 'editor', autoSave: false },
|
|
2722
2844
|
loaded: false,
|
|
2723
2845
|
loading: null,
|
|
2724
2846
|
listeners: new Set(),
|
|
@@ -2741,7 +2863,7 @@ function fileIconSpec(name, isDir) {
|
|
|
2741
2863
|
runtimeSettings.loading = null;
|
|
2742
2864
|
var openMode = settings && (settings.fileOpenMode === 'system' || settings.fileOpenMode === 'copy') ? settings.fileOpenMode : 'editor';
|
|
2743
2865
|
var mode = settings && (settings.editorThemeMode === 'light' || settings.editorThemeMode === 'dark') ? settings.editorThemeMode : 'system';
|
|
2744
|
-
runtimeSettings.apply({ editorTheme: settings && settings.editorTheme ? settings.editorTheme : DEFAULT_THEME_ID, editorThemeFamily: themeFamilyFor(settings), editorThemeMode: mode, fileOpenMode: openMode });
|
|
2866
|
+
runtimeSettings.apply({ editorTheme: settings && settings.editorTheme ? settings.editorTheme : DEFAULT_THEME_ID, editorThemeFamily: themeFamilyFor(settings), editorThemeMode: mode, fileOpenMode: openMode, autoSave: Boolean(settings && settings.autoSave) });
|
|
2745
2867
|
return runtimeSettings.value;
|
|
2746
2868
|
}).catch(function () {
|
|
2747
2869
|
runtimeSettings.loaded = true;
|
|
@@ -2764,6 +2886,13 @@ function fileIconSpec(name, isDir) {
|
|
|
2764
2886
|
return runtimeSettings.value;
|
|
2765
2887
|
});
|
|
2766
2888
|
},
|
|
2889
|
+
saveAutoSave: function (enabled) {
|
|
2890
|
+
return apiCall('settings.update', { autoSave: Boolean(enabled) }).then(function (settings) {
|
|
2891
|
+
runtimeSettings.loaded = true;
|
|
2892
|
+
runtimeSettings.apply({ autoSave: Boolean(settings && settings.autoSave) });
|
|
2893
|
+
return runtimeSettings.value;
|
|
2894
|
+
});
|
|
2895
|
+
},
|
|
2767
2896
|
};
|
|
2768
2897
|
|
|
2769
2898
|
function themeFamilyFor(settings) {
|
|
@@ -2968,6 +3097,10 @@ function fileIconSpec(name, isDir) {
|
|
|
2968
3097
|
'.rw-option-title{display:flex;align-items:center;gap:8px;font-size:13px;line-height:20px;font-weight:600}',
|
|
2969
3098
|
'.rw-option-desc{font-size:11.5px;line-height:17px;color:var(--dsw-alias-label-tertiary)}',
|
|
2970
3099
|
'.rw-warn-box{padding:8px 10px;border-radius:8px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-warn-primary) 45%,transparent);background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 8%,var(--dsw-alias-bg-layer-1));font-size:12px;line-height:18px}',
|
|
3100
|
+
'.rw-setting-row{display:flex;align-items:center;gap:10px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l1);border-radius:9px;background:var(--dsw-alias-bg-layer-1)}',
|
|
3101
|
+
'.rw-shortcut-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:4px 16px}',
|
|
3102
|
+
'.rw-shortcut-row{display:flex;align-items:center;gap:10px;min-height:28px;font-size:12px;color:var(--dsw-alias-label-secondary)}',
|
|
3103
|
+
'.rw-kbd{display:inline-flex;align-items:center;justify-content:center;min-width:64px;padding:2px 7px;border:1px solid var(--dsw-alias-border-l2);border-bottom-width:2px;border-radius:5px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:11px/16px ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:nowrap}',
|
|
2971
3104
|
'@media (max-width:720px){.rw-theme-grid{grid-template-columns:repeat(auto-fill,minmax(132px,1fr))}.rw-pair{grid-template-columns:minmax(0,1fr)}}',
|
|
2972
3105
|
'.rw-linked-toolbar{display:flex;align-items:center;gap:6px;padding:6px 8px;border-bottom:1px solid var(--dsw-alias-border-l1);min-width:0;position:relative;z-index:5}',
|
|
2973
3106
|
'.rw-menu-wrap{position:relative}',
|
|
@@ -3077,6 +3210,17 @@ function fileIconSpec(name, isDir) {
|
|
|
3077
3210
|
'.rw-props{display:grid;grid-template-columns:70px minmax(0,1fr);gap:5px 9px;font-size:12px;line-height:18px}',
|
|
3078
3211
|
'.rw-props-key{color:var(--dsw-alias-label-tertiary)}',
|
|
3079
3212
|
'.rw-props-value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
3213
|
+
'.rw-image-preview{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary)}',
|
|
3214
|
+
'.rw-image-head{display:flex;align-items:center;gap:8px;padding:6px 10px;border-bottom:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);min-width:0;flex:none}',
|
|
3215
|
+
'.rw-image-head .rw-actions{display:flex;align-items:center;gap:6px;flex:none}',
|
|
3216
|
+
'.rw-image-stage{display:flex;align-items:center;justify-content:center;flex:1 1 auto;min-height:0;overflow:auto;padding:18px;background:repeating-conic-gradient(var(--dsw-alias-bg-layer-1) 0% 25%,var(--dsw-alias-bg-base) 0% 50%) 50%/18px 18px}',
|
|
3217
|
+
'.rw-image{max-width:100%;max-height:100%;object-fit:contain;border-radius:4px;box-shadow:0 2px 16px rgba(0,0,0,.18)}',
|
|
3218
|
+
'.rw-csv-wrap{flex:1 1 auto;min-height:0;overflow:auto;background:var(--dsw-alias-bg-base)}',
|
|
3219
|
+
'.rw-csv-table{border-collapse:collapse;font:12px/18px ui-monospace,"Cascadia Code",Consolas,monospace;min-width:100%}',
|
|
3220
|
+
'.rw-csv-table th{position:sticky;top:0;z-index:1;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-tertiary);font-weight:600;text-align:left;padding:4px 8px;border:1px solid var(--dsw-alias-border-l1);white-space:nowrap}',
|
|
3221
|
+
'.rw-csv-table td{padding:3px 8px;border:1px solid var(--dsw-alias-border-l1);vertical-align:top;max-width:360px;overflow-wrap:anywhere}',
|
|
3222
|
+
'.rw-csv-table tr:nth-child(even) td{background:color-mix(in srgb,var(--dsw-alias-bg-layer-1) 52%,transparent)}',
|
|
3223
|
+
'.rw-csv-note{padding:5px 9px;font-size:10.5px;color:var(--dsw-alias-label-tertiary);border-top:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1)}',
|
|
3080
3224
|
'.rw-queue-row{display:flex;align-items:center;gap:8px;padding:5px 0;border-bottom:1px solid var(--dsw-alias-border-l1);font-size:12px}',
|
|
3081
3225
|
'.rw-queue-label{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
3082
3226
|
'.rw-queue-state{flex:none;font-size:10.5px;color:var(--dsw-alias-label-tertiary)}',
|
|
@@ -3184,6 +3328,12 @@ function fileIconSpec(name, isDir) {
|
|
|
3184
3328
|
h('circle', { key: 'd', cx: 4.6, cy: 11.3, r: 0.8, fill: 'currentColor' }),
|
|
3185
3329
|
]);
|
|
3186
3330
|
}
|
|
3331
|
+
function IconTerminal(size) {
|
|
3332
|
+
return svg(size || 14, [
|
|
3333
|
+
h('rect', { key: 'a', x: 1.8, y: 2.4, width: 12.4, height: 11.2, rx: 1.8, stroke: 'currentColor', strokeWidth: 1.25 }),
|
|
3334
|
+
h('path', { key: 'b', d: 'm4.4 6 2.1 2-2.1 2M8.2 10h3.3', stroke: 'currentColor', strokeWidth: 1.3, strokeLinecap: 'round', strokeLinejoin: 'round' }),
|
|
3335
|
+
]);
|
|
3336
|
+
}
|
|
3187
3337
|
function IconEye(size) {
|
|
3188
3338
|
return svg(size || 14, [
|
|
3189
3339
|
h('path', { key: 'a', d: 'M1.6 8s2.4-4.1 6.4-4.1S14.4 8 14.4 8 12 12.1 8 12.1 1.6 8 1.6 8Z', stroke: 'currentColor', strokeWidth: 1.25, strokeLinejoin: 'round' }),
|
|
@@ -3427,6 +3577,7 @@ function fileIconSpec(name, isDir) {
|
|
|
3427
3577
|
var t = props.t;
|
|
3428
3578
|
var locale = props.locale;
|
|
3429
3579
|
var initialPath = props.initialPath || '';
|
|
3580
|
+
var root = props.root || '';
|
|
3430
3581
|
var state = useState({ loading: true, listing: null, error: '', input: initialPath, creating: false, folderName: '' });
|
|
3431
3582
|
var local = state[0];
|
|
3432
3583
|
var setLocal = state[1];
|
|
@@ -3448,6 +3599,7 @@ function fileIconSpec(name, isDir) {
|
|
|
3448
3599
|
|
|
3449
3600
|
function navigate(path) {
|
|
3450
3601
|
if (!path) return;
|
|
3602
|
+
if (root && !pathInsideOf(root, path)) return;
|
|
3451
3603
|
load(path);
|
|
3452
3604
|
}
|
|
3453
3605
|
|
|
@@ -3550,7 +3702,7 @@ function fileIconSpec(name, isDir) {
|
|
|
3550
3702
|
h('div', { className: 'rw-actions' },
|
|
3551
3703
|
h('button', {
|
|
3552
3704
|
type: 'button', className: 'rw-btn rw-btn-sm rw-btn-primary',
|
|
3553
|
-
disabled: !listing || local.loading,
|
|
3705
|
+
disabled: !listing || local.loading || (root && !pathInsideOf(root, listing.path)),
|
|
3554
3706
|
onClick: function () { if (listing && props.onPick) props.onPick(listing.path); },
|
|
3555
3707
|
}, IconCheck(12), t('useThisDir')))),
|
|
3556
3708
|
local.creating
|
|
@@ -3648,6 +3800,128 @@ function fileIconSpec(name, isDir) {
|
|
|
3648
3800
|
}, IconCheck(12), t('useThisDir'))));
|
|
3649
3801
|
}
|
|
3650
3802
|
|
|
3803
|
+
function ScopePathPicker(props) {
|
|
3804
|
+
var t = props.t;
|
|
3805
|
+
var locale = props.locale;
|
|
3806
|
+
var scope = props.scope;
|
|
3807
|
+
var root = props.root || '';
|
|
3808
|
+
var state = useState({ loading: true, listing: null, error: '' });
|
|
3809
|
+
var view = state[0];
|
|
3810
|
+
var setView = state[1];
|
|
3811
|
+
|
|
3812
|
+
var load = useCallback(function (path) {
|
|
3813
|
+
setView(function (previous) { return Object.assign({}, previous, { loading: true, error: '' }); });
|
|
3814
|
+
var request = Object.assign({ path: path || root }, scopeParams(scope));
|
|
3815
|
+
apiCall('fs.list', request).then(function (result) {
|
|
3816
|
+
setView({ loading: false, listing: result, error: '' });
|
|
3817
|
+
}).catch(function (error) {
|
|
3818
|
+
setView({ loading: false, listing: null, error: messageOf(error, t, locale) });
|
|
3819
|
+
});
|
|
3820
|
+
}, [scopeKeyOf(scope), root]);
|
|
3821
|
+
|
|
3822
|
+
useEffect(function () { load(props.initialPath || root); }, []);
|
|
3823
|
+
|
|
3824
|
+
var listing = view.listing;
|
|
3825
|
+
var dirs = listing ? (listing.entries || []).filter(function (entry) { return entry.type === 'dir'; }) : [];
|
|
3826
|
+
var parent = listing && listing.path !== root
|
|
3827
|
+
? listing.path.replace(/\/+$/, '').replace(/\/[^/]*$/, '') || '/'
|
|
3828
|
+
: '';
|
|
3829
|
+
return h('div', { className: 'rw-browser rw-move-browser' },
|
|
3830
|
+
h('div', { className: 'rw-browser-head' },
|
|
3831
|
+
h('button', {
|
|
3832
|
+
type: 'button', className: 'rw-icon-btn', title: t('parentDir'),
|
|
3833
|
+
disabled: view.loading || !parent,
|
|
3834
|
+
onClick: function () { if (parent) load(parent); },
|
|
3835
|
+
}, h('span', { style: { transform: 'rotate(180deg)', display: 'inline-flex' } }, IconChevron(13))),
|
|
3836
|
+
h('div', { className: 'rw-breadcrumb rw-input-mono', title: listing ? listing.path : '' },
|
|
3837
|
+
listing ? listing.path : t('loading')),
|
|
3838
|
+
view.loading ? Spinner(13) : null),
|
|
3839
|
+
h('div', { className: 'rw-browser-list', role: 'list' },
|
|
3840
|
+
view.error
|
|
3841
|
+
? h('div', { className: 'rw-row-msg' }, IconAlert(12), view.error)
|
|
3842
|
+
: dirs.length === 0
|
|
3843
|
+
? h('div', { className: 'rw-row-msg' }, t('noEntries'))
|
|
3844
|
+
: dirs.map(function (entry) {
|
|
3845
|
+
return h('button', {
|
|
3846
|
+
key: entry.path, type: 'button', className: 'rw-browser-row', role: 'listitem',
|
|
3847
|
+
onClick: function () { load(entry.path); },
|
|
3848
|
+
}, h('span', { className: 'rw-dir-icon' }, IconFolder(13, false)), h('span', null, entry.name));
|
|
3849
|
+
})),
|
|
3850
|
+
h('div', { className: 'rw-browser-actions', style: { padding: '8px' } },
|
|
3851
|
+
h('span', { className: 'rw-hint' }, t('moveHint')),
|
|
3852
|
+
h('button', {
|
|
3853
|
+
type: 'button', className: 'rw-btn rw-btn-sm rw-btn-primary',
|
|
3854
|
+
disabled: !listing || view.loading,
|
|
3855
|
+
onClick: function () { if (listing) props.onPick(listing.path); },
|
|
3856
|
+
}, IconCheck(12), t('useThisDir'))));
|
|
3857
|
+
}
|
|
3858
|
+
|
|
3859
|
+
function ImagePreview(props) {
|
|
3860
|
+
var ctx = props.ctx;
|
|
3861
|
+
var t = props.t;
|
|
3862
|
+
var locale = props.locale;
|
|
3863
|
+
var scope = props.scope;
|
|
3864
|
+
var local = props.local === true;
|
|
3865
|
+
var filePath = props.path;
|
|
3866
|
+
var state = useState({ loading: true, error: null, data: null });
|
|
3867
|
+
var view = state[0];
|
|
3868
|
+
var setView = state[1];
|
|
3869
|
+
var params = scopeParams(scope);
|
|
3870
|
+
|
|
3871
|
+
var load = useCallback(function () {
|
|
3872
|
+
if (!filePath || (!local && !scope)) {
|
|
3873
|
+
setView({ loading: false, error: new Error(t('noWorkspace')), data: null });
|
|
3874
|
+
return;
|
|
3875
|
+
}
|
|
3876
|
+
setView({ loading: true, error: null, data: null });
|
|
3877
|
+
var request = local ? { path: filePath } : Object.assign({ path: filePath }, params);
|
|
3878
|
+
apiCall(local ? 'local.media' : 'media.read', request).then(function (result) {
|
|
3879
|
+
setView({ loading: false, error: null, data: result });
|
|
3880
|
+
}).catch(function (error) {
|
|
3881
|
+
setView({ loading: false, error: error, data: null });
|
|
3882
|
+
});
|
|
3883
|
+
}, [scopeKeyOf(scope), filePath, local]);
|
|
3884
|
+
|
|
3885
|
+
useEffect(function () { load(); }, [load]);
|
|
3886
|
+
|
|
3887
|
+
var name = basename(filePath) || t('fileTabTitle');
|
|
3888
|
+
var header = h('div', { className: 'rw-image-head' },
|
|
3889
|
+
h(FileGlyph, { name: name, size: 15 }),
|
|
3890
|
+
h('div', { className: 'rw-editor-title' },
|
|
3891
|
+
h('span', { className: 'rw-editor-name', title: name }, name),
|
|
3892
|
+
h('span', { className: 'rw-editor-path', title: filePath }, filePath)),
|
|
3893
|
+
h('div', { className: 'rw-actions' },
|
|
3894
|
+
h('button', { type: 'button', className: 'rw-btn rw-btn-sm', onClick: load, disabled: view.loading }, IconRefresh(12), t('reload'))));
|
|
3895
|
+
|
|
3896
|
+
if (view.loading) {
|
|
3897
|
+
return h('div', { className: 'rw-image-preview' }, header,
|
|
3898
|
+
h('div', { className: 'rw-editor-message' }, Spinner(14), t('loading')));
|
|
3899
|
+
}
|
|
3900
|
+
if (view.error) {
|
|
3901
|
+
return h('div', { className: 'rw-image-preview' }, header,
|
|
3902
|
+
h('div', { className: 'rw-editor-message' },
|
|
3903
|
+
h(Banner, { kind: 'error' }, t('imageLoadError') + ':' + messageOf(view.error, t, locale)),
|
|
3904
|
+
h('button', { type: 'button', className: 'rw-btn', onClick: load }, IconRefresh(13), t('retry'))));
|
|
3905
|
+
}
|
|
3906
|
+
var data = view.data;
|
|
3907
|
+
return h('div', { className: 'rw-image-preview' },
|
|
3908
|
+
header,
|
|
3909
|
+
h('div', { className: 'rw-image-stage' },
|
|
3910
|
+
h('img', {
|
|
3911
|
+
className: 'rw-image',
|
|
3912
|
+
src: 'data:' + data.mime + ';base64,' + data.base64,
|
|
3913
|
+
alt: name,
|
|
3914
|
+
onError: function () {
|
|
3915
|
+
setView({ loading: false, error: new Error(t('imageLoadError')), data: null });
|
|
3916
|
+
},
|
|
3917
|
+
})),
|
|
3918
|
+
h('div', { className: 'rw-editor-status' },
|
|
3919
|
+
h('span', { className: 'rw-status-chip' }, data.mime || 'image'),
|
|
3920
|
+
h('span', { className: 'rw-status-chip' }, formatBytes(data.size || 0)),
|
|
3921
|
+
h('span', { className: 'rw-fill', style: { flex: '1 1 auto' } }),
|
|
3922
|
+
h('span', { className: 'rw-status-chip' }, t('previewOnly'))));
|
|
3923
|
+
}
|
|
3924
|
+
|
|
3651
3925
|
// ── editor ─────────────────────────────────────────────────────────────
|
|
3652
3926
|
|
|
3653
3927
|
function colorForToken(colors, type) {
|
|
@@ -3699,6 +3973,10 @@ function fileIconSpec(name, isDir) {
|
|
|
3699
3973
|
var settings = useRuntimeSettings();
|
|
3700
3974
|
var theme = getTheme(resolveEditorTheme(settings));
|
|
3701
3975
|
var language = languageOf(filePath);
|
|
3976
|
+
var csvMode = isCsvPath(filePath);
|
|
3977
|
+
var csvTableState = useState(true);
|
|
3978
|
+
var showCsvTable = csvTableState[0];
|
|
3979
|
+
var setShowCsvTable = csvTableState[1];
|
|
3702
3980
|
|
|
3703
3981
|
var loadState = useState({ loading: true, error: null, file: null });
|
|
3704
3982
|
var load = loadState[0];
|
|
@@ -3750,11 +4028,11 @@ function fileIconSpec(name, isDir) {
|
|
|
3750
4028
|
}, [dirty, baseTitle]);
|
|
3751
4029
|
|
|
3752
4030
|
function saveFile(force) {
|
|
3753
|
-
if (readOnly || !load.file || load.file.binary || load.file.tooLarge || save.saving) return;
|
|
4031
|
+
if (readOnly || !load.file || load.file.binary || load.file.tooLarge || save.saving) return Promise.resolve(false);
|
|
3754
4032
|
setSave({ saving: true, conflict: false, error: '', ok: false });
|
|
3755
4033
|
var payload = local ? { path: filePath, content: content } : Object.assign({ path: filePath, content: content }, params);
|
|
3756
4034
|
if (!force && load.file && typeof load.file.mtime === 'number') payload.expectedMtime = load.file.mtime;
|
|
3757
|
-
apiCall(local ? 'local.write' : 'fs.write', payload).then(function (result) {
|
|
4035
|
+
return apiCall(local ? 'local.write' : 'fs.write', payload).then(function (result) {
|
|
3758
4036
|
setLoad(function (previous) {
|
|
3759
4037
|
if (!previous.file) return previous;
|
|
3760
4038
|
return { loading: false, error: null, file: Object.assign({}, previous.file, { mtime: result.mtime, size: result.size }) };
|
|
@@ -3764,15 +4042,33 @@ function fileIconSpec(name, isDir) {
|
|
|
3764
4042
|
setTimeout(function () {
|
|
3765
4043
|
setSave(function (previous) { return previous.ok ? { saving: false, conflict: false, error: '', ok: false } : previous; });
|
|
3766
4044
|
}, 2200);
|
|
4045
|
+
return true;
|
|
3767
4046
|
}).catch(function (error) {
|
|
3768
4047
|
if (error && error.code === 'CONFLICT') {
|
|
3769
4048
|
setSave({ saving: false, conflict: true, error: '', ok: false });
|
|
3770
4049
|
} else {
|
|
3771
4050
|
setSave({ saving: false, conflict: false, error: messageOf(error, t, locale), ok: false });
|
|
3772
4051
|
}
|
|
4052
|
+
return false;
|
|
3773
4053
|
});
|
|
3774
4054
|
}
|
|
3775
4055
|
|
|
4056
|
+
var saveRef = useRef(null);
|
|
4057
|
+
saveRef.current = saveFile;
|
|
4058
|
+
useEffect(function () {
|
|
4059
|
+
var notify = props.onSaveReady;
|
|
4060
|
+
if (typeof notify === 'function') notify(function () { return saveRef.current(false); });
|
|
4061
|
+
return function () {
|
|
4062
|
+
if (typeof notify === 'function') notify(null);
|
|
4063
|
+
};
|
|
4064
|
+
}, [filePath, local]);
|
|
4065
|
+
|
|
4066
|
+
useEffect(function () {
|
|
4067
|
+
if (!settings.autoSave || !dirty || !editable || save.saving || save.conflict) return undefined;
|
|
4068
|
+
var timer = setTimeout(function () { saveRef.current(false); }, 900);
|
|
4069
|
+
return function () { clearTimeout(timer); };
|
|
4070
|
+
}, [content, dirty, settings.autoSave, editable, save.saving, save.conflict]);
|
|
4071
|
+
|
|
3776
4072
|
useEffect(function () {
|
|
3777
4073
|
if (props.visible === false) return undefined;
|
|
3778
4074
|
function onKey(event) {
|
|
@@ -3805,6 +4101,14 @@ function fileIconSpec(name, isDir) {
|
|
|
3805
4101
|
return count;
|
|
3806
4102
|
}, [content]);
|
|
3807
4103
|
var gutterLines = typeof lineCount === 'number' && lineCount <= 20000;
|
|
4104
|
+
var csvRows = useMemo(function () {
|
|
4105
|
+
if (!csvMode) return [];
|
|
4106
|
+
var text = content.length > 1000000 ? content.slice(0, 1000000) : content;
|
|
4107
|
+
var delimiter = csvDelimiterOf(filePath, text);
|
|
4108
|
+
return parseDelimited(text, delimiter).slice(0, 1000).map(function (row) {
|
|
4109
|
+
return row.slice(0, 80);
|
|
4110
|
+
});
|
|
4111
|
+
}, [content, filePath, csvMode]);
|
|
3808
4112
|
var highlighted = useMemo(function () {
|
|
3809
4113
|
if (!editable) return null;
|
|
3810
4114
|
return highlightNodes(content, language, theme, 180000);
|
|
@@ -3835,13 +4139,20 @@ function fileIconSpec(name, isDir) {
|
|
|
3835
4139
|
props.onClose
|
|
3836
4140
|
? h('button', { type: 'button', className: 'rw-icon-btn', title: t('closeEditor'), onClick: props.onClose }, IconClose(13))
|
|
3837
4141
|
: null,
|
|
4142
|
+
csvMode
|
|
4143
|
+
? h('button', {
|
|
4144
|
+
type: 'button', className: 'rw-btn rw-btn-sm',
|
|
4145
|
+
onClick: function () { setShowCsvTable(function (previous) { return !previous; }); },
|
|
4146
|
+
title: showCsvTable ? t('csvSource') : t('csvTable'),
|
|
4147
|
+
}, showCsvTable ? t('csvSource') : t('csvTable'))
|
|
4148
|
+
: null,
|
|
3838
4149
|
h('button', {
|
|
3839
4150
|
type: 'button', className: 'rw-btn rw-btn-sm', onClick: function () { loadFile(); },
|
|
3840
4151
|
disabled: load.loading || save.saving,
|
|
3841
4152
|
}, IconRefresh(12), t('reload')),
|
|
3842
4153
|
h('button', {
|
|
3843
4154
|
type: 'button', className: 'rw-btn rw-btn-sm rw-btn-primary', onClick: function () { saveFile(false); },
|
|
3844
|
-
disabled: !editable || save.saving,
|
|
4155
|
+
disabled: !editable || save.saving || (csvMode && showCsvTable),
|
|
3845
4156
|
}, save.saving ? Spinner(12) : IconSave(12), save.saving ? t('saving') : t('save'))));
|
|
3846
4157
|
|
|
3847
4158
|
if (load.loading) {
|
|
@@ -3877,46 +4188,66 @@ function fileIconSpec(name, isDir) {
|
|
|
3877
4188
|
: null;
|
|
3878
4189
|
|
|
3879
4190
|
var canView = Boolean(load.file && !load.file.binary && !load.file.tooLarge);
|
|
4191
|
+
var csvColumnCount = 0;
|
|
4192
|
+
csvRows.forEach(function (row) { if (row.length > csvColumnCount) csvColumnCount = row.length; });
|
|
4193
|
+
var csvTable = csvRows.length === 0
|
|
4194
|
+
? h('div', { className: 'rw-editor-message' }, t('csvEmpty'))
|
|
4195
|
+
: h('div', { className: 'rw-csv-wrap' },
|
|
4196
|
+
h('table', { className: 'rw-csv-table' },
|
|
4197
|
+
h('thead', null, h('tr', null,
|
|
4198
|
+
Array.from({ length: csvColumnCount }, function (_, index) {
|
|
4199
|
+
return h('th', { key: 'h' + index, title: csvRows[0][index] || '' }, csvRows[0][index] || '');
|
|
4200
|
+
}))),
|
|
4201
|
+
h('tbody', null, csvRows.slice(1).map(function (row, rowIndex) {
|
|
4202
|
+
return h('tr', { key: 'r' + rowIndex },
|
|
4203
|
+
Array.from({ length: csvColumnCount }, function (_, columnIndex) {
|
|
4204
|
+
var value = row[columnIndex] === undefined ? '' : row[columnIndex];
|
|
4205
|
+
return h('td', { key: 'c' + columnIndex, title: value }, value);
|
|
4206
|
+
}));
|
|
4207
|
+
}))),
|
|
4208
|
+
csvRows.length >= 1000 ? h('div', { className: 'rw-csv-note' }, t('csvTruncated')) : null);
|
|
3880
4209
|
var body = canView
|
|
3881
|
-
?
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
},
|
|
3902
|
-
onScroll: syncScroll,
|
|
3903
|
-
onKeyDown: function (event) {
|
|
3904
|
-
if (!editable) return;
|
|
3905
|
-
if (event.key === 'Tab' && !event.ctrlKey && !event.altKey && !event.metaKey) {
|
|
3906
|
-
event.preventDefault();
|
|
3907
|
-
var target = event.currentTarget;
|
|
3908
|
-
var start = target.selectionStart;
|
|
3909
|
-
var end = target.selectionEnd;
|
|
3910
|
-
var next = content.slice(0, start) + ' ' + content.slice(end);
|
|
3911
|
-
setContent(next);
|
|
4210
|
+
? (csvMode && showCsvTable
|
|
4211
|
+
? csvTable
|
|
4212
|
+
: h('div', { className: 'rw-editor-body' },
|
|
4213
|
+
h('div', { className: 'rw-code' },
|
|
4214
|
+
h('div', { className: 'rw-gutter', ref: gutterRef },
|
|
4215
|
+
gutterLines
|
|
4216
|
+
? h('div', { className: 'rw-gutter-inner' }, Array.from({ length: lineCount }, function (_, index) { return index + 1; }).join('\n'))
|
|
4217
|
+
: null),
|
|
4218
|
+
h('div', { className: 'rw-code-area' },
|
|
4219
|
+
h('pre', { className: 'rw-highlight', ref: highlightRef, 'aria-hidden': 'true' },
|
|
4220
|
+
highlighted || content),
|
|
4221
|
+
h('textarea', {
|
|
4222
|
+
ref: textareaRef,
|
|
4223
|
+
className: 'rw-textarea',
|
|
4224
|
+
readOnly: readOnly || !editable,
|
|
4225
|
+
value: content,
|
|
4226
|
+
spellCheck: false,
|
|
4227
|
+
wrap: 'off',
|
|
4228
|
+
onChange: function (event) {
|
|
4229
|
+
setContent(event.currentTarget.value);
|
|
3912
4230
|
setDirty(true);
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
4231
|
+
setSave({ saving: false, conflict: false, error: '', ok: false });
|
|
4232
|
+
},
|
|
4233
|
+
onScroll: syncScroll,
|
|
4234
|
+
onKeyDown: function (event) {
|
|
4235
|
+
if (!editable) return;
|
|
4236
|
+
if (event.key === 'Tab' && !event.ctrlKey && !event.altKey && !event.metaKey) {
|
|
4237
|
+
event.preventDefault();
|
|
4238
|
+
var target = event.currentTarget;
|
|
4239
|
+
var start = target.selectionStart;
|
|
4240
|
+
var end = target.selectionEnd;
|
|
4241
|
+
var next = content.slice(0, start) + ' ' + content.slice(end);
|
|
4242
|
+
setContent(next);
|
|
4243
|
+
setDirty(true);
|
|
4244
|
+
requestAnimationFrame(function () {
|
|
4245
|
+
target.selectionStart = start + 4;
|
|
4246
|
+
target.selectionEnd = start + 4;
|
|
4247
|
+
});
|
|
4248
|
+
}
|
|
4249
|
+
},
|
|
4250
|
+
})))))
|
|
3920
4251
|
: h('div', { className: 'rw-editor-message' }, notice, h('span', { className: 'rw-muted' }, t('readOnly')));
|
|
3921
4252
|
|
|
3922
4253
|
return h('div', { className: 'rw-editor', style: containerStyle },
|
|
@@ -4056,6 +4387,7 @@ function fileIconSpec(name, isDir) {
|
|
|
4056
4387
|
state: { entries: cache.entries },
|
|
4057
4388
|
load: load,
|
|
4058
4389
|
onSelect: props.onSelect,
|
|
4390
|
+
onPin: props.onPin,
|
|
4059
4391
|
onSelectPath: props.onSelectPath,
|
|
4060
4392
|
onContextMenu: props.onContextMenu,
|
|
4061
4393
|
selectedPath: props.selectedPath || '',
|
|
@@ -4142,6 +4474,20 @@ function fileIconSpec(name, isDir) {
|
|
|
4142
4474
|
});
|
|
4143
4475
|
})))
|
|
4144
4476
|
: null),
|
|
4477
|
+
h('button', {
|
|
4478
|
+
type: 'button',
|
|
4479
|
+
className: 'rw-icon-btn',
|
|
4480
|
+
title: t('terminalRemote'),
|
|
4481
|
+
onClick: function () { props.onOpenTerminal('remote'); },
|
|
4482
|
+
}, IconTerminal(14)),
|
|
4483
|
+
props.localEnabled
|
|
4484
|
+
? h('button', {
|
|
4485
|
+
type: 'button',
|
|
4486
|
+
className: 'rw-icon-btn',
|
|
4487
|
+
title: t('terminalLocal'),
|
|
4488
|
+
onClick: function () { props.onOpenTerminal('local'); },
|
|
4489
|
+
}, IconTerminal(14))
|
|
4490
|
+
: null,
|
|
4145
4491
|
h('button', {
|
|
4146
4492
|
type: 'button',
|
|
4147
4493
|
className: 'rw-icon-btn' + (props.directoryOpen ? ' is-on' : ''),
|
|
@@ -4388,6 +4734,9 @@ function fileIconSpec(name, isDir) {
|
|
|
4388
4734
|
h('div', { className: 'rw-dialog-message' }, props.message),
|
|
4389
4735
|
h('div', { className: 'rw-dialog-actions' },
|
|
4390
4736
|
h('button', { type: 'button', className: 'rw-btn rw-btn-sm', onClick: props.onClose }, props.t('cancel')),
|
|
4737
|
+
props.secondaryLabel
|
|
4738
|
+
? h('button', { type: 'button', className: 'rw-btn rw-btn-sm rw-btn-primary', onClick: props.onSecondary }, props.secondaryLabel)
|
|
4739
|
+
: null,
|
|
4391
4740
|
h('button', { type: 'button', className: 'rw-btn rw-btn-sm rw-btn-danger', onClick: props.onConfirm }, props.confirmLabel)));
|
|
4392
4741
|
}
|
|
4393
4742
|
|
|
@@ -4402,6 +4751,23 @@ function fileIconSpec(name, isDir) {
|
|
|
4402
4751
|
h('button', { type: 'button', className: 'rw-btn rw-btn-sm', onClick: props.onClose }, props.t('close'))));
|
|
4403
4752
|
}
|
|
4404
4753
|
|
|
4754
|
+
function MoveDialog(props) {
|
|
4755
|
+
var picker = props.side === 'local'
|
|
4756
|
+
? h(LocalPathPicker, {
|
|
4757
|
+
t: props.t, locale: props.locale, initialPath: props.initialPath, root: props.root,
|
|
4758
|
+
onPick: props.onPick,
|
|
4759
|
+
})
|
|
4760
|
+
: h(ScopePathPicker, {
|
|
4761
|
+
t: props.t, locale: props.locale, scope: props.scope, root: props.root,
|
|
4762
|
+
initialPath: props.initialPath, onPick: props.onPick,
|
|
4763
|
+
});
|
|
4764
|
+
return h(DialogShell, { title: props.title, onClose: props.onClose },
|
|
4765
|
+
h('div', { className: 'rw-dialog-message' }, props.message),
|
|
4766
|
+
picker,
|
|
4767
|
+
h('div', { className: 'rw-dialog-actions' },
|
|
4768
|
+
h('button', { type: 'button', className: 'rw-btn rw-btn-sm', onClick: props.onClose }, props.t('cancel'))));
|
|
4769
|
+
}
|
|
4770
|
+
|
|
4405
4771
|
function QueueDialog(props) {
|
|
4406
4772
|
return h(DialogShell, { title: props.title, onClose: props.onClose },
|
|
4407
4773
|
props.queue.length === 0
|
|
@@ -4426,6 +4792,7 @@ function fileIconSpec(name, isDir) {
|
|
|
4426
4792
|
var path = props.path;
|
|
4427
4793
|
var local = props.local === true;
|
|
4428
4794
|
var onSaved = props.onSaved;
|
|
4795
|
+
var settings = useRuntimeSettings();
|
|
4429
4796
|
var kind = documentKindOf(path);
|
|
4430
4797
|
var stateRef = useState({
|
|
4431
4798
|
loading: true, error: '', data: null, text: '', dirty: false, saving: false,
|
|
@@ -4465,7 +4832,7 @@ function fileIconSpec(name, isDir) {
|
|
|
4465
4832
|
}, [state.dirty]);
|
|
4466
4833
|
|
|
4467
4834
|
function save() {
|
|
4468
|
-
if (state.saving) return;
|
|
4835
|
+
if (state.saving) return Promise.resolve(false);
|
|
4469
4836
|
patch({ saving: true, error: '', notice: '' });
|
|
4470
4837
|
var data = {};
|
|
4471
4838
|
if (kind === 'markdown') data = { text: state.text };
|
|
@@ -4479,15 +4846,49 @@ function fileIconSpec(name, isDir) {
|
|
|
4479
4846
|
deleted: Object.keys(state.deleted).filter(function (index) { return state.deleted[index]; }).map(Number),
|
|
4480
4847
|
};
|
|
4481
4848
|
}
|
|
4482
|
-
apiCall('docs.write', params({ data: data, expectedMtime: state.data ? state.data.mtime : undefined })).then(function () {
|
|
4483
|
-
patch({
|
|
4849
|
+
return apiCall('docs.write', params({ data: data, expectedMtime: state.data ? state.data.mtime : undefined })).then(function (result) {
|
|
4850
|
+
patch({
|
|
4851
|
+
saving: false,
|
|
4852
|
+
dirty: false,
|
|
4853
|
+
notice: t('saved'),
|
|
4854
|
+
data: state.data ? Object.assign({}, state.data, { mtime: result.mtime, size: result.size }) : state.data,
|
|
4855
|
+
});
|
|
4484
4856
|
if (onSaved) onSaved();
|
|
4485
|
-
|
|
4857
|
+
return true;
|
|
4486
4858
|
}).catch(function (error) {
|
|
4487
4859
|
patch({ saving: false, error: messageOf(error, t, locale) });
|
|
4860
|
+
return false;
|
|
4488
4861
|
});
|
|
4489
4862
|
}
|
|
4490
4863
|
|
|
4864
|
+
var saveRef = useRef(null);
|
|
4865
|
+
saveRef.current = save;
|
|
4866
|
+
useEffect(function () {
|
|
4867
|
+
var notify = props.onSaveReady;
|
|
4868
|
+
if (typeof notify === 'function') notify(function () { return saveRef.current(); });
|
|
4869
|
+
return function () {
|
|
4870
|
+
if (typeof notify === 'function') notify(null);
|
|
4871
|
+
};
|
|
4872
|
+
}, [path, local]);
|
|
4873
|
+
|
|
4874
|
+
useEffect(function () {
|
|
4875
|
+
if (props.visible === false) return undefined;
|
|
4876
|
+
function onKey(event) {
|
|
4877
|
+
var mod = event.ctrlKey || event.metaKey;
|
|
4878
|
+
if (!mod || String(event.key || '').toLowerCase() !== 's') return;
|
|
4879
|
+
event.preventDefault();
|
|
4880
|
+
saveRef.current();
|
|
4881
|
+
}
|
|
4882
|
+
window.addEventListener('keydown', onKey, true);
|
|
4883
|
+
return function () { window.removeEventListener('keydown', onKey, true); };
|
|
4884
|
+
}, [props.visible]);
|
|
4885
|
+
|
|
4886
|
+
useEffect(function () {
|
|
4887
|
+
if (!settings.autoSave || !state.dirty || state.saving) return undefined;
|
|
4888
|
+
var timer = setTimeout(function () { saveRef.current(); }, 900);
|
|
4889
|
+
return function () { clearTimeout(timer); };
|
|
4890
|
+
}, [settings.autoSave, state.dirty, state.saving, state.text, state.data, state.updates, state.pptxUpdates, state.rotations, state.deleted]);
|
|
4891
|
+
|
|
4491
4892
|
function showPreview() {
|
|
4492
4893
|
var next = !state.preview;
|
|
4493
4894
|
patch({ preview: next, previewLoading: next && !state.html });
|
|
@@ -4835,6 +5236,7 @@ function fileIconSpec(name, isDir) {
|
|
|
4835
5236
|
var fileDirtyState = useState({});
|
|
4836
5237
|
var fileDirty = fileDirtyState[0];
|
|
4837
5238
|
var setFileDirty = fileDirtyState[1];
|
|
5239
|
+
var saveHandlersRef = useRef({});
|
|
4838
5240
|
var directoryTabId = props.tab && props.tab.id;
|
|
4839
5241
|
|
|
4840
5242
|
useEffect(function () {
|
|
@@ -4917,7 +5319,25 @@ function fileIconSpec(name, isDir) {
|
|
|
4917
5319
|
});
|
|
4918
5320
|
}
|
|
4919
5321
|
|
|
4920
|
-
function
|
|
5322
|
+
function openTerminal(side) {
|
|
5323
|
+
if (!selected) return;
|
|
5324
|
+
var request = { side: side };
|
|
5325
|
+
if (selected.kind === 'linked') request.linkedId = selected.id;
|
|
5326
|
+
else request.workspaceId = selected.id;
|
|
5327
|
+
apiCall('terminal.open', request).then(function () {
|
|
5328
|
+
showNotice('success', t('terminalOpened'));
|
|
5329
|
+
}).catch(function (error) {
|
|
5330
|
+
showNotice('error', t('terminalOpenFailed') + ':' + messageOf(error, t, locale));
|
|
5331
|
+
});
|
|
5332
|
+
}
|
|
5333
|
+
|
|
5334
|
+
function pinByMode(side, filePath) {
|
|
5335
|
+
var mode = settings.fileOpenMode || 'editor';
|
|
5336
|
+
if (mode !== 'editor') return;
|
|
5337
|
+
openByMode(side, filePath, { pinned: true, preview: false });
|
|
5338
|
+
}
|
|
5339
|
+
|
|
5340
|
+
function openByMode(side, filePath, options) {
|
|
4921
5341
|
if (!selected || !filePath) return;
|
|
4922
5342
|
var openMode = settings.fileOpenMode || 'editor';
|
|
4923
5343
|
if (openMode === 'copy') {
|
|
@@ -4936,14 +5356,34 @@ function fileIconSpec(name, isDir) {
|
|
|
4936
5356
|
});
|
|
4937
5357
|
return;
|
|
4938
5358
|
}
|
|
4939
|
-
openInternalFile(side, filePath);
|
|
5359
|
+
openInternalFile(side, filePath, options || { pinned: true });
|
|
4940
5360
|
}
|
|
4941
5361
|
|
|
4942
|
-
function openInternalFile(side, filePath) {
|
|
5362
|
+
function openInternalFile(side, filePath, options) {
|
|
5363
|
+
var opts = options || { pinned: true };
|
|
4943
5364
|
var key = side + ':' + (side === 'remote' ? scopeKeyOf(scopeRef) : 'local') + ':' + filePath;
|
|
4944
5365
|
setOpenFiles(function (previous) {
|
|
4945
|
-
|
|
4946
|
-
|
|
5366
|
+
var existing = previous.filter(function (file) { return file.key === key; })[0];
|
|
5367
|
+
if (existing) {
|
|
5368
|
+
if (opts.pinned && !existing.pinned) {
|
|
5369
|
+
return previous.map(function (file) { return file.key === key ? Object.assign({}, file, { pinned: true }) : file; });
|
|
5370
|
+
}
|
|
5371
|
+
return previous;
|
|
5372
|
+
}
|
|
5373
|
+
if (opts.preview === true) {
|
|
5374
|
+
var activeIndex = -1;
|
|
5375
|
+
previous.forEach(function (file, index) { if (file.key === activeFileKey) activeIndex = index; });
|
|
5376
|
+
if (activeIndex >= 0) {
|
|
5377
|
+
var active = previous[activeIndex];
|
|
5378
|
+
if (!active.pinned && !fileDirty[active.key]) {
|
|
5379
|
+
var replaced = previous.slice();
|
|
5380
|
+
replaced[activeIndex] = { key: key, side: side, path: filePath, name: basename(filePath), pinned: false };
|
|
5381
|
+
setFileDirty(function (dirtyMap) { var copy = Object.assign({}, dirtyMap); delete copy[active.key]; return copy; });
|
|
5382
|
+
return replaced;
|
|
5383
|
+
}
|
|
5384
|
+
}
|
|
5385
|
+
}
|
|
5386
|
+
return previous.concat([{ key: key, side: side, path: filePath, name: basename(filePath), pinned: Boolean(opts.pinned) }]);
|
|
4947
5387
|
});
|
|
4948
5388
|
setActiveFileKey(key);
|
|
4949
5389
|
setDirectoryOpen(true);
|
|
@@ -4953,11 +5393,67 @@ function fileIconSpec(name, isDir) {
|
|
|
4953
5393
|
setOpenFiles(function (previous) {
|
|
4954
5394
|
var next = previous.filter(function (file) { return file.key !== key; });
|
|
4955
5395
|
setFileDirty(function (dirtyMap) { var copy = Object.assign({}, dirtyMap); delete copy[key]; return copy; });
|
|
5396
|
+
delete saveHandlersRef.current[key];
|
|
4956
5397
|
if (activeFileKey === key) setActiveFileKey(next.length > 0 ? next[next.length - 1].key : '');
|
|
4957
5398
|
return next;
|
|
4958
5399
|
});
|
|
4959
5400
|
}
|
|
4960
5401
|
|
|
5402
|
+
function requestCloseInternalFile(key) {
|
|
5403
|
+
if (!fileDirty[key]) { closeInternalFile(key); return; }
|
|
5404
|
+
var file = openFiles.filter(function (entry) { return entry.key === key; })[0];
|
|
5405
|
+
setDialog({ kind: 'closeDirty', key: key, name: file ? file.name : '' });
|
|
5406
|
+
}
|
|
5407
|
+
|
|
5408
|
+
function saveAndCloseInternalFile(key) {
|
|
5409
|
+
var handler = saveHandlersRef.current[key];
|
|
5410
|
+
setDialog(null);
|
|
5411
|
+
if (!handler) { closeInternalFile(key); return; }
|
|
5412
|
+
handler().then(function (ok) {
|
|
5413
|
+
if (ok) closeInternalFile(key);
|
|
5414
|
+
else showNotice('error', t('saveFailed'));
|
|
5415
|
+
});
|
|
5416
|
+
}
|
|
5417
|
+
|
|
5418
|
+
useEffect(function () {
|
|
5419
|
+
function onKey(event) {
|
|
5420
|
+
var mod = event.ctrlKey || event.metaKey;
|
|
5421
|
+
if (!mod) return;
|
|
5422
|
+
var key = String(event.key || '').toLowerCase();
|
|
5423
|
+
if (key === 'w') {
|
|
5424
|
+
if (!activeFileKey) return;
|
|
5425
|
+
event.preventDefault();
|
|
5426
|
+
event.stopPropagation();
|
|
5427
|
+
requestCloseInternalFile(activeFileKey);
|
|
5428
|
+
return;
|
|
5429
|
+
}
|
|
5430
|
+
if (key === 'tab') {
|
|
5431
|
+
if (openFiles.length < 2) return;
|
|
5432
|
+
event.preventDefault();
|
|
5433
|
+
event.stopPropagation();
|
|
5434
|
+
var index = -1;
|
|
5435
|
+
openFiles.forEach(function (file, i) { if (file.key === activeFileKey) index = i; });
|
|
5436
|
+
var step = event.shiftKey ? -1 : 1;
|
|
5437
|
+
var nextIndex = (index + step + openFiles.length) % openFiles.length;
|
|
5438
|
+
setActiveFileKey(openFiles[nextIndex].key);
|
|
5439
|
+
return;
|
|
5440
|
+
}
|
|
5441
|
+
if (key === 'b') {
|
|
5442
|
+
event.preventDefault();
|
|
5443
|
+
event.stopPropagation();
|
|
5444
|
+
setDirectoryOpen(function (open) { return !open; });
|
|
5445
|
+
return;
|
|
5446
|
+
}
|
|
5447
|
+
if (key === 'f' && event.shiftKey) {
|
|
5448
|
+
event.preventDefault();
|
|
5449
|
+
event.stopPropagation();
|
|
5450
|
+
setSearchOpen(true);
|
|
5451
|
+
}
|
|
5452
|
+
}
|
|
5453
|
+
window.addEventListener('keydown', onKey, true);
|
|
5454
|
+
return function () { window.removeEventListener('keydown', onKey, true); };
|
|
5455
|
+
}, [activeFileKey, openFiles, directoryOpen]);
|
|
5456
|
+
|
|
4961
5457
|
function closeContextMenu() { setCtxMenu(null); }
|
|
4962
5458
|
|
|
4963
5459
|
function openContextMenu(side, info) {
|
|
@@ -5097,6 +5593,56 @@ function fileIconSpec(name, isDir) {
|
|
|
5097
5593
|
}).catch(noticeError);
|
|
5098
5594
|
}
|
|
5099
5595
|
|
|
5596
|
+
function pathUnder(filePath, parentPath) {
|
|
5597
|
+
if (!filePath || !parentPath) return false;
|
|
5598
|
+
var normalized = String(filePath).replace(/\\/g, '/').replace(/\/+$/, '');
|
|
5599
|
+
var parent = String(parentPath).replace(/\\/g, '/').replace(/\/+$/, '');
|
|
5600
|
+
return normalized === parent || normalized.indexOf(parent + '/') === 0 || normalized.indexOf(parent + '\\') === 0;
|
|
5601
|
+
}
|
|
5602
|
+
|
|
5603
|
+
function moveEntry(side, oldPath, targetDir) {
|
|
5604
|
+
var blocked = openFiles.some(function (file) {
|
|
5605
|
+
return file.side === side && pathUnder(file.path, oldPath) && fileDirty[file.key];
|
|
5606
|
+
});
|
|
5607
|
+
if (blocked) {
|
|
5608
|
+
showNotice('error', t('moveDirty'));
|
|
5609
|
+
return;
|
|
5610
|
+
}
|
|
5611
|
+
var method = side === 'remote' ? 'fs.move' : 'local.move';
|
|
5612
|
+
var params = side === 'remote'
|
|
5613
|
+
? Object.assign({ path: oldPath, targetDir: targetDir }, scopeParams(scopeRef))
|
|
5614
|
+
: { path: oldPath, targetDir: targetDir, root: selected.localPath };
|
|
5615
|
+
apiCall(method, params).then(function (result) {
|
|
5616
|
+
var nextPath = result && result.path ? result.path : oldPath;
|
|
5617
|
+
setOpenFiles(function (previous) {
|
|
5618
|
+
return previous.filter(function (file) {
|
|
5619
|
+
return !(file.side === side && pathUnder(file.path, oldPath));
|
|
5620
|
+
});
|
|
5621
|
+
});
|
|
5622
|
+
setActiveFileKey(function (activeKey) {
|
|
5623
|
+
var stillOpen = openFiles.filter(function (file) {
|
|
5624
|
+
return file.key === activeKey && !(file.side === side && pathUnder(file.path, oldPath));
|
|
5625
|
+
});
|
|
5626
|
+
if (stillOpen.length > 0) return activeKey;
|
|
5627
|
+
var remaining = openFiles.filter(function (file) {
|
|
5628
|
+
return !(file.side === side && pathUnder(file.path, oldPath));
|
|
5629
|
+
});
|
|
5630
|
+
return remaining.length > 0 ? remaining[remaining.length - 1].key : null;
|
|
5631
|
+
});
|
|
5632
|
+
setFileDirty(function (previous) {
|
|
5633
|
+
var next = Object.assign({}, previous);
|
|
5634
|
+
openFiles.forEach(function (file) {
|
|
5635
|
+
if (file.side === side && pathUnder(file.path, oldPath)) delete next[file.key];
|
|
5636
|
+
});
|
|
5637
|
+
return next;
|
|
5638
|
+
});
|
|
5639
|
+
setDialog(null);
|
|
5640
|
+
showNotice('success', t('moveDone'));
|
|
5641
|
+
refreshTree();
|
|
5642
|
+
if (nextPath !== oldPath) refreshTree();
|
|
5643
|
+
}).catch(noticeError);
|
|
5644
|
+
}
|
|
5645
|
+
|
|
5100
5646
|
function submitNew(side, dirPath, name, isDir) {
|
|
5101
5647
|
var target = joinChildPath(dirPath, name, side);
|
|
5102
5648
|
var docKind = documentKindOf(name);
|
|
@@ -5176,6 +5722,15 @@ function fileIconSpec(name, isDir) {
|
|
|
5176
5722
|
items.push({ separator: true });
|
|
5177
5723
|
items.push({ label: t('ctxCopyPath'), onSelect: function () { copyPathToClipboard(menu.path); } });
|
|
5178
5724
|
items.push({ separator: true });
|
|
5725
|
+
var rootPath = menu.side === 'remote' ? selected.remotePath : selected.localPath;
|
|
5726
|
+
if (menu.path !== rootPath) {
|
|
5727
|
+
items.push({ label: t('ctxMove'), onSelect: function () {
|
|
5728
|
+
setDialog({
|
|
5729
|
+
kind: 'move', side: menu.side, path: menu.path, name: menu.name, isDir: menu.isDir,
|
|
5730
|
+
initialPath: parentPathOf(menu.path, menu.side),
|
|
5731
|
+
});
|
|
5732
|
+
} });
|
|
5733
|
+
}
|
|
5179
5734
|
items.push({ label: t('ctxRename'), onSelect: function () {
|
|
5180
5735
|
setDialog({ kind: 'rename', side: menu.side, path: menu.path, value: menu.name, title: t('renameTitle'), label: t('renameLabel') });
|
|
5181
5736
|
} });
|
|
@@ -5227,6 +5782,31 @@ function fileIconSpec(name, isDir) {
|
|
|
5227
5782
|
onConfirm: function () { setDialog(null); confirmDelete(dialog); },
|
|
5228
5783
|
});
|
|
5229
5784
|
}
|
|
5785
|
+
if (dialog.kind === 'move') {
|
|
5786
|
+
return h(MoveDialog, {
|
|
5787
|
+
t: t, locale: locale,
|
|
5788
|
+
title: t('moveTitle'),
|
|
5789
|
+
message: t('moveMessage', { name: dialog.name }),
|
|
5790
|
+
side: dialog.side,
|
|
5791
|
+
scope: dialog.side === 'remote' ? sourceScopeOf(selected) : null,
|
|
5792
|
+
root: dialog.side === 'remote' ? selected.remotePath : selected.localPath,
|
|
5793
|
+
initialPath: dialog.initialPath,
|
|
5794
|
+
onClose: function () { setDialog(null); },
|
|
5795
|
+
onPick: function (targetDir) { moveEntry(dialog.side, dialog.path, targetDir); },
|
|
5796
|
+
});
|
|
5797
|
+
}
|
|
5798
|
+
if (dialog.kind === 'closeDirty') {
|
|
5799
|
+
return h(ConfirmDialog, {
|
|
5800
|
+
t: t,
|
|
5801
|
+
title: t('dirtyCloseTitle'),
|
|
5802
|
+
message: t('dirtyCloseMessage', { name: dialog.name }),
|
|
5803
|
+
secondaryLabel: t('dirtyCloseSave'),
|
|
5804
|
+
confirmLabel: t('dirtyCloseDiscard'),
|
|
5805
|
+
onClose: function () { setDialog(null); },
|
|
5806
|
+
onSecondary: function () { saveAndCloseInternalFile(dialog.key); },
|
|
5807
|
+
onConfirm: function () { setDialog(null); closeInternalFile(dialog.key); },
|
|
5808
|
+
});
|
|
5809
|
+
}
|
|
5230
5810
|
return h(PromptDialog, {
|
|
5231
5811
|
t: t,
|
|
5232
5812
|
title: dialog.title,
|
|
@@ -5340,7 +5920,8 @@ function fileIconSpec(name, isDir) {
|
|
|
5340
5920
|
showHidden: showHidden,
|
|
5341
5921
|
selectedPath: selectedEntry && selectedEntry.side === 'remote' ? selectedEntry.path : '',
|
|
5342
5922
|
onSelectPath: function (info) { setSelectedEntry(Object.assign({ side: 'remote' }, info)); },
|
|
5343
|
-
onSelect: function (filePath) { openByMode('remote', filePath); },
|
|
5923
|
+
onSelect: function (filePath) { openByMode('remote', filePath, { pinned: false, preview: true }); },
|
|
5924
|
+
onPin: function (filePath) { pinByMode('remote', filePath); },
|
|
5344
5925
|
onContextMenu: function (info) { setSelectedEntry(Object.assign({ side: 'remote' }, info)); openContextMenu('remote', info); },
|
|
5345
5926
|
})),
|
|
5346
5927
|
selected.kind === 'linked'
|
|
@@ -5360,7 +5941,8 @@ function fileIconSpec(name, isDir) {
|
|
|
5360
5941
|
showHidden: showHidden,
|
|
5361
5942
|
selectedPath: selectedEntry && selectedEntry.side === 'local' ? selectedEntry.path : '',
|
|
5362
5943
|
onSelectPath: function (info) { setSelectedEntry(Object.assign({ side: 'local' }, info)); },
|
|
5363
|
-
onSelect: function (filePath) { openByMode('local', filePath); },
|
|
5944
|
+
onSelect: function (filePath) { openByMode('local', filePath, { pinned: false, preview: true }); },
|
|
5945
|
+
onPin: function (filePath) { pinByMode('local', filePath); },
|
|
5364
5946
|
refreshTick: localTick,
|
|
5365
5947
|
onContextMenu: function (info) { setSelectedEntry(Object.assign({ side: 'local' }, info)); openContextMenu('local', info); },
|
|
5366
5948
|
}))
|
|
@@ -5370,13 +5952,24 @@ function fileIconSpec(name, isDir) {
|
|
|
5370
5952
|
var activeFile = openFiles.filter(function (file) { return file.key === activeFileKey; })[0] || null;
|
|
5371
5953
|
var activeDocKind = activeFile ? documentKindOf(activeFile.name) : '';
|
|
5372
5954
|
var contentArea = activeFile
|
|
5373
|
-
? (activeDocKind
|
|
5955
|
+
? (activeDocKind === 'image'
|
|
5956
|
+
? h(ImagePreview, {
|
|
5957
|
+
ctx: ctx, t: t, locale: locale,
|
|
5958
|
+
scope: activeFile.side === 'remote' ? sourceScopeOf(selected) : undefined,
|
|
5959
|
+
path: activeFile.path,
|
|
5960
|
+
local: activeFile.side === 'local',
|
|
5961
|
+
})
|
|
5962
|
+
: activeDocKind && activeDocKind !== 'csv'
|
|
5374
5963
|
? h(DocumentPane, {
|
|
5375
5964
|
ctx: ctx, t: t, locale: locale,
|
|
5376
5965
|
scope: activeFile.side === 'remote' ? sourceScopeOf(selected) : undefined,
|
|
5377
5966
|
path: activeFile.path,
|
|
5378
5967
|
local: activeFile.side === 'local',
|
|
5379
5968
|
onSaved: refreshTree,
|
|
5969
|
+
onSaveReady: function (handler) {
|
|
5970
|
+
if (handler) saveHandlersRef.current[activeFile.key] = handler;
|
|
5971
|
+
else delete saveHandlersRef.current[activeFile.key];
|
|
5972
|
+
},
|
|
5380
5973
|
onDirtyChange: function (dirty) {
|
|
5381
5974
|
setFileDirty(function (previous) {
|
|
5382
5975
|
if (Boolean(previous[activeFile.key]) === Boolean(dirty)) return previous;
|
|
@@ -5393,6 +5986,10 @@ function fileIconSpec(name, isDir) {
|
|
|
5393
5986
|
local: activeFile.side === 'local',
|
|
5394
5987
|
readOnly: remoteOnly,
|
|
5395
5988
|
hideHeader: true,
|
|
5989
|
+
onSaveReady: function (handler) {
|
|
5990
|
+
if (handler) saveHandlersRef.current[activeFile.key] = handler;
|
|
5991
|
+
else delete saveHandlersRef.current[activeFile.key];
|
|
5992
|
+
},
|
|
5396
5993
|
onDirtyChange: function (dirty) {
|
|
5397
5994
|
setFileDirty(function (previous) {
|
|
5398
5995
|
if (Boolean(previous[activeFile.key]) === Boolean(dirty)) return previous;
|
|
@@ -5416,6 +6013,8 @@ function fileIconSpec(name, isDir) {
|
|
|
5416
6013
|
openMode: settings.fileOpenMode || 'editor',
|
|
5417
6014
|
directoryOpen: directoryOpen,
|
|
5418
6015
|
onToggleDirectory: function () { setDirectoryOpen(!directoryOpen); },
|
|
6016
|
+
onOpenTerminal: openTerminal,
|
|
6017
|
+
localEnabled: !remoteOnly && selected.kind === 'linked',
|
|
5419
6018
|
showHidden: showHidden,
|
|
5420
6019
|
onToggleHidden: function () { setShowHidden(!showHidden); },
|
|
5421
6020
|
transferEnabled: !remoteOnly && Boolean(selectedEntry),
|
|
@@ -5474,7 +6073,7 @@ function fileIconSpec(name, isDir) {
|
|
|
5474
6073
|
h('span', { className: 'rw-file-tab-name' }, file.name),
|
|
5475
6074
|
h('button', {
|
|
5476
6075
|
type: 'button', className: 'rw-file-tab-close', title: t('closeFile'),
|
|
5477
|
-
onClick: function (event) { event.stopPropagation();
|
|
6076
|
+
onClick: function (event) { event.stopPropagation(); requestCloseInternalFile(file.key); },
|
|
5478
6077
|
}, IconClose(10)));
|
|
5479
6078
|
}))
|
|
5480
6079
|
: null,
|
|
@@ -5535,6 +6134,7 @@ function fileIconSpec(name, isDir) {
|
|
|
5535
6134
|
var toggle = props.toggle;
|
|
5536
6135
|
var state = props.state;
|
|
5537
6136
|
var onSelect = props.onSelect;
|
|
6137
|
+
var onPin = props.onPin;
|
|
5538
6138
|
var onSelectPath = props.onSelectPath;
|
|
5539
6139
|
var onContextMenu = props.onContextMenu;
|
|
5540
6140
|
var selectedPath = props.selectedPath;
|
|
@@ -5572,6 +6172,7 @@ function fileIconSpec(name, isDir) {
|
|
|
5572
6172
|
state: state,
|
|
5573
6173
|
load: props.load,
|
|
5574
6174
|
onSelect: onSelect,
|
|
6175
|
+
onPin: onPin,
|
|
5575
6176
|
onSelectPath: onSelectPath,
|
|
5576
6177
|
onContextMenu: onContextMenu,
|
|
5577
6178
|
selectedPath: selectedPath,
|
|
@@ -5584,10 +6185,11 @@ function fileIconSpec(name, isDir) {
|
|
|
5584
6185
|
className: 'rw-row' + (active ? ' is-active' : ''),
|
|
5585
6186
|
style: { paddingLeft: 6 + (depth + 1) * 12 },
|
|
5586
6187
|
title: child.path,
|
|
5587
|
-
onClick: function () {
|
|
6188
|
+
onClick: function (event) {
|
|
5588
6189
|
if (onSelectPath) onSelectPath({ path: child.path, name: child.name, isDir: false });
|
|
6190
|
+
if (event.detail !== 2) onSelect(child.path);
|
|
5589
6191
|
},
|
|
5590
|
-
onDoubleClick: function () {
|
|
6192
|
+
onDoubleClick: function () { if (onPin) onPin(child.path); },
|
|
5591
6193
|
onContextMenu: function (event) {
|
|
5592
6194
|
if (!onContextMenu) return;
|
|
5593
6195
|
event.preventDefault();
|
|
@@ -5610,10 +6212,10 @@ function fileIconSpec(name, isDir) {
|
|
|
5610
6212
|
className: 'rw-row' + (selectedPath === path ? ' is-active' : ''),
|
|
5611
6213
|
style: { paddingLeft: 6 + depth * 12 },
|
|
5612
6214
|
title: path,
|
|
5613
|
-
onClick: function () {
|
|
6215
|
+
onClick: function (event) {
|
|
5614
6216
|
if (onSelectPath) onSelectPath({ path: path, name: name, isDir: true });
|
|
6217
|
+
if (event.detail !== 2) toggle(path);
|
|
5615
6218
|
},
|
|
5616
|
-
onDoubleClick: function () { toggle(path); },
|
|
5617
6219
|
onContextMenu: function (event) {
|
|
5618
6220
|
if (!onContextMenu) return;
|
|
5619
6221
|
event.preventDefault();
|
|
@@ -6408,6 +7010,61 @@ function fileIconSpec(name, isDir) {
|
|
|
6408
7010
|
: null);
|
|
6409
7011
|
}
|
|
6410
7012
|
|
|
7013
|
+
function EditorSettingsGroup(props) {
|
|
7014
|
+
var t = props.t;
|
|
7015
|
+
var locale = props.locale;
|
|
7016
|
+
var settings = useRuntimeSettings();
|
|
7017
|
+
var state = useState({ saving: false, message: null });
|
|
7018
|
+
var local = state[0];
|
|
7019
|
+
var setLocal = state[1];
|
|
7020
|
+
var shortcuts = [
|
|
7021
|
+
['Ctrl+S', t('shortcutSave')],
|
|
7022
|
+
['Ctrl+W', t('shortcutClose')],
|
|
7023
|
+
['Ctrl+Tab', t('shortcutSwitch')],
|
|
7024
|
+
['Ctrl+B', t('shortcutDirectory')],
|
|
7025
|
+
['Ctrl+Shift+F', t('shortcutSearch')],
|
|
7026
|
+
];
|
|
7027
|
+
|
|
7028
|
+
function toggleAutoSave(next) {
|
|
7029
|
+
if (local.saving) return;
|
|
7030
|
+
setLocal({ saving: true, message: null });
|
|
7031
|
+
runtimeSettings.saveAutoSave(next).then(function () {
|
|
7032
|
+
setLocal({ saving: false, message: { kind: 'success', text: t('autoSaveSaved') } });
|
|
7033
|
+
bus.emit();
|
|
7034
|
+
}).catch(function (error) {
|
|
7035
|
+
setLocal({ saving: false, message: { kind: 'error', text: messageOf(error, t, locale) } });
|
|
7036
|
+
});
|
|
7037
|
+
}
|
|
7038
|
+
|
|
7039
|
+
return h('div', { className: 'rw-group' },
|
|
7040
|
+
h('div', { className: 'rw-group-head' },
|
|
7041
|
+
h('span', { className: 'rw-group-title' }, t('autoSaveTitle')),
|
|
7042
|
+
h('span', { className: 'rw-group-hint' }, t('autoSaveHint'))),
|
|
7043
|
+
h('div', { className: 'rw-setting-row' },
|
|
7044
|
+
h(Switch, {
|
|
7045
|
+
checked: Boolean(settings.autoSave),
|
|
7046
|
+
onChange: toggleAutoSave,
|
|
7047
|
+
label: t('autoSaveLabel'),
|
|
7048
|
+
text: t('autoSaveLabel'),
|
|
7049
|
+
}),
|
|
7050
|
+
local.saving ? Spinner(12) : null),
|
|
7051
|
+
h('div', { className: 'rw-group-head', style: { marginTop: 14 } },
|
|
7052
|
+
h('span', { className: 'rw-group-title' }, t('shortcutTitle')),
|
|
7053
|
+
h('span', { className: 'rw-group-hint' }, t('shortcutHint'))),
|
|
7054
|
+
h('div', { className: 'rw-shortcut-grid' },
|
|
7055
|
+
shortcuts.map(function (item) {
|
|
7056
|
+
return h('div', { key: item[0], className: 'rw-shortcut-row' },
|
|
7057
|
+
h('kbd', { className: 'rw-kbd' }, item[0]),
|
|
7058
|
+
h('span', null, item[1]));
|
|
7059
|
+
})),
|
|
7060
|
+
local.message
|
|
7061
|
+
? h(Banner, {
|
|
7062
|
+
kind: local.message.kind === 'success' ? 'success' : 'error',
|
|
7063
|
+
onClose: function () { setLocal({ saving: false, message: null }); },
|
|
7064
|
+
}, local.message.text)
|
|
7065
|
+
: null);
|
|
7066
|
+
}
|
|
7067
|
+
|
|
6411
7068
|
function ConnectionsPage(props) {
|
|
6412
7069
|
var ctx = props.ctx;
|
|
6413
7070
|
var locale = useLocale(ctx);
|
|
@@ -6449,6 +7106,7 @@ function fileIconSpec(name, isDir) {
|
|
|
6449
7106
|
}, adding === 'remote' ? IconClose(13) : IconServer(13), t('addRemoteDir')))),
|
|
6450
7107
|
!sidebarAvailable ? h(Banner, { kind: 'warn' }, t('needsSidebar')) : null,
|
|
6451
7108
|
h(ThemeGroup, { t: t, locale: locale }),
|
|
7109
|
+
h(EditorSettingsGroup, { t: t, locale: locale }),
|
|
6452
7110
|
adding !== ''
|
|
6453
7111
|
? h(AddWorkspaceFlow, {
|
|
6454
7112
|
ctx: ctx, t: t, locale: locale, initialMode: adding, lockedMode: adding,
|