dsh-workspace-ide 1.8.7 → 1.8.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -2
- package/lib/client.js +242 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -109,8 +109,12 @@ standard:
|
|
|
109
109
|
with `Cmd/Ctrl+Shift+V`. The preview is built from React elements — never injected HTML — and
|
|
110
110
|
covers headings, fenced code blocks (highlighted by the same engine, in the selected theme),
|
|
111
111
|
ordered/unordered and task lists, blockquotes, GFM tables, links, images and rules;
|
|
112
|
-
`javascript:`/`data:` URLs are dropped.
|
|
113
|
-
|
|
112
|
+
`javascript:`/`data:` URLs are dropped. Relative images are resolved against the
|
|
113
|
+
Markdown file's folder and served through the authenticated Host image route, so
|
|
114
|
+
`` renders a real workspace file in the preview; remote `https:`
|
|
115
|
+
images keep their direct URL, and paths escaping the workspace are rejected. It renders
|
|
116
|
+
the current draft, so unsaved edits preview immediately, and read-only/oversized Markdown
|
|
117
|
+
files still preview.
|
|
114
118
|
|
|
115
119
|

|
|
116
120
|
|
|
@@ -223,6 +227,7 @@ node scripts/build-client.mjs --check # fail when lib/client.js is stale
|
|
|
223
227
|
node --check lib/client.js
|
|
224
228
|
node --test test/host/ # Host route unit tests (node:test, 118)
|
|
225
229
|
node test/smoke/client-smoke.mjs # headless client module/face smoke test (43 checks)
|
|
230
|
+
python3 scripts/check-screenshots.py # README screenshots are non-blank renders
|
|
226
231
|
node --test test/review/*.test.mjs # independent adversarial + contract suites (78)
|
|
227
232
|
python3 scripts/screenshots/build-screenshots.py # rebuild docs/ screenshots (mock data)
|
|
228
233
|
```
|
package/lib/client.js
CHANGED
|
@@ -3823,6 +3823,127 @@ function mdSafeUrl(raw) {
|
|
|
3823
3823
|
return null;
|
|
3824
3824
|
return url;
|
|
3825
3825
|
}
|
|
3826
|
+
/**
|
|
3827
|
+
* Resolve a Markdown image URL to a workspace-relative file path. Remote/
|
|
3828
|
+
* protocol-relative/anchor URLs answer null; `..` escaping the workspace root
|
|
3829
|
+
* answers null. Query/fragment suffixes are ignored and percent escapes are
|
|
3830
|
+
* decoded, so `` resolves to `img/my shot.png`.
|
|
3831
|
+
* @param {unknown} raw - Markdown image URL.
|
|
3832
|
+
* @param {unknown} filePath - Markdown file path relative to the workspace root.
|
|
3833
|
+
* @returns {string|null} normalized workspace-relative path.
|
|
3834
|
+
*/
|
|
3835
|
+
function mdImagePath(raw, filePath) {
|
|
3836
|
+
if (typeof raw !== 'string' || typeof filePath !== 'string')
|
|
3837
|
+
return null;
|
|
3838
|
+
let url = raw.trim();
|
|
3839
|
+
if (url.length === 0 || /^(?:[a-z][a-z0-9+.-]*:|\/\/|#)/i.test(url))
|
|
3840
|
+
return null;
|
|
3841
|
+
const suffix = url.search(/[?#]/);
|
|
3842
|
+
if (suffix >= 0)
|
|
3843
|
+
url = url.slice(0, suffix);
|
|
3844
|
+
if (url.length === 0)
|
|
3845
|
+
return null;
|
|
3846
|
+
let decoded;
|
|
3847
|
+
try {
|
|
3848
|
+
decoded = decodeURIComponent(url);
|
|
3849
|
+
}
|
|
3850
|
+
catch (error) {
|
|
3851
|
+
return null;
|
|
3852
|
+
}
|
|
3853
|
+
if (decoded.indexOf('\u0000') !== -1)
|
|
3854
|
+
return null;
|
|
3855
|
+
decoded = decoded.replace(/\\/g, '/');
|
|
3856
|
+
if (decoded.length === 0)
|
|
3857
|
+
return null;
|
|
3858
|
+
const directory = filePath.indexOf('/') >= 0 ? filePath.slice(0, filePath.lastIndexOf('/')) : '';
|
|
3859
|
+
const source = decoded.charAt(0) === '/' ? decoded.slice(1) : (directory.length > 0 ? directory + '/' + decoded : decoded);
|
|
3860
|
+
const parts = [];
|
|
3861
|
+
const segments = source.split('/');
|
|
3862
|
+
for (let index = 0; index < segments.length; index = index + 1) {
|
|
3863
|
+
const segment = segments[index];
|
|
3864
|
+
if (segment.length === 0 || segment === '.')
|
|
3865
|
+
continue;
|
|
3866
|
+
if (segment === '..') {
|
|
3867
|
+
if (parts.length === 0)
|
|
3868
|
+
return null;
|
|
3869
|
+
parts.pop();
|
|
3870
|
+
continue;
|
|
3871
|
+
}
|
|
3872
|
+
parts.push(segment);
|
|
3873
|
+
}
|
|
3874
|
+
return parts.length > 0 ? parts.join('/') : null;
|
|
3875
|
+
}
|
|
3876
|
+
/**
|
|
3877
|
+
* Collect the workspace-relative images referenced by a Markdown document.
|
|
3878
|
+
* Remote images, anchors and paths escaping the workspace are omitted. Rows are
|
|
3879
|
+
* unique by resolved path, in document order.
|
|
3880
|
+
* @param {unknown} source - Markdown source.
|
|
3881
|
+
* @param {unknown} filePath - Markdown file path relative to the workspace root.
|
|
3882
|
+
* @returns {Array<{url: string, path: string}>} image rows for the viewer to load.
|
|
3883
|
+
*/
|
|
3884
|
+
function collectMarkdownImages(source, filePath) {
|
|
3885
|
+
const text = typeof source === 'string' ? source : '';
|
|
3886
|
+
if (text.length === 0)
|
|
3887
|
+
return [];
|
|
3888
|
+
const rows = [];
|
|
3889
|
+
const seen = {};
|
|
3890
|
+
function visitInline(nodes) {
|
|
3891
|
+
for (let index = 0; index < nodes.length; index = index + 1) {
|
|
3892
|
+
const node = nodes[index];
|
|
3893
|
+
if (node === null || typeof node !== 'object')
|
|
3894
|
+
continue;
|
|
3895
|
+
if (node.type === 'image') {
|
|
3896
|
+
const url = mdSafeUrl(node.url);
|
|
3897
|
+
if (url !== null) {
|
|
3898
|
+
const resolved = mdImagePath(url, filePath);
|
|
3899
|
+
if (resolved !== null && seen[resolved] !== true) {
|
|
3900
|
+
seen[resolved] = true;
|
|
3901
|
+
rows.push({ url: url, path: resolved });
|
|
3902
|
+
}
|
|
3903
|
+
}
|
|
3904
|
+
}
|
|
3905
|
+
if (Array.isArray(node.children))
|
|
3906
|
+
visitInline(node.children);
|
|
3907
|
+
}
|
|
3908
|
+
}
|
|
3909
|
+
function visitBlocks(blocks) {
|
|
3910
|
+
for (let index = 0; index < blocks.length; index = index + 1) {
|
|
3911
|
+
const block = blocks[index];
|
|
3912
|
+
if (block === null || typeof block !== 'object')
|
|
3913
|
+
continue;
|
|
3914
|
+
if (Array.isArray(block.inline))
|
|
3915
|
+
visitInline(block.inline);
|
|
3916
|
+
if (Array.isArray(block.blocks))
|
|
3917
|
+
visitBlocks(block.blocks);
|
|
3918
|
+
if (Array.isArray(block.items)) {
|
|
3919
|
+
for (let itemIndex = 0; itemIndex < block.items.length; itemIndex = itemIndex + 1) {
|
|
3920
|
+
const item = block.items[itemIndex];
|
|
3921
|
+
if (item === null || typeof item !== 'object')
|
|
3922
|
+
continue;
|
|
3923
|
+
if (Array.isArray(item.inline))
|
|
3924
|
+
visitInline(item.inline);
|
|
3925
|
+
if (item.nested !== null && item.nested !== undefined && typeof item.nested === 'object')
|
|
3926
|
+
visitBlocks([item.nested]);
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
if (Array.isArray(block.header)) {
|
|
3930
|
+
for (let cellIndex = 0; cellIndex < block.header.length; cellIndex = cellIndex + 1)
|
|
3931
|
+
visitInline(block.header[cellIndex]);
|
|
3932
|
+
}
|
|
3933
|
+
if (Array.isArray(block.rows)) {
|
|
3934
|
+
for (let rowIndex = 0; rowIndex < block.rows.length; rowIndex = rowIndex + 1) {
|
|
3935
|
+
const row = block.rows[rowIndex];
|
|
3936
|
+
if (!Array.isArray(row))
|
|
3937
|
+
continue;
|
|
3938
|
+
for (let cellIndex = 0; cellIndex < row.length; cellIndex = cellIndex + 1)
|
|
3939
|
+
visitInline(row[cellIndex]);
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
visitBlocks(parseMarkdown(text));
|
|
3945
|
+
return rows;
|
|
3946
|
+
}
|
|
3826
3947
|
function mdPlainInline(nodes) {
|
|
3827
3948
|
let out = '';
|
|
3828
3949
|
for (let index = 0; index < nodes.length; index = index + 1) {
|
|
@@ -4261,6 +4382,15 @@ const MD_STYLE = {
|
|
|
4261
4382
|
taskBox: { marginRight: '6px', verticalAlign: 'middle' },
|
|
4262
4383
|
link: { color: C.accent, textDecoration: 'underline' },
|
|
4263
4384
|
image: { maxWidth: '100%', borderRadius: '6px', margin: '6px 0', display: 'block' },
|
|
4385
|
+
imagePending: {
|
|
4386
|
+
display: 'inline-block',
|
|
4387
|
+
margin: '3px 0',
|
|
4388
|
+
padding: '2px 7px',
|
|
4389
|
+
border: '1px dashed ' + C.border1,
|
|
4390
|
+
borderRadius: '4px',
|
|
4391
|
+
color: C.textSecondary,
|
|
4392
|
+
fontSize: '12px',
|
|
4393
|
+
},
|
|
4264
4394
|
table: { width: '100%', borderCollapse: 'collapse', margin: '0 0 14px', fontSize: '12.5px', lineHeight: '1.6' },
|
|
4265
4395
|
tableCell: { border: '1px solid ' + C.border1, padding: '5px 10px', textAlign: 'left', verticalAlign: 'top' },
|
|
4266
4396
|
tableHeadCell: { border: '1px solid ' + C.border1, padding: '5px 10px', textAlign: 'left', background: C.layer1, fontWeight: '600' },
|
|
@@ -4272,7 +4402,7 @@ const MD_STYLE = {
|
|
|
4272
4402
|
* @param {string} keyPrefix - stable key prefix.
|
|
4273
4403
|
* @returns {Array} React children.
|
|
4274
4404
|
*/
|
|
4275
|
-
function renderMarkdownInline(nodes, keyPrefix) {
|
|
4405
|
+
function renderMarkdownInline(nodes, keyPrefix, options) {
|
|
4276
4406
|
const out = [];
|
|
4277
4407
|
for (let index = 0; index < nodes.length; index = index + 1) {
|
|
4278
4408
|
const node = nodes[index];
|
|
@@ -4292,31 +4422,44 @@ function renderMarkdownInline(nodes, keyPrefix) {
|
|
|
4292
4422
|
continue;
|
|
4293
4423
|
}
|
|
4294
4424
|
if (node.type === 'strong') {
|
|
4295
|
-
out.push(h('strong', { key: key }, renderMarkdownInline(node.children || [], key)));
|
|
4425
|
+
out.push(h('strong', { key: key }, renderMarkdownInline(node.children || [], key, options)));
|
|
4296
4426
|
continue;
|
|
4297
4427
|
}
|
|
4298
4428
|
if (node.type === 'em') {
|
|
4299
|
-
out.push(h('em', { key: key }, renderMarkdownInline(node.children || [], key)));
|
|
4429
|
+
out.push(h('em', { key: key }, renderMarkdownInline(node.children || [], key, options)));
|
|
4300
4430
|
continue;
|
|
4301
4431
|
}
|
|
4302
4432
|
if (node.type === 'del') {
|
|
4303
|
-
out.push(h('del', { key: key }, renderMarkdownInline(node.children || [], key)));
|
|
4433
|
+
out.push(h('del', { key: key }, renderMarkdownInline(node.children || [], key, options)));
|
|
4304
4434
|
continue;
|
|
4305
4435
|
}
|
|
4306
4436
|
if (node.type === 'link') {
|
|
4307
4437
|
const url = mdSafeUrl(node.url);
|
|
4308
4438
|
if (url === null)
|
|
4309
|
-
out.push(h('span', { key: key }, renderMarkdownInline(node.children || [], key)));
|
|
4439
|
+
out.push(h('span', { key: key }, renderMarkdownInline(node.children || [], key, options)));
|
|
4310
4440
|
else
|
|
4311
|
-
out.push(h('a', { key: key, href: url, target: '_blank', rel: 'noreferrer noopener', style: MD_STYLE.link }, renderMarkdownInline(node.children || [], key)));
|
|
4441
|
+
out.push(h('a', { key: key, href: url, target: '_blank', rel: 'noreferrer noopener', style: MD_STYLE.link }, renderMarkdownInline(node.children || [], key, options)));
|
|
4312
4442
|
continue;
|
|
4313
4443
|
}
|
|
4314
4444
|
if (node.type === 'image') {
|
|
4315
4445
|
const url = mdSafeUrl(node.url);
|
|
4316
|
-
if (url === null)
|
|
4446
|
+
if (url === null) {
|
|
4317
4447
|
out.push(h('span', { key: key, style: MD_STYLE.link }, String(node.alt)));
|
|
4318
|
-
|
|
4319
|
-
|
|
4448
|
+
continue;
|
|
4449
|
+
}
|
|
4450
|
+
// Remote images load directly; workspace-relative paths ask the Host,
|
|
4451
|
+
// because a DSH page origin has no access to the user's workspace files.
|
|
4452
|
+
if (options !== undefined && typeof options.resolveImage === 'function' && /^(?:https?:)?\/\//i.test(url) === false) {
|
|
4453
|
+
const resolved = options.resolveImage(url);
|
|
4454
|
+
if (typeof resolved === 'string' && resolved.length > 0) {
|
|
4455
|
+
out.push(h('img', { key: key, src: resolved, alt: String(node.alt), style: MD_STYLE.image, loading: 'lazy' }));
|
|
4456
|
+
}
|
|
4457
|
+
else {
|
|
4458
|
+
out.push(h('span', { key: key, style: MD_STYLE.imagePending, 'data-md-image-pending': url }, String(node.alt)));
|
|
4459
|
+
}
|
|
4460
|
+
continue;
|
|
4461
|
+
}
|
|
4462
|
+
out.push(h('img', { key: key, src: url, alt: String(node.alt), style: MD_STYLE.image, loading: 'lazy' }));
|
|
4320
4463
|
continue;
|
|
4321
4464
|
}
|
|
4322
4465
|
}
|
|
@@ -4352,17 +4495,17 @@ function renderMarkdownCode(block, key) {
|
|
|
4352
4495
|
* @param {string} keyPrefix - stable key prefix.
|
|
4353
4496
|
* @returns {Array} React children.
|
|
4354
4497
|
*/
|
|
4355
|
-
function renderMarkdownBlocks(blocks, keyPrefix) {
|
|
4498
|
+
function renderMarkdownBlocks(blocks, keyPrefix, options) {
|
|
4356
4499
|
const out = [];
|
|
4357
4500
|
for (let index = 0; index < blocks.length; index = index + 1) {
|
|
4358
4501
|
const block = blocks[index];
|
|
4359
4502
|
const key = keyPrefix + '-' + String(index);
|
|
4360
4503
|
if (block.type === 'heading') {
|
|
4361
|
-
out.push(h('h' + String(block.depth), { key: key, style: MD_STYLE.heading(block.depth) }, renderMarkdownInline(block.inline || [], key)));
|
|
4504
|
+
out.push(h('h' + String(block.depth), { key: key, style: MD_STYLE.heading(block.depth) }, renderMarkdownInline(block.inline || [], key, options)));
|
|
4362
4505
|
continue;
|
|
4363
4506
|
}
|
|
4364
4507
|
if (block.type === 'paragraph') {
|
|
4365
|
-
out.push(h('p', { key: key, style: MD_STYLE.paragraph }, renderMarkdownInline(block.inline || [], key)));
|
|
4508
|
+
out.push(h('p', { key: key, style: MD_STYLE.paragraph }, renderMarkdownInline(block.inline || [], key, options)));
|
|
4366
4509
|
continue;
|
|
4367
4510
|
}
|
|
4368
4511
|
if (block.type === 'code') {
|
|
@@ -4370,7 +4513,7 @@ function renderMarkdownBlocks(blocks, keyPrefix) {
|
|
|
4370
4513
|
continue;
|
|
4371
4514
|
}
|
|
4372
4515
|
if (block.type === 'blockquote') {
|
|
4373
|
-
out.push(h('blockquote', { key: key, style: MD_STYLE.blockquote }, renderMarkdownBlocks(block.blocks || [], key)));
|
|
4516
|
+
out.push(h('blockquote', { key: key, style: MD_STYLE.blockquote }, renderMarkdownBlocks(block.blocks || [], key, options)));
|
|
4374
4517
|
continue;
|
|
4375
4518
|
}
|
|
4376
4519
|
if (block.type === 'list') {
|
|
@@ -4387,9 +4530,9 @@ function renderMarkdownBlocks(blocks, keyPrefix) {
|
|
|
4387
4530
|
style: MD_STYLE.taskBox,
|
|
4388
4531
|
}));
|
|
4389
4532
|
}
|
|
4390
|
-
children.push.apply(children, renderMarkdownInline(item.inline, itemKey));
|
|
4533
|
+
children.push.apply(children, renderMarkdownInline(item.inline, itemKey, options));
|
|
4391
4534
|
if (item.nested !== null && item.nested !== undefined) {
|
|
4392
|
-
children.push(h(item.nested.ordered ? 'ol' : 'ul', { key: itemKey + '-nested', style: MD_STYLE.nestedList }, renderMarkdownListItems(item.nested, itemKey + '-nested')));
|
|
4535
|
+
children.push(h(item.nested.ordered ? 'ol' : 'ul', { key: itemKey + '-nested', style: MD_STYLE.nestedList }, renderMarkdownListItems(item.nested, itemKey + '-nested', options)));
|
|
4393
4536
|
}
|
|
4394
4537
|
return h('li', { key: itemKey, style: MD_STYLE.listItem }, children);
|
|
4395
4538
|
});
|
|
@@ -4399,12 +4542,12 @@ function renderMarkdownBlocks(blocks, keyPrefix) {
|
|
|
4399
4542
|
if (block.type === 'table') {
|
|
4400
4543
|
const align = block.align || [];
|
|
4401
4544
|
const headerCells = (block.header || []).map(function (cell, cellIndex) {
|
|
4402
|
-
return h('th', { key: key + '-h' + String(cellIndex), style: Object.assign({}, MD_STYLE.tableHeadCell, align[cellIndex] ? { textAlign: align[cellIndex] } : null) }, renderMarkdownInline(cell, key + '-h' + String(cellIndex)));
|
|
4545
|
+
return h('th', { key: key + '-h' + String(cellIndex), style: Object.assign({}, MD_STYLE.tableHeadCell, align[cellIndex] ? { textAlign: align[cellIndex] } : null) }, renderMarkdownInline(cell, key + '-h' + String(cellIndex), options));
|
|
4403
4546
|
});
|
|
4404
4547
|
const bodyRows = (block.rows || []).map(function (row, rowIndex) {
|
|
4405
4548
|
const rowKey = key + '-r' + String(rowIndex);
|
|
4406
4549
|
const cells = row.map(function (cell, cellIndex) {
|
|
4407
|
-
return h('td', { key: rowKey + '-c' + String(cellIndex), style: Object.assign({}, MD_STYLE.tableCell, align[cellIndex] ? { textAlign: align[cellIndex] } : null) }, renderMarkdownInline(cell, rowKey + '-c' + String(cellIndex)));
|
|
4550
|
+
return h('td', { key: rowKey + '-c' + String(cellIndex), style: Object.assign({}, MD_STYLE.tableCell, align[cellIndex] ? { textAlign: align[cellIndex] } : null) }, renderMarkdownInline(cell, rowKey + '-c' + String(cellIndex), options));
|
|
4408
4551
|
});
|
|
4409
4552
|
return h('tr', { key: rowKey }, cells);
|
|
4410
4553
|
});
|
|
@@ -4419,10 +4562,10 @@ function renderMarkdownBlocks(blocks, keyPrefix) {
|
|
|
4419
4562
|
return out;
|
|
4420
4563
|
}
|
|
4421
4564
|
/** Shallow helper so nested lists reuse the item renderer without duplication. */
|
|
4422
|
-
function renderMarkdownListItems(listBlock, keyPrefix) {
|
|
4565
|
+
function renderMarkdownListItems(listBlock, keyPrefix, options) {
|
|
4423
4566
|
return listBlock.items.map(function (item, itemIndex) {
|
|
4424
4567
|
const itemKey = keyPrefix + '-' + String(itemIndex);
|
|
4425
|
-
return h('li', { key: itemKey, style: MD_STYLE.listItem }, renderMarkdownInline(item.inline, itemKey), item.nested ? h(item.nested.ordered ? 'ol' : 'ul', { style: MD_STYLE.nestedList }, renderMarkdownListItems(item.nested, itemKey + '-n')) : null);
|
|
4568
|
+
return h('li', { key: itemKey, style: MD_STYLE.listItem }, renderMarkdownInline(item.inline, itemKey, options), item.nested ? h(item.nested.ordered ? 'ol' : 'ul', { style: MD_STYLE.nestedList }, renderMarkdownListItems(item.nested, itemKey + '-n', options)) : null);
|
|
4426
4569
|
});
|
|
4427
4570
|
}
|
|
4428
4571
|
/**
|
|
@@ -4430,15 +4573,17 @@ function renderMarkdownListItems(listBlock, keyPrefix) {
|
|
|
4430
4573
|
* @param {unknown} source - markdown source.
|
|
4431
4574
|
* @returns {Array} React children for the preview body.
|
|
4432
4575
|
*/
|
|
4433
|
-
function renderMarkdown(source) {
|
|
4576
|
+
function renderMarkdown(source, options) {
|
|
4434
4577
|
const text = typeof source === 'string' ? source : '';
|
|
4435
|
-
return renderMarkdownBlocks(parseMarkdown(text), 'md');
|
|
4578
|
+
return renderMarkdownBlocks(parseMarkdown(text), 'md', options);
|
|
4436
4579
|
}
|
|
4437
4580
|
S.MD_MAX_SOURCE_CHARS = MD_MAX_SOURCE_CHARS;
|
|
4438
4581
|
S.MD_STYLE = MD_STYLE;
|
|
4439
4582
|
S.parseMarkdown = parseMarkdown;
|
|
4440
4583
|
S.parseMarkdownInline = parseMarkdownInline;
|
|
4441
4584
|
S.renderMarkdown = renderMarkdown;
|
|
4585
|
+
S.mdImagePath = mdImagePath;
|
|
4586
|
+
S.collectMarkdownImages = collectMarkdownImages;
|
|
4442
4587
|
|
|
4443
4588
|
/* ─── src/client/10-shell.ts ─────────────────────────────────────────────────── */
|
|
4444
4589
|
"use strict";
|
|
@@ -7365,6 +7510,9 @@ function FileViewer(props) {
|
|
|
7365
7510
|
const bracketRanges = Array.isArray(props.bracketRanges) ? props.bracketRanges : [];
|
|
7366
7511
|
const bracketRef = useRef(null);
|
|
7367
7512
|
const [editorScrollTop, setEditorScrollTop] = useState(0);
|
|
7513
|
+
const [mdImages, setMdImages] = useState({});
|
|
7514
|
+
const mdImagePendingRef = useRef({});
|
|
7515
|
+
const mdImageCacheRef = useRef({});
|
|
7368
7516
|
const findHighlight = useMemo(function () {
|
|
7369
7517
|
if (findOpen === false || findMatches.length === 0 || readOnly === true)
|
|
7370
7518
|
return null;
|
|
@@ -7451,6 +7599,65 @@ function FileViewer(props) {
|
|
|
7451
7599
|
const isMarkdown = props.language === 'markdown';
|
|
7452
7600
|
const markdownMode = props.markdownMode === 'preview' ? 'preview' : 'source';
|
|
7453
7601
|
const previewMode = isMarkdown && markdownMode === 'preview';
|
|
7602
|
+
const mdImageRoot = typeof props.root === 'string' ? props.root : '';
|
|
7603
|
+
const mdImageEntries = useMemo(function () {
|
|
7604
|
+
if (previewMode !== true || typeof S.collectMarkdownImages !== 'function')
|
|
7605
|
+
return [];
|
|
7606
|
+
return S.collectMarkdownImages(props.value, props.path);
|
|
7607
|
+
}, [previewMode, props.value, props.path]);
|
|
7608
|
+
// A different Markdown file resolves the same relative URL against another
|
|
7609
|
+
// directory, so drop the per-file image cache when the open path changes.
|
|
7610
|
+
useEffect(function () {
|
|
7611
|
+
mdImagePendingRef.current = {};
|
|
7612
|
+
mdImageCacheRef.current = {};
|
|
7613
|
+
setMdImages({});
|
|
7614
|
+
}, [props.path]);
|
|
7615
|
+
// Markdown images that live in the workspace are read through the Host image
|
|
7616
|
+
// route and turned into data URLs; a page origin cannot fetch file paths.
|
|
7617
|
+
useEffect(function () {
|
|
7618
|
+
if (previewMode !== true || mdImageEntries.length === 0)
|
|
7619
|
+
return undefined;
|
|
7620
|
+
let cancelled = false;
|
|
7621
|
+
for (let index = 0; index < mdImageEntries.length; index = index + 1) {
|
|
7622
|
+
const entry = mdImageEntries[index];
|
|
7623
|
+
if (entry === null || entry === undefined || typeof entry.path !== 'string' || entry.path.length === 0)
|
|
7624
|
+
continue;
|
|
7625
|
+
const cacheKey = entry.path;
|
|
7626
|
+
if (mdImageCacheRef.current[cacheKey] !== undefined || mdImagePendingRef.current[cacheKey] === true)
|
|
7627
|
+
continue;
|
|
7628
|
+
mdImagePendingRef.current[cacheKey] = true;
|
|
7629
|
+
S.requestJson(S.apiUrl(ROUTES.image, { root: mdImageRoot, path: cacheKey }))
|
|
7630
|
+
.then(function (payload) {
|
|
7631
|
+
if (cancelled)
|
|
7632
|
+
return;
|
|
7633
|
+
const mime = payload !== null && payload !== undefined && typeof payload.mime === 'string' ? payload.mime : '';
|
|
7634
|
+
const base64 = payload !== null && payload !== undefined && typeof payload.base64 === 'string' ? payload.base64 : '';
|
|
7635
|
+
const src = mime.length > 0 && base64.length > 0 && typeof S.buildImageDataUrl === 'function'
|
|
7636
|
+
? S.buildImageDataUrl(mime, base64)
|
|
7637
|
+
: null;
|
|
7638
|
+
if (typeof src !== 'string' || src.length === 0)
|
|
7639
|
+
return;
|
|
7640
|
+
mdImageCacheRef.current[cacheKey] = src;
|
|
7641
|
+
setMdImages(function (previous) {
|
|
7642
|
+
if (previous[cacheKey] === src)
|
|
7643
|
+
return previous;
|
|
7644
|
+
const next = Object.assign({}, previous);
|
|
7645
|
+
next[cacheKey] = src;
|
|
7646
|
+
return next;
|
|
7647
|
+
});
|
|
7648
|
+
})
|
|
7649
|
+
.catch(function () {
|
|
7650
|
+
// Leave the dashed alt placeholder in place; a missing/permission-
|
|
7651
|
+
// denied image must not blank the whole preview.
|
|
7652
|
+
if (cancelled)
|
|
7653
|
+
return;
|
|
7654
|
+
mdImageCacheRef.current[cacheKey] = '';
|
|
7655
|
+
});
|
|
7656
|
+
}
|
|
7657
|
+
return function () {
|
|
7658
|
+
cancelled = true;
|
|
7659
|
+
};
|
|
7660
|
+
}, [previewMode, mdImageEntries, mdImageRoot]);
|
|
7454
7661
|
const isSvg = props.isSvg === true;
|
|
7455
7662
|
const svgMode = props.svgMode === 'source' ? 'source' : 'image';
|
|
7456
7663
|
const svgImageMode = isSvg && svgMode === 'image';
|
|
@@ -7758,7 +7965,17 @@ function FileViewer(props) {
|
|
|
7758
7965
|
tabIndex: 0,
|
|
7759
7966
|
'data-md-preview': '1',
|
|
7760
7967
|
onKeyDown: props.onKeyDown,
|
|
7761
|
-
}, h('div', { style: S.MD_STYLE.inner }, typeof S.renderMarkdown === 'function'
|
|
7968
|
+
}, h('div', { style: S.MD_STYLE.inner }, typeof S.renderMarkdown === 'function'
|
|
7969
|
+
? S.renderMarkdown(props.value, {
|
|
7970
|
+
resolveImage: function (url) {
|
|
7971
|
+
const imagePath = typeof S.mdImagePath === 'function' ? S.mdImagePath(url, props.path) : null;
|
|
7972
|
+
if (imagePath === null)
|
|
7973
|
+
return null;
|
|
7974
|
+
const resolved = mdImages[imagePath];
|
|
7975
|
+
return typeof resolved === 'string' && resolved.length > 0 ? resolved : null;
|
|
7976
|
+
},
|
|
7977
|
+
})
|
|
7978
|
+
: null))
|
|
7762
7979
|
: readOnly
|
|
7763
7980
|
? h('div', { style: ST_FILES.readOnlyScroll }, h('div', { style: wrap ? ST_FILES.readOnlyInnerWrap : ST_FILES.readOnlyInner }, wrap ? null : h('pre', { style: editorReadOnlyGutterStyle, 'aria-hidden': true }, numbersText), h('pre', {
|
|
7764
7981
|
style: editorReadOnlyCodeStyle,
|
|
@@ -9373,6 +9590,7 @@ function FilesView(props) {
|
|
|
9373
9590
|
else if (file.status === 'loaded') {
|
|
9374
9591
|
viewer = h(FileViewer, {
|
|
9375
9592
|
t: t,
|
|
9593
|
+
root: root,
|
|
9376
9594
|
path: selected,
|
|
9377
9595
|
value: draft,
|
|
9378
9596
|
lineCount: lineCount,
|
|
@@ -11278,6 +11496,8 @@ S.face = {
|
|
|
11278
11496
|
parseMarkdown: S.parseMarkdown,
|
|
11279
11497
|
parseMarkdownInline: S.parseMarkdownInline,
|
|
11280
11498
|
renderMarkdown: S.renderMarkdown,
|
|
11499
|
+
mdImagePath: S.mdImagePath,
|
|
11500
|
+
collectMarkdownImages: S.collectMarkdownImages,
|
|
11281
11501
|
MD_STYLE: S.MD_STYLE,
|
|
11282
11502
|
MD_MAX_SOURCE_CHARS: S.MD_MAX_SOURCE_CHARS,
|
|
11283
11503
|
ST: { core: S.ST_CORE, shell: S.ST_SHELL, files: S.ST_FILES, search: S.ST_SEARCH, git: S.ST_GIT },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-workspace-ide",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.8",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A JetBrains-inspired workspace IDE panel for DeepSeek Harness: file tree, TypeScript code editor with find & replace, Quick Open, tabs and syntax highlighting, full-text search, and git status / diff / history",
|
|
6
6
|
"type": "module",
|