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/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 300px;max-width:48%;min-height:0;border-left:1px solid var(--dsw-alias-border-l1)}',
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: true, error: null, entries: existing.entries || [], loaded: existing.loaded });
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 (side === 'remote') {
4672
- promise = isDir
5064
+ if (isDir) {
5065
+ promise = side === 'remote'
4673
5066
  ? apiCall('fs.mkdir', Object.assign({ path: target }, scopeParams(scopeRef)))
4674
- : apiCall('fs.write', Object.assign({ path: target, content: '' }, scopeParams(scopeRef)));
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 = isDir
4677
- ? apiCall('local.mkdir', { path: dirPath, name: name })
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
- ? h(RemoteFileEditor, {
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', { className: 'rw-directory-pane' },
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')),