dsh-linked-workspace 0.5.12 → 0.6.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 +17 -0
- package/README.en.md +22 -0
- package/README.md +12 -0
- package/docs/API.md +10 -0
- package/lib/client.js +438 -12
- package/lib/index.js +256 -2
- package/package.json +3 -2
- package/scripts/doc_tool.py +405 -0
package/lib/client.js
CHANGED
|
@@ -2057,6 +2057,22 @@ function fileIconSpec(name, isDir) {
|
|
|
2057
2057
|
openFileHint: '从工作区目录树中选择文件',
|
|
2058
2058
|
toggleDirectory: '开启/关闭联动工作区目录面板',
|
|
2059
2059
|
closeFile: '关闭文件标签',
|
|
2060
|
+
resizeDirectory: '拖动调整目录面板宽度;双击恢复默认宽度',
|
|
2061
|
+
docPreview: '预览',
|
|
2062
|
+
docEdit: '编辑',
|
|
2063
|
+
exportPdf: '导出 PDF',
|
|
2064
|
+
docPages: '页',
|
|
2065
|
+
docRotate: '旋转 90°',
|
|
2066
|
+
docDeletePage: '删除当前页',
|
|
2067
|
+
docParagraph: '段落',
|
|
2068
|
+
docTable: '表格',
|
|
2069
|
+
docSheet: '工作表',
|
|
2070
|
+
docSlide: '幻灯片',
|
|
2071
|
+
docPdfTitle: '标题',
|
|
2072
|
+
docPdfAuthor: '作者',
|
|
2073
|
+
docPageOf: '第 {page} / {total} 页',
|
|
2074
|
+
docExportDone: '已导出 PDF:{path}',
|
|
2075
|
+
docPreviewFailed: '预览失败',
|
|
2060
2076
|
remoteTabTitle: '远程目录',
|
|
2061
2077
|
remoteTabDesc: '只读查看已添加的远程目录及其文件,不允许编辑。',
|
|
2062
2078
|
remoteDirsTitle: '远程目录',
|
|
@@ -2331,6 +2347,22 @@ function fileIconSpec(name, isDir) {
|
|
|
2331
2347
|
openFileHint: 'Select a file from the workspace directory tree',
|
|
2332
2348
|
toggleDirectory: 'Show or hide the linked workspace directory panel',
|
|
2333
2349
|
closeFile: 'Close file tab',
|
|
2350
|
+
resizeDirectory: 'Drag to resize the directory panel; double-click to reset',
|
|
2351
|
+
docPreview: 'Preview',
|
|
2352
|
+
docEdit: 'Edit',
|
|
2353
|
+
exportPdf: 'Export PDF',
|
|
2354
|
+
docPages: 'pages',
|
|
2355
|
+
docRotate: 'Rotate 90°',
|
|
2356
|
+
docDeletePage: 'Delete current page',
|
|
2357
|
+
docParagraph: 'Paragraph',
|
|
2358
|
+
docTable: 'Table',
|
|
2359
|
+
docSheet: 'Sheet',
|
|
2360
|
+
docSlide: 'Slide',
|
|
2361
|
+
docPdfTitle: 'Title',
|
|
2362
|
+
docPdfAuthor: 'Author',
|
|
2363
|
+
docPageOf: 'Page {page} / {total}',
|
|
2364
|
+
docExportDone: 'Exported PDF: {path}',
|
|
2365
|
+
docPreviewFailed: 'Preview failed',
|
|
2334
2366
|
remoteTabTitle: 'Remote directories',
|
|
2335
2367
|
remoteTabDesc: 'Read-only view of configured remote directories and files; editing is disabled.',
|
|
2336
2368
|
remoteDirsTitle: 'Remote directories',
|
|
@@ -2587,6 +2619,35 @@ function fileIconSpec(name, isDir) {
|
|
|
2587
2619
|
return error.message || String(error);
|
|
2588
2620
|
}
|
|
2589
2621
|
|
|
2622
|
+
function documentKindOf(path) {
|
|
2623
|
+
var ext = String(path || '').toLowerCase().match(/\.[^./\\]+$/);
|
|
2624
|
+
if (!ext) return '';
|
|
2625
|
+
var value = ext[0];
|
|
2626
|
+
if (value === '.md' || value === '.markdown') return 'markdown';
|
|
2627
|
+
if (value === '.pdf') return 'pdf';
|
|
2628
|
+
if (value === '.docx') return 'docx';
|
|
2629
|
+
if (value === '.xlsx') return 'xlsx';
|
|
2630
|
+
if (value === '.pptx') return 'pptx';
|
|
2631
|
+
return '';
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2634
|
+
function sanitizeDocumentHtml(html) {
|
|
2635
|
+
try {
|
|
2636
|
+
var parsed = new DOMParser().parseFromString(String(html || ''), 'text/html');
|
|
2637
|
+
parsed.querySelectorAll('script,style,iframe,object,embed,link,meta,base,form').forEach(function (node) { node.remove(); });
|
|
2638
|
+
parsed.querySelectorAll('*').forEach(function (element) {
|
|
2639
|
+
Array.from(element.attributes).forEach(function (attribute) {
|
|
2640
|
+
if (/^on/i.test(attribute.name) || /^javascript:/i.test(attribute.value) || /^data:text\/html/i.test(attribute.value)) {
|
|
2641
|
+
element.removeAttribute(attribute.name);
|
|
2642
|
+
}
|
|
2643
|
+
});
|
|
2644
|
+
});
|
|
2645
|
+
return parsed.body.innerHTML;
|
|
2646
|
+
} catch (error) {
|
|
2647
|
+
return '';
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2590
2651
|
function basename(path) {
|
|
2591
2652
|
if (!path) return '';
|
|
2592
2653
|
var clean = path.replace(/[\\/]+$/, '');
|
|
@@ -2934,7 +2995,38 @@ function fileIconSpec(name, isDir) {
|
|
|
2934
2995
|
'.rw-ctx-backdrop{position:fixed;inset:0;z-index:70}',
|
|
2935
2996
|
'.rw-workspace-body{display:flex;flex:1 1 auto;min-height:0;min-width:0}',
|
|
2936
2997
|
'.rw-content-pane{display:flex;flex-direction:column;flex:1 1 auto;min-width:0;min-height:0}',
|
|
2937
|
-
'.rw-directory-pane{display:flex;flex-direction:column;flex:0 0
|
|
2998
|
+
'.rw-directory-pane{display:flex;flex-direction:column;flex:0 0 auto;max-width:60%;min-width:240px;min-height:0;border-left:1px solid var(--dsw-alias-border-l1)}',
|
|
2999
|
+
'.rw-pane-resizer{flex:0 0 6px;align-self:stretch;cursor:col-resize;background:transparent;position:relative;z-index:2;touch-action:none}',
|
|
3000
|
+
'.rw-doc{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;min-width:0;background:var(--dsw-alias-bg-base)}',
|
|
3001
|
+
'.rw-doc-toolbar{display:flex;align-items:center;gap:6px;padding:6px 8px;border-bottom:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);flex:none;flex-wrap:wrap}',
|
|
3002
|
+
'.rw-doc-body{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;overflow:auto;padding:10px}',
|
|
3003
|
+
'.rw-doc-textarea{width:100%;min-height:260px;flex:1 1 auto;resize:none;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:10px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:12.5px/18px ui-monospace,"Cascadia Code",Consolas,monospace;outline:none;box-sizing:border-box}',
|
|
3004
|
+
'.rw-doc-preview{max-width:920px;margin:0 auto;color:var(--dsw-alias-label-primary);font-size:14px;line-height:1.65;overflow-wrap:anywhere}',
|
|
3005
|
+
'.rw-doc-preview h1,.rw-doc-preview h2,.rw-doc-preview h3{line-height:1.3;margin:1em 0 .5em}',
|
|
3006
|
+
'.rw-doc-preview pre{background:var(--dsw-alias-bg-layer-2);padding:10px;border-radius:8px;overflow:auto}',
|
|
3007
|
+
'.rw-doc-preview table{border-collapse:collapse;margin:.8em 0}',
|
|
3008
|
+
'.rw-doc-preview th,.rw-doc-preview td{border:1px solid var(--dsw-alias-border-l2);padding:5px 8px}',
|
|
3009
|
+
'.rw-doc-preview img{max-width:100%}',
|
|
3010
|
+
'.rw-doc-preview blockquote{border-left:3px solid var(--dsw-alias-border-l2);margin:0;padding-left:10px;color:var(--dsw-alias-label-secondary)}',
|
|
3011
|
+
'.rw-doc-pdf-page{display:block;max-width:100%;margin:0 auto 10px;border:1px solid var(--dsw-alias-border-l2);box-shadow:0 2px 10px rgba(0,0,0,.08);background:#fff}',
|
|
3012
|
+
'.rw-doc-field{display:flex;align-items:center;gap:8px;margin:6px 0}',
|
|
3013
|
+
'.rw-doc-field label{font-size:11.5px;color:var(--dsw-alias-label-tertiary);min-width:38px}',
|
|
3014
|
+
'.rw-doc-input{height:28px;padding:0 8px;border-radius:6px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;box-sizing:border-box}',
|
|
3015
|
+
'.rw-doc-block{border:1px solid var(--dsw-alias-border-l1);border-radius:8px;padding:7px;margin:0 0 8px;background:var(--dsw-alias-bg-layer-1)}',
|
|
3016
|
+
'.rw-doc-block-label{font-size:10px;line-height:15px;color:var(--dsw-alias-label-tertiary);margin-bottom:3px}',
|
|
3017
|
+
'.rw-doc-block textarea{width:100%;min-height:52px;resize:vertical;border:0;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:20px;outline:none;box-sizing:border-box}',
|
|
3018
|
+
'.rw-doc-table{border-collapse:collapse;width:100%;margin:0 0 10px;font-size:12px}',
|
|
3019
|
+
'.rw-doc-table td{border:1px solid var(--dsw-alias-border-l2);padding:0}',
|
|
3020
|
+
'.rw-doc-table input{width:100%;border:0;background:transparent;padding:4px 5px;font:inherit;color:var(--dsw-alias-label-primary);outline:none;box-sizing:border-box}',
|
|
3021
|
+
'.rw-doc-tabs{display:flex;gap:3px;padding:5px;border-bottom:1px solid var(--dsw-alias-border-l1);overflow:auto;flex:none}',
|
|
3022
|
+
'.rw-doc-tab{padding:2px 8px;border:1px solid transparent;border-radius:5px;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:11.5px;cursor:pointer;white-space:nowrap}',
|
|
3023
|
+
'.rw-doc-tab.is-on{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary)}',
|
|
3024
|
+
'.rw-doc-grid{width:100%;border-collapse:collapse;font-size:11.5px}',
|
|
3025
|
+
'.rw-doc-grid th{position:sticky;top:0;background:var(--dsw-alias-bg-layer-2);font-weight:500;color:var(--dsw-alias-label-tertiary);padding:3px 4px;border:1px solid var(--dsw-alias-border-l1);z-index:1}',
|
|
3026
|
+
'.rw-doc-grid td{border:1px solid var(--dsw-alias-border-l1);padding:0;min-width:60px}',
|
|
3027
|
+
'.rw-doc-grid input{width:100%;border:0;background:transparent;padding:3px 4px;font:inherit;color:var(--dsw-alias-label-primary);outline:none;box-sizing:border-box}',
|
|
3028
|
+
'.rw-doc-status{display:flex;align-items:center;gap:8px;padding:4px 8px;border-top:1px solid var(--dsw-alias-border-l1);font-size:10.5px;color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-layer-1);flex:none}',
|
|
3029
|
+
'.rw-pane-resizer:hover,.rw-pane-resizer:active{background:var(--dsw-alias-brand-primary)}',
|
|
2938
3030
|
'.rw-directory-head{display:flex;align-items:center;gap:6px;padding:5px 8px;font-size:10.5px;line-height:16px;letter-spacing:.03em;text-transform:uppercase;color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-layer-1);border-bottom:1px solid var(--dsw-alias-border-l1);flex:none}',
|
|
2939
3031
|
'.rw-directory-body{display:flex;flex-direction:column;flex:1 1 auto;min-height:0}',
|
|
2940
3032
|
'.rw-file-tabs{display:flex;align-items:stretch;gap:2px;min-height:30px;padding:3px 5px;overflow:auto;background:var(--dsw-alias-bg-layer-1);border-bottom:1px solid var(--dsw-alias-border-l1);flex:none}',
|
|
@@ -3889,6 +3981,11 @@ function fileIconSpec(name, isDir) {
|
|
|
3889
3981
|
var setCache = cacheState[1];
|
|
3890
3982
|
var pending = useRef(new Set());
|
|
3891
3983
|
|
|
3984
|
+
useEffect(function () {
|
|
3985
|
+
if (!props.refreshTick) return;
|
|
3986
|
+
Array.from(expanded).forEach(function (path) { load(path, true); });
|
|
3987
|
+
}, [props.refreshTick]);
|
|
3988
|
+
|
|
3892
3989
|
function toggle(path) {
|
|
3893
3990
|
setExpanded(function (previous) {
|
|
3894
3991
|
var next = new Set(previous);
|
|
@@ -3898,13 +3995,13 @@ function fileIconSpec(name, isDir) {
|
|
|
3898
3995
|
});
|
|
3899
3996
|
}
|
|
3900
3997
|
|
|
3901
|
-
function load(path) {
|
|
3998
|
+
function load(path, silent) {
|
|
3902
3999
|
if (pending.current.has(path)) return;
|
|
3903
4000
|
pending.current.add(path);
|
|
3904
4001
|
setCache(function (previous) {
|
|
3905
4002
|
var entries = new Map(previous.entries);
|
|
3906
4003
|
var existing = entries.get(path) || {};
|
|
3907
|
-
entries.set(path, { loading:
|
|
4004
|
+
entries.set(path, { loading: !silent, error: null, entries: existing.entries || [], loaded: existing.loaded });
|
|
3908
4005
|
return { entries: entries };
|
|
3909
4006
|
});
|
|
3910
4007
|
var request = props.kind === 'local'
|
|
@@ -4310,6 +4407,243 @@ function fileIconSpec(name, isDir) {
|
|
|
4310
4407
|
h('button', { type: 'button', className: 'rw-btn rw-btn-sm', onClick: props.onClose }, props.t('close'))));
|
|
4311
4408
|
}
|
|
4312
4409
|
|
|
4410
|
+
function DocumentPane(props) {
|
|
4411
|
+
var ctx = props.ctx;
|
|
4412
|
+
var t = props.t;
|
|
4413
|
+
var locale = props.locale;
|
|
4414
|
+
var scope = props.scope;
|
|
4415
|
+
var path = props.path;
|
|
4416
|
+
var local = props.local === true;
|
|
4417
|
+
var onSaved = props.onSaved;
|
|
4418
|
+
var kind = documentKindOf(path);
|
|
4419
|
+
var stateRef = useState({
|
|
4420
|
+
loading: true, error: '', data: null, text: '', dirty: false, saving: false,
|
|
4421
|
+
preview: false, html: '', previewLoading: false, page: 0, sheet: 0, slide: 0,
|
|
4422
|
+
updates: {}, pptxUpdates: {}, rotations: {}, deleted: {}, notice: '',
|
|
4423
|
+
});
|
|
4424
|
+
var state = stateRef[0];
|
|
4425
|
+
var setState = stateRef[1];
|
|
4426
|
+
|
|
4427
|
+
function patch(next) {
|
|
4428
|
+
setState(function (previous) { return Object.assign({}, previous, next); });
|
|
4429
|
+
}
|
|
4430
|
+
|
|
4431
|
+
function params(extra) {
|
|
4432
|
+
var base = local ? { path: path } : Object.assign({ path: path }, scopeParams(scope));
|
|
4433
|
+
return Object.assign(base, extra || {});
|
|
4434
|
+
}
|
|
4435
|
+
|
|
4436
|
+
function load() {
|
|
4437
|
+
patch({ loading: true, error: '', notice: '' });
|
|
4438
|
+
apiCall('docs.read', params()).then(function (result) {
|
|
4439
|
+
patch({
|
|
4440
|
+
loading: false, error: '', data: result,
|
|
4441
|
+
text: result.kind === 'markdown' ? String(result.text || '') : '',
|
|
4442
|
+
preview: false, html: '', page: 0, sheet: 0, slide: 0,
|
|
4443
|
+
updates: {}, pptxUpdates: {}, rotations: {}, deleted: {}, dirty: false,
|
|
4444
|
+
});
|
|
4445
|
+
}).catch(function (error) {
|
|
4446
|
+
patch({ loading: false, error: messageOf(error, t, locale) });
|
|
4447
|
+
});
|
|
4448
|
+
}
|
|
4449
|
+
|
|
4450
|
+
useEffect(function () { load(); }, [path, local, scope && scope.kind, scope && scope.id]);
|
|
4451
|
+
|
|
4452
|
+
useEffect(function () {
|
|
4453
|
+
if (typeof props.onDirtyChange === 'function') props.onDirtyChange(state.dirty);
|
|
4454
|
+
}, [state.dirty]);
|
|
4455
|
+
|
|
4456
|
+
function save() {
|
|
4457
|
+
if (state.saving) return;
|
|
4458
|
+
patch({ saving: true, error: '', notice: '' });
|
|
4459
|
+
var data = {};
|
|
4460
|
+
if (kind === 'markdown') data = { text: state.text };
|
|
4461
|
+
if (kind === 'docx' && state.data) data = { paragraphs: state.data.paragraphs || [], tables: state.data.tables || [] };
|
|
4462
|
+
if (kind === 'xlsx') data = { updates: Object.keys(state.updates).map(function (key) { return state.updates[key]; }) };
|
|
4463
|
+
if (kind === 'pptx') data = { updates: Object.keys(state.pptxUpdates).map(function (key) { return state.pptxUpdates[key]; }) };
|
|
4464
|
+
if (kind === 'pdf' && state.data) {
|
|
4465
|
+
data = {
|
|
4466
|
+
metadata: state.data.metadata || {},
|
|
4467
|
+
rotations: Object.keys(state.rotations).map(function (index) { return { index: Number(index), degrees: state.rotations[index] }; }),
|
|
4468
|
+
deleted: Object.keys(state.deleted).filter(function (index) { return state.deleted[index]; }).map(Number),
|
|
4469
|
+
};
|
|
4470
|
+
}
|
|
4471
|
+
apiCall('docs.write', params({ data: data, expectedMtime: state.data ? state.data.mtime : undefined })).then(function () {
|
|
4472
|
+
patch({ saving: false, dirty: false, notice: t('saved') });
|
|
4473
|
+
if (onSaved) onSaved();
|
|
4474
|
+
load();
|
|
4475
|
+
}).catch(function (error) {
|
|
4476
|
+
patch({ saving: false, error: messageOf(error, t, locale) });
|
|
4477
|
+
});
|
|
4478
|
+
}
|
|
4479
|
+
|
|
4480
|
+
function showPreview() {
|
|
4481
|
+
var next = !state.preview;
|
|
4482
|
+
patch({ preview: next, previewLoading: next && !state.html });
|
|
4483
|
+
if (!next || state.html) return;
|
|
4484
|
+
apiCall('docs.mdPreview', params({ text: state.text })).then(function (result) {
|
|
4485
|
+
patch({ previewLoading: false, html: sanitizeDocumentHtml(result.html) });
|
|
4486
|
+
}).catch(function (error) {
|
|
4487
|
+
patch({ previewLoading: false, error: messageOf(error, t, locale) });
|
|
4488
|
+
});
|
|
4489
|
+
}
|
|
4490
|
+
|
|
4491
|
+
function exportPdf() {
|
|
4492
|
+
patch({ saving: true, error: '', notice: '' });
|
|
4493
|
+
apiCall('docs.mdPdf', params({ text: state.text })).then(function (result) {
|
|
4494
|
+
patch({ saving: false, notice: t('docExportDone', { path: result.path }) });
|
|
4495
|
+
if (onSaved) onSaved();
|
|
4496
|
+
}).catch(function (error) {
|
|
4497
|
+
patch({ saving: false, error: messageOf(error, t, locale) });
|
|
4498
|
+
});
|
|
4499
|
+
}
|
|
4500
|
+
|
|
4501
|
+
function updateParagraph(index, value) {
|
|
4502
|
+
var data = state.data || {};
|
|
4503
|
+
var paragraphs = (data.paragraphs || []).slice();
|
|
4504
|
+
paragraphs[index] = Object.assign({}, paragraphs[index], { text: value });
|
|
4505
|
+
patch({ data: Object.assign({}, data, { paragraphs: paragraphs }), dirty: true });
|
|
4506
|
+
}
|
|
4507
|
+
|
|
4508
|
+
function updateTableCell(tableIndex, rowIndex, cellIndex, value) {
|
|
4509
|
+
var data = state.data || {};
|
|
4510
|
+
var tables = (data.tables || []).map(function (rows) { return rows.map(function (row) { return row.slice(); }); });
|
|
4511
|
+
if (!tables[tableIndex] || !tables[tableIndex][rowIndex]) return;
|
|
4512
|
+
tables[tableIndex][rowIndex][cellIndex] = value;
|
|
4513
|
+
patch({ data: Object.assign({}, data, { tables: tables }), dirty: true });
|
|
4514
|
+
}
|
|
4515
|
+
|
|
4516
|
+
function updateCell(sheetName, cell, value) {
|
|
4517
|
+
var key = sheetName + '!' + cell;
|
|
4518
|
+
var updates = Object.assign({}, state.updates);
|
|
4519
|
+
updates[key] = { sheet: sheetName, cell: cell, value: value };
|
|
4520
|
+
patch({ updates: updates, dirty: true });
|
|
4521
|
+
}
|
|
4522
|
+
|
|
4523
|
+
function updateSlideText(slideIndex, shapeIndex, value) {
|
|
4524
|
+
var key = slideIndex + ':' + shapeIndex;
|
|
4525
|
+
var updates = Object.assign({}, state.pptxUpdates);
|
|
4526
|
+
updates[key] = { slide: slideIndex, shape: shapeIndex, text: value };
|
|
4527
|
+
patch({ pptxUpdates: updates, dirty: true });
|
|
4528
|
+
}
|
|
4529
|
+
|
|
4530
|
+
var toolbar = h('div', { className: 'rw-doc-toolbar' },
|
|
4531
|
+
h('span', { className: 'rw-chip' }, kind.toUpperCase()),
|
|
4532
|
+
h('span', { className: 'rw-hint', style: { flex: '1 1 auto', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, title: path }, path),
|
|
4533
|
+
kind === 'markdown' ? h('button', { type: 'button', className: 'rw-btn rw-btn-sm', onClick: showPreview }, state.preview ? t('docEdit') : t('docPreview')) : null,
|
|
4534
|
+
kind === 'markdown' ? h('button', { type: 'button', className: 'rw-btn rw-btn-sm', onClick: exportPdf, disabled: state.saving }, IconDownload(12), t('exportPdf')) : null,
|
|
4535
|
+
h('button', { type: 'button', className: 'rw-btn rw-btn-sm rw-btn-primary', onClick: save, disabled: state.saving || !state.dirty }, state.saving ? Spinner(12) : IconSave(12), state.saving ? t('saving') : t('save')));
|
|
4536
|
+
|
|
4537
|
+
if (state.loading) {
|
|
4538
|
+
return h('div', { className: 'rw-doc' }, toolbar, h('div', { className: 'rw-editor-message' }, Spinner(14), t('loading')));
|
|
4539
|
+
}
|
|
4540
|
+
if (state.error && !state.data) {
|
|
4541
|
+
return h('div', { className: 'rw-doc' }, toolbar,
|
|
4542
|
+
h('div', { className: 'rw-editor-message' },
|
|
4543
|
+
h(Banner, { kind: 'error' }, state.error),
|
|
4544
|
+
h('button', { type: 'button', className: 'rw-btn', onClick: load }, IconRefresh(13), t('reload'))));
|
|
4545
|
+
}
|
|
4546
|
+
|
|
4547
|
+
var body = null;
|
|
4548
|
+
if (kind === 'markdown') {
|
|
4549
|
+
body = state.preview
|
|
4550
|
+
? h('div', { className: 'rw-doc-body' },
|
|
4551
|
+
state.previewLoading ? h('div', { className: 'rw-editor-message' }, Spinner(14), t('loading')) : null,
|
|
4552
|
+
state.html ? h('div', { className: 'rw-doc-preview', dangerouslySetInnerHTML: { __html: state.html } }) : null)
|
|
4553
|
+
: h('div', { className: 'rw-doc-body' },
|
|
4554
|
+
h('textarea', {
|
|
4555
|
+
className: 'rw-doc-textarea', value: state.text, spellCheck: false,
|
|
4556
|
+
onChange: function (event) { patch({ text: event.currentTarget.value, dirty: true, html: '' }); },
|
|
4557
|
+
}));
|
|
4558
|
+
} else if (kind === 'pdf' && state.data) {
|
|
4559
|
+
var pages = state.data.pages || [];
|
|
4560
|
+
var visible = pages.filter(function (page) { return !state.deleted[page.index]; });
|
|
4561
|
+
var current = visible[Math.min(state.page, Math.max(visible.length - 1, 0))] || null;
|
|
4562
|
+
body = h('div', { className: 'rw-doc-body' },
|
|
4563
|
+
h('div', { className: 'rw-doc-field' },
|
|
4564
|
+
h('label', null, t('docPdfTitle')),
|
|
4565
|
+
h('input', { className: 'rw-doc-input', style: { flex: '1 1 auto' }, value: (state.data.metadata && state.data.metadata.title) || '', onChange: function (event) { patch({ data: Object.assign({}, state.data, { metadata: Object.assign({}, state.data.metadata, { title: event.currentTarget.value }) }), dirty: true }); } }),
|
|
4566
|
+
h('label', null, t('docPdfAuthor')),
|
|
4567
|
+
h('input', { className: 'rw-doc-input', style: { flex: '1 1 auto' }, value: (state.data.metadata && state.data.metadata.author) || '', onChange: function (event) { patch({ data: Object.assign({}, state.data, { metadata: Object.assign({}, state.data.metadata, { author: event.currentTarget.value }) }), dirty: true }); } })),
|
|
4568
|
+
h('div', { className: 'rw-inline', style: { justifyContent: 'space-between', margin: '6px 0' } },
|
|
4569
|
+
h('div', { className: 'rw-inline' },
|
|
4570
|
+
h('button', { type: 'button', className: 'rw-btn rw-btn-sm', disabled: state.page <= 0, onClick: function () { patch({ page: state.page - 1 }); } }, '‹'),
|
|
4571
|
+
h('span', { className: 'rw-hint' }, current ? t('docPageOf', { page: current.index + 1, total: state.data.pageCount }) : ''),
|
|
4572
|
+
h('button', { type: 'button', className: 'rw-btn rw-btn-sm', disabled: state.page >= visible.length - 1, onClick: function () { patch({ page: state.page + 1 }); } }, '›')),
|
|
4573
|
+
h('div', { className: 'rw-inline' },
|
|
4574
|
+
h('button', { type: 'button', className: 'rw-btn rw-btn-sm', disabled: !current, onClick: function () { var rotations = Object.assign({}, state.rotations); rotations[current.index] = ((rotations[current.index] || current.rotation || 0) + 90) % 360; patch({ rotations: rotations, dirty: true }); } }, t('docRotate')),
|
|
4575
|
+
h('button', { type: 'button', className: 'rw-btn rw-btn-sm rw-btn-danger', disabled: !current || visible.length <= 1, onClick: function () { var deleted = Object.assign({}, state.deleted); deleted[current.index] = true; patch({ deleted: deleted, page: Math.max(0, state.page - 1), dirty: true }); } }, t('docDeletePage')))),
|
|
4576
|
+
current ? h('img', { className: 'rw-doc-pdf-page', src: current.png, style: { transform: 'rotate(' + ((state.rotations[current.index] || current.rotation || 0) % 360) + 'deg)' } }) : h('div', { className: 'rw-empty' }, t('noEntries')));
|
|
4577
|
+
} else if (kind === 'docx' && state.data) {
|
|
4578
|
+
var paragraphs = state.data.paragraphs || [];
|
|
4579
|
+
var tables = state.data.tables || [];
|
|
4580
|
+
body = h('div', { className: 'rw-doc-body' },
|
|
4581
|
+
paragraphs.map(function (paragraph, index) {
|
|
4582
|
+
return h('div', { key: 'p' + index, className: 'rw-doc-block' },
|
|
4583
|
+
h('div', { className: 'rw-doc-block-label' }, (paragraph.style || t('docParagraph')) + ' · ' + (index + 1)),
|
|
4584
|
+
h('textarea', { value: paragraph.text || '', onChange: function (event) { updateParagraph(index, event.currentTarget.value); } }));
|
|
4585
|
+
}),
|
|
4586
|
+
tables.map(function (rows, tableIndex) {
|
|
4587
|
+
return h('div', { key: 't' + tableIndex },
|
|
4588
|
+
h('div', { className: 'rw-doc-block-label' }, t('docTable') + ' ' + (tableIndex + 1)),
|
|
4589
|
+
h('table', { className: 'rw-doc-table' }, h('tbody', null,
|
|
4590
|
+
rows.map(function (row, rowIndex) {
|
|
4591
|
+
return h('tr', { key: rowIndex }, row.map(function (cell, cellIndex) {
|
|
4592
|
+
return h('td', { key: cellIndex }, h('input', { value: cell || '', onChange: function (event) { updateTableCell(tableIndex, rowIndex, cellIndex, event.currentTarget.value); } }));
|
|
4593
|
+
}));
|
|
4594
|
+
}))));
|
|
4595
|
+
}));
|
|
4596
|
+
} else if (kind === 'xlsx' && state.data) {
|
|
4597
|
+
var sheets = state.data.sheets || [];
|
|
4598
|
+
var active = sheets[Math.min(state.sheet, Math.max(sheets.length - 1, 0))] || { name: '', rows: [] };
|
|
4599
|
+
var columns = Math.max(active.maxCol || 0, 8);
|
|
4600
|
+
var header = h('tr', null, h('th', null, ''), Array.from({ length: columns }, function (_, index) { return h('th', { key: index }, String.fromCharCode(65 + index)); }));
|
|
4601
|
+
var gridRows = (active.rows || []).map(function (row, rowIndex) {
|
|
4602
|
+
return h('tr', { key: rowIndex },
|
|
4603
|
+
h('th', null, String(rowIndex + 1)),
|
|
4604
|
+
Array.from({ length: columns }, function (_, colIndex) {
|
|
4605
|
+
var cell = String.fromCharCode(65 + colIndex) + (rowIndex + 1);
|
|
4606
|
+
var value = row[colIndex];
|
|
4607
|
+
return h('td', { key: colIndex }, h('input', {
|
|
4608
|
+
value: value === null || value === undefined ? '' : String(value),
|
|
4609
|
+
onChange: function (event) { updateCell(active.name, cell, event.currentTarget.value); },
|
|
4610
|
+
}));
|
|
4611
|
+
}));
|
|
4612
|
+
});
|
|
4613
|
+
body = h('div', { className: 'rw-doc-body', style: { padding: 0, overflow: 'hidden' } },
|
|
4614
|
+
h('div', { className: 'rw-doc-tabs' }, sheets.map(function (sheet, index) {
|
|
4615
|
+
return h('button', { key: sheet.name, type: 'button', className: 'rw-doc-tab' + (index === state.sheet ? ' is-on' : ''), onClick: function () { patch({ sheet: index }); } }, sheet.name);
|
|
4616
|
+
})),
|
|
4617
|
+
h('div', { style: { flex: '1 1 auto', overflow: 'auto', padding: 8 } },
|
|
4618
|
+
h('table', { className: 'rw-doc-grid' },
|
|
4619
|
+
h('thead', null, header),
|
|
4620
|
+
h('tbody', null, gridRows))));
|
|
4621
|
+
} else if (kind === 'pptx' && state.data) {
|
|
4622
|
+
var slides = state.data.slides || [];
|
|
4623
|
+
var slide = slides[Math.min(state.slide, Math.max(slides.length - 1, 0))] || { texts: [] };
|
|
4624
|
+
body = h('div', { className: 'rw-doc-body' },
|
|
4625
|
+
h('div', { className: 'rw-inline', style: { justifyContent: 'space-between', marginBottom: 6 } },
|
|
4626
|
+
h('div', { className: 'rw-inline' },
|
|
4627
|
+
h('button', { type: 'button', className: 'rw-btn rw-btn-sm', disabled: state.slide <= 0, onClick: function () { patch({ slide: state.slide - 1 }); } }, '‹'),
|
|
4628
|
+
h('span', { className: 'rw-hint' }, (state.slide + 1) + ' / ' + slides.length),
|
|
4629
|
+
h('button', { type: 'button', className: 'rw-btn rw-btn-sm', disabled: state.slide >= slides.length - 1, onClick: function () { patch({ slide: state.slide + 1 }); } }, '›'))),
|
|
4630
|
+
(slide.texts || []).map(function (item) {
|
|
4631
|
+
return h('div', { key: item.shape, className: 'rw-doc-block' },
|
|
4632
|
+
h('div', { className: 'rw-doc-block-label' }, (item.name || t('docSlide')) + ' · ' + item.shape),
|
|
4633
|
+
h('textarea', { value: (state.pptxUpdates[state.slide + ':' + item.shape] || item).text || '', onChange: function (event) { updateSlideText(state.slide, item.shape, event.currentTarget.value); } }));
|
|
4634
|
+
}));
|
|
4635
|
+
}
|
|
4636
|
+
|
|
4637
|
+
return h('div', { className: 'rw-doc' }, toolbar,
|
|
4638
|
+
state.notice ? h('div', { style: { padding: '6px 10px 0' } }, h(Banner, { kind: 'success' }, state.notice)) : null,
|
|
4639
|
+
state.error ? h('div', { style: { padding: '6px 10px 0' } }, h(Banner, { kind: 'error' }, state.error)) : null,
|
|
4640
|
+
body,
|
|
4641
|
+
h('div', { className: 'rw-doc-status' },
|
|
4642
|
+
h('span', null, kind.toUpperCase()),
|
|
4643
|
+
h('span', null, path),
|
|
4644
|
+
state.dirty ? h('span', { className: 'rw-status-chip rw-status-warn' }, t('dirty')) : null));
|
|
4645
|
+
}
|
|
4646
|
+
|
|
4313
4647
|
function RemoteDirectoryTab(props) {
|
|
4314
4648
|
var ctx = props.ctx;
|
|
4315
4649
|
var t = props.t;
|
|
@@ -4445,10 +4779,68 @@ function fileIconSpec(name, isDir) {
|
|
|
4445
4779
|
var directoryOpenState = useState(true);
|
|
4446
4780
|
var directoryOpen = directoryOpenState[0];
|
|
4447
4781
|
var setDirectoryOpen = directoryOpenState[1];
|
|
4782
|
+
var directoryWidthState = useState(function () {
|
|
4783
|
+
try {
|
|
4784
|
+
var saved = parseInt(window.localStorage.getItem('dsh-linked-workspace:directory-width'), 10);
|
|
4785
|
+
return saved >= 240 && saved <= 900 ? saved : 300;
|
|
4786
|
+
} catch (error) { return 300; }
|
|
4787
|
+
});
|
|
4788
|
+
var directoryWidth = directoryWidthState[0];
|
|
4789
|
+
var setDirectoryWidth = directoryWidthState[1];
|
|
4790
|
+
var directoryWidthRef = useRef(directoryWidth);
|
|
4791
|
+
var workbenchRef = useRef(null);
|
|
4792
|
+
var localTickState = useState(0);
|
|
4793
|
+
var localTick = localTickState[0];
|
|
4794
|
+
var setLocalTick = localTickState[1];
|
|
4448
4795
|
var fileDirtyState = useState({});
|
|
4449
4796
|
var fileDirty = fileDirtyState[0];
|
|
4450
4797
|
var setFileDirty = fileDirtyState[1];
|
|
4451
4798
|
var directoryTabId = props.tab && props.tab.id;
|
|
4799
|
+
|
|
4800
|
+
useEffect(function () {
|
|
4801
|
+
directoryWidthRef.current = directoryWidth;
|
|
4802
|
+
try { window.localStorage.setItem('dsh-linked-workspace:directory-width', String(directoryWidth)); } catch (error) { /* storage unavailable */ }
|
|
4803
|
+
}, [directoryWidth]);
|
|
4804
|
+
|
|
4805
|
+
useEffect(function () {
|
|
4806
|
+
if (!directoryOpen) return undefined;
|
|
4807
|
+
var bump = function () {
|
|
4808
|
+
if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return;
|
|
4809
|
+
setLocalTick(function (value) { return value + 1; });
|
|
4810
|
+
};
|
|
4811
|
+
var timer = setInterval(bump, 4000);
|
|
4812
|
+
if (timer && typeof timer.unref === 'function') timer.unref();
|
|
4813
|
+
window.addEventListener('focus', bump);
|
|
4814
|
+
return function () {
|
|
4815
|
+
clearInterval(timer);
|
|
4816
|
+
window.removeEventListener('focus', bump);
|
|
4817
|
+
};
|
|
4818
|
+
}, [directoryOpen, selectedKey]);
|
|
4819
|
+
|
|
4820
|
+
function beginResize(event) {
|
|
4821
|
+
if (event.button !== undefined && event.button !== 0) return;
|
|
4822
|
+
event.preventDefault();
|
|
4823
|
+
var startX = event.clientX;
|
|
4824
|
+
var startWidth = directoryWidthRef.current;
|
|
4825
|
+
var workbench = workbenchRef.current;
|
|
4826
|
+
var maxWidth = workbench ? Math.max(260, workbench.clientWidth - 320) : 720;
|
|
4827
|
+
document.body.style.cursor = 'col-resize';
|
|
4828
|
+
document.body.style.userSelect = 'none';
|
|
4829
|
+
function move(moveEvent) {
|
|
4830
|
+
var next = Math.max(240, Math.min(maxWidth, startWidth + (startX - moveEvent.clientX)));
|
|
4831
|
+
setDirectoryWidth(next);
|
|
4832
|
+
}
|
|
4833
|
+
function finish() {
|
|
4834
|
+
document.removeEventListener('pointermove', move);
|
|
4835
|
+
document.removeEventListener('pointerup', finish);
|
|
4836
|
+
document.removeEventListener('pointercancel', finish);
|
|
4837
|
+
document.body.style.cursor = '';
|
|
4838
|
+
document.body.style.userSelect = '';
|
|
4839
|
+
}
|
|
4840
|
+
document.addEventListener('pointermove', move);
|
|
4841
|
+
document.addEventListener('pointerup', finish);
|
|
4842
|
+
document.addEventListener('pointercancel', finish);
|
|
4843
|
+
}
|
|
4452
4844
|
function restoreDirectoryTab() {
|
|
4453
4845
|
if (!sidebar || typeof sidebar.activateTab !== 'function' || !directoryTabId) return;
|
|
4454
4846
|
setTimeout(function () {
|
|
@@ -4667,19 +5059,25 @@ function fileIconSpec(name, isDir) {
|
|
|
4667
5059
|
|
|
4668
5060
|
function submitNew(side, dirPath, name, isDir) {
|
|
4669
5061
|
var target = joinChildPath(dirPath, name, side);
|
|
5062
|
+
var docKind = documentKindOf(name);
|
|
4670
5063
|
var promise;
|
|
4671
|
-
if (
|
|
4672
|
-
promise =
|
|
5064
|
+
if (isDir) {
|
|
5065
|
+
promise = side === 'remote'
|
|
4673
5066
|
? apiCall('fs.mkdir', Object.assign({ path: target }, scopeParams(scopeRef)))
|
|
4674
|
-
: apiCall('
|
|
5067
|
+
: apiCall('local.mkdir', { path: dirPath, name: name });
|
|
5068
|
+
} else if (docKind) {
|
|
5069
|
+
promise = side === 'remote'
|
|
5070
|
+
? apiCall('docs.create', Object.assign({ path: target, kind: docKind }, scopeParams(scopeRef)))
|
|
5071
|
+
: apiCall('docs.create', { path: target, kind: docKind });
|
|
4675
5072
|
} else {
|
|
4676
|
-
promise =
|
|
4677
|
-
? apiCall('
|
|
5073
|
+
promise = side === 'remote'
|
|
5074
|
+
? apiCall('fs.write', Object.assign({ path: target, content: '' }, scopeParams(scopeRef)))
|
|
4678
5075
|
: apiCall('local.newFile', { path: target });
|
|
4679
5076
|
}
|
|
4680
5077
|
promise.then(function () {
|
|
4681
5078
|
showNotice('success', t('newDone'));
|
|
4682
5079
|
refreshTree();
|
|
5080
|
+
if (!isDir && docKind) openInternalFile(side, target);
|
|
4683
5081
|
}).catch(noticeError);
|
|
4684
5082
|
}
|
|
4685
5083
|
|
|
@@ -4860,7 +5258,7 @@ function fileIconSpec(name, isDir) {
|
|
|
4860
5258
|
}, [searchOpen, searchQuery, selectedKey, version]);
|
|
4861
5259
|
|
|
4862
5260
|
if (!selected) {
|
|
4863
|
-
return h('div', { className: 'rw-workbench rw-workspace' },
|
|
5261
|
+
return h('div', { className: 'rw-workbench rw-workspace', ref: workbenchRef },
|
|
4864
5262
|
h('div', { className: 'rw-tree-scroll' },
|
|
4865
5263
|
data.data.loading
|
|
4866
5264
|
? h('div', { className: 'rw-row-msg' }, Spinner(13), t('loading'))
|
|
@@ -4923,14 +5321,32 @@ function fileIconSpec(name, isDir) {
|
|
|
4923
5321
|
selectedPath: selectedEntry && selectedEntry.side === 'local' ? selectedEntry.path : '',
|
|
4924
5322
|
onSelectPath: function (info) { setSelectedEntry(Object.assign({ side: 'local' }, info)); },
|
|
4925
5323
|
onSelect: function (filePath) { openByMode('local', filePath); },
|
|
5324
|
+
refreshTick: localTick,
|
|
4926
5325
|
onContextMenu: function (info) { setSelectedEntry(Object.assign({ side: 'local' }, info)); openContextMenu('local', info); },
|
|
4927
5326
|
}))
|
|
4928
5327
|
: null);
|
|
4929
5328
|
}
|
|
4930
5329
|
|
|
4931
5330
|
var activeFile = openFiles.filter(function (file) { return file.key === activeFileKey; })[0] || null;
|
|
5331
|
+
var activeDocKind = activeFile ? documentKindOf(activeFile.name) : '';
|
|
4932
5332
|
var contentArea = activeFile
|
|
4933
|
-
?
|
|
5333
|
+
? (activeDocKind
|
|
5334
|
+
? h(DocumentPane, {
|
|
5335
|
+
ctx: ctx, t: t, locale: locale,
|
|
5336
|
+
scope: activeFile.side === 'remote' ? sourceScopeOf(selected) : undefined,
|
|
5337
|
+
path: activeFile.path,
|
|
5338
|
+
local: activeFile.side === 'local',
|
|
5339
|
+
onSaved: refreshTree,
|
|
5340
|
+
onDirtyChange: function (dirty) {
|
|
5341
|
+
setFileDirty(function (previous) {
|
|
5342
|
+
if (Boolean(previous[activeFile.key]) === Boolean(dirty)) return previous;
|
|
5343
|
+
var next = Object.assign({}, previous);
|
|
5344
|
+
next[activeFile.key] = Boolean(dirty);
|
|
5345
|
+
return next;
|
|
5346
|
+
});
|
|
5347
|
+
},
|
|
5348
|
+
})
|
|
5349
|
+
: h(RemoteFileEditor, {
|
|
4934
5350
|
ctx: ctx, t: t, locale: locale,
|
|
4935
5351
|
scope: activeFile.side === 'remote' ? sourceScopeOf(selected) : undefined,
|
|
4936
5352
|
path: activeFile.path,
|
|
@@ -4945,7 +5361,7 @@ function fileIconSpec(name, isDir) {
|
|
|
4945
5361
|
return next;
|
|
4946
5362
|
});
|
|
4947
5363
|
},
|
|
4948
|
-
})
|
|
5364
|
+
}))
|
|
4949
5365
|
: h('div', { className: 'rw-placeholder' },
|
|
4950
5366
|
h('span', { style: { display: 'inline-flex', opacity: 0.7 } }, IconFolder(30, false)),
|
|
4951
5367
|
h('div', { className: 'rw-placeholder-title' }, t('openFileTitle')),
|
|
@@ -5024,7 +5440,17 @@ function fileIconSpec(name, isDir) {
|
|
|
5024
5440
|
: null,
|
|
5025
5441
|
h('div', { className: 'rw-content-body' }, contentArea)),
|
|
5026
5442
|
directoryOpen
|
|
5027
|
-
? h('div', {
|
|
5443
|
+
? h('div', {
|
|
5444
|
+
className: 'rw-pane-resizer',
|
|
5445
|
+
role: 'separator',
|
|
5446
|
+
'aria-orientation': 'vertical',
|
|
5447
|
+
title: t('resizeDirectory'),
|
|
5448
|
+
onPointerDown: beginResize,
|
|
5449
|
+
onDoubleClick: function () { setDirectoryWidth(300); },
|
|
5450
|
+
})
|
|
5451
|
+
: null,
|
|
5452
|
+
directoryOpen
|
|
5453
|
+
? h('div', { className: 'rw-directory-pane', style: { flexBasis: directoryWidth + 'px' } },
|
|
5028
5454
|
h('div', { className: 'rw-directory-head' },
|
|
5029
5455
|
IconFolder(11, false),
|
|
5030
5456
|
h('span', { style: { flex: '1 1 auto', minWidth: 0 } }, remoteOnly ? t('remoteTabTitle') : t('tabTitle')),
|