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.
Files changed (3) hide show
  1. package/README.md +7 -2
  2. package/lib/client.js +242 -22
  3. 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. It renders the current draft, so unsaved edits
113
- preview immediately, and read-only/oversized Markdown files still preview.
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
+ `![shot](public/hero.png)` 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
  ![Markdown preview with source toggle](docs/screenshot-markdown.png)
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 `![x](img/my%20shot.png?raw=1)` 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
- else
4319
- out.push(h('img', { key: key, src: url, alt: String(node.alt), style: MD_STYLE.image, loading: 'lazy' }));
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' ? S.renderMarkdown(props.value) : null))
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.7",
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",