feat: adapt to DSH 0.1.7-rc.1

Move the support line from 0.1.7-alpha.1 to 0.1.7-rc.1 (peer floor
`^0.1.7-rc.1`). 0.21.0-alpha.1 never reached npm, and once the code-card
labels below are fixed the same build crashes on alpha.1 (its DiffBlock
still reads the `labels.files` upstream deleted), so declaring only the
host we verify against is the honest floor — and from rc.1 on the host
enforces peers at boot anyway: a row whose `@deepseek-ai/dsh-*` peers do
not satisfy the runtime is silently disabled.

Dependencies: 28 devDependency pins and 14 peers move to 0.1.7-rc.1.
rc.1 also raised the vendored runtime (cordis 4.0.4, schemastery 3.18.4,
cordis-plugin-group 1.0.4, cordis-plugin-include 1.0.9) and publishes DSH
cross-references as exact versions, so bumping schemastery/cordis alone
left four unmet transitive peers — the three vendor packages are hoisted
into devDependencies, which is what makes `pnpm peers check` clean again.

One compile break: rc.1 added `CodeToolbarLabels` (codeLabel / wrapLabel /
unwrapLabel, all required), made DiffBlockLabels / ReadBlockLabels extend
it and deleted `DiffBlockLabels.files`. Fixing SideChatView then leaving
markdown on the legacy banner would render two different code-card styles
inside one side-chat view, so the plugin adopts the card everywhere:
`markdownTextProps()` now passes `labels.code.toolbarLabels` with the
host's own wording (codeBlock.title|wrap|unwrap, added to all 20
dictionaries; `sideChatBlockFiles` retires with the deleted footer).

Adopting the card surfaced a silent regression the suite caught: the new
toolbar shows `codeLabel` for languages the highlighter does not know, and
mermaid has no shiki grammar, so the info string no longer reaches the DOM
and the banner-text probe in mermaid.tsx matched nothing — mermaid fences
stopped being swapped for diagrams. Detection now matches the FENCE BODY
from the source that produced the render.

`tests/market-manifest.spec.ts` pins every DSH peer as a floating caret
range: an exact pin (the devDependency style) or a comparator without a
prerelease would fail rc.1's preflight and silently stop the plugin from
loading, and `pnpm peers check` cannot see that — it validates the
resolved dev tree, not the range shape the host reads.
This commit is contained in:
Menghuan1918
2026-09-23 22:24:56 +08:00
parent 2d2d78f3f2
commit bd11bad65c
36 changed files with 773 additions and 610 deletions
+3 -3
View File
@@ -19,7 +19,7 @@ import { useLayoutEffect, useMemo, useRef } from 'react'
import { createElement, type ReactNode } from 'react'
import DOMPurify from 'dompurify'
import { MarkdownText } from '@deepseek-ai/dsh-client-ui-primitives'
import { markdownTextProps } from './markdown-labels.tsx'
import { markdownTextProps, type MarkdownCopyLabels } from './markdown-labels.tsx'
import { resolveLocalMediaDest, rewriteLocalImageUrls } from './markdown-images.ts'
import {
analyzeHtmlSegment,
@@ -160,7 +160,7 @@ interface MarkdownSegmentProps {
text: string
hasMermaid: boolean
media: MarkdownHtmlMedia
codeLabels: { copyLabel: string; copiedLabel: string }
codeLabels: MarkdownCopyLabels
}
/**
@@ -218,7 +218,7 @@ type PreparedSegment =
interface MarkdownDocumentProps {
info: AnalyzedMarkdownHtml
media: MarkdownHtmlMedia
codeLabels: { copyLabel: string; copiedLabel: string }
codeLabels: MarkdownCopyLabels
}
/**
+16 -2
View File
@@ -121,6 +121,11 @@ interface ThreadCache {
interface RowLabels {
copyLabel: string
copiedLabel: string
/** Code-card chrome (DSH 0.1.7-rc.1's fence toolbar): the markdown rows read
* these three straight off this object, the diff / read cards mix them in. */
codeLabel: string
wrapLabel: string
unwrapLabel: string
thinkLabel: string
injectionLabel: string
terminal: TerminalBlockLabels
@@ -345,9 +350,18 @@ export function SideChatView(props: {
expand: (hidden: number) => t('sideChatBlockExpand', { hidden }),
expandAria: (hidden: number) => t('sideChatBlockExpandAria', { hidden }),
}
// DSH 0.1.7-rc.1 widened the diff / read cards into code cards and DELETED
// `DiffBlockLabels.files`, so the old "N files" footer is gone and these
// three strings are required by both cards.
const codeCard = {
codeLabel: t('codeBlockTitle'),
wrapLabel: t('codeBlockWrap'),
unwrapLabel: t('codeBlockUnwrap'),
}
return {
copyLabel: t('copy'),
copiedLabel: t('copied'),
...codeCard,
thinkLabel: t('sideChatThink'),
injectionLabel: t('sideChatInjection'),
terminal: {
@@ -362,8 +376,8 @@ export function SideChatView(props: {
done: t('sideChatBlockDone'),
noOutput: t('sideChatBlockNoOutput'),
},
diff: { ...shared, files: (count: number) => t('sideChatBlockFiles', { count }) },
read: { ...shared, window: (shown: number, total: number) => t('sideChatBlockWindow', { shown, total }) },
diff: { ...shared, ...codeCard },
read: { ...shared, ...codeCard, window: (shown: number, total: number) => t('sideChatBlockWindow', { shown, total }) },
}
}, [])
+7 -1
View File
@@ -363,7 +363,13 @@ export function TextEditor(props: FileViewerProps) {
// eslint-disable-next-line react-hooks/exhaustive-deps
[scope.sessionId, scope.cwd, path],
)
const codeLabels = { copyLabel: t('copy'), copiedLabel: t('copied') }
const codeLabels = {
copyLabel: t('copy'),
copiedLabel: t('copied'),
codeLabel: t('codeBlockTitle'),
wrapLabel: t('codeBlockWrap'),
unwrapLabel: t('codeBlockUnwrap'),
}
/**
* Selection popup for the markdown preview: a mouse-up inside the preview
+7 -1
View File
@@ -108,7 +108,13 @@ function PaneToggle(props: { on: boolean; label: string; title?: string; onClick
* lifted out); the plain path stays byte-for-byte for documents without
* any. */
function MdReadingView(props: { text: string }) {
const codeLabels = { copyLabel: t('copy'), copiedLabel: t('copied') }
const codeLabels = {
copyLabel: t('copy'),
copiedLabel: t('copied'),
codeLabel: t('codeBlockTitle'),
wrapLabel: t('codeBlockWrap'),
unwrapLabel: t('codeBlockUnwrap'),
}
const hasMermaid = splitMermaidBlocks(props.text).some((block) => block.kind === 'mermaid')
return (
<div className={css.paneBody}>
+3 -1
View File
@@ -361,7 +361,9 @@ export const ar: Record<string, string> = {
sideChatBlockFailed: 'فشل',
sideChatBlockDone: 'اكتمل',
sideChatBlockNoOutput: '(لا يوجد إخراج)',
sideChatBlockFiles: '{count} ملفات',
codeBlockTitle: 'كتلة التعليمات البرمجية',
codeBlockWrap: 'التفاف الأسطر',
codeBlockUnwrap: 'بدون التفاف للأسطر',
sideChatBlockWindow: '{shown} من {total} سطرًا',
sideChatConnDisconnected: 'انقطع الاتصال',
sideChatConnReconnect: 'إعادة الاتصال',
+3 -1
View File
@@ -346,7 +346,9 @@ export const de: Record<string, string> = {
sideChatBlockFailed: 'Fehlgeschlagen',
sideChatBlockDone: 'Fertig',
sideChatBlockNoOutput: '(keine Ausgabe)',
sideChatBlockFiles: '{count} Dateien',
codeBlockTitle: 'Codeblock',
codeBlockWrap: 'Zeilen umbrechen',
codeBlockUnwrap: 'Zeilen nicht umbrechen',
sideChatBlockWindow: '{shown} von {total} Zeilen',
sideChatConnDisconnected: 'Verbindung verloren',
sideChatConnReconnect: 'Neu verbinden',
+3 -1
View File
@@ -353,7 +353,9 @@ export const fr: Record<string, string> = {
sideChatBlockFailed: 'Échec',
sideChatBlockDone: 'Terminé',
sideChatBlockNoOutput: '(aucune sortie)',
sideChatBlockFiles: '{count} fichiers',
codeBlockTitle: 'Bloc de code',
codeBlockWrap: 'Retour à la ligne',
codeBlockUnwrap: 'Ne pas retourner à la ligne',
sideChatBlockWindow: '{shown} sur {total} lignes',
sideChatConnDisconnected: 'Connexion perdue',
sideChatConnReconnect: 'Reconnecter',
+3 -1
View File
@@ -360,7 +360,9 @@ export const hi: Record<string, string> = {
sideChatBlockFailed: 'विफल',
sideChatBlockDone: 'पूर्ण',
sideChatBlockNoOutput: '(कोई आउटपुट नहीं)',
sideChatBlockFiles: '{count} फ़ाइलें',
codeBlockTitle: 'कोड ब्लॉक',
codeBlockWrap: 'पंक्तियाँ लपेटें',
codeBlockUnwrap: 'पंक्तियाँ न लपेटें',
sideChatBlockWindow: 'कुल {total} में से {shown} पंक्तियाँ',
sideChatConnDisconnected: 'कनेक्शन टूट गया',
sideChatConnReconnect: 'फिर से कनेक्ट करें',
+3 -1
View File
@@ -358,7 +358,9 @@ export const id: Record<string, string> = {
sideChatBlockFailed: 'Gagal',
sideChatBlockDone: 'Selesai',
sideChatBlockNoOutput: '(tanpa output)',
sideChatBlockFiles: '{count} file',
codeBlockTitle: 'Blok kode',
codeBlockWrap: 'Bungkus baris',
codeBlockUnwrap: 'Jangan bungkus baris',
sideChatBlockWindow: '{shown} dari {total} baris',
sideChatConnDisconnected: 'Koneksi terputus',
sideChatConnReconnect: 'Sambungkan ulang',
+3 -1
View File
@@ -351,7 +351,9 @@ export const it: Record<string, string> = {
sideChatBlockFailed: 'Non riuscito',
sideChatBlockDone: 'Completato',
sideChatBlockNoOutput: '(nessun output)',
sideChatBlockFiles: '{count} file',
codeBlockTitle: 'Blocco di codice',
codeBlockWrap: 'A capo automatico',
codeBlockUnwrap: 'Nessun a capo',
sideChatBlockWindow: '{shown} di {total} righe',
sideChatConnDisconnected: 'Connessione persa',
sideChatConnReconnect: 'Riconnetti',
+3 -1
View File
@@ -360,7 +360,9 @@ export const ja: Record<string, string> = {
sideChatBlockFailed: '失敗',
sideChatBlockDone: '完了',
sideChatBlockNoOutput: '(出力なし)',
sideChatBlockFiles: '{count} 個のファイル',
codeBlockTitle: 'コードブロック',
codeBlockWrap: '折り返しを有効にする',
codeBlockUnwrap: '折り返しを無効にする',
sideChatBlockWindow: '全 {total} 行中 {shown} 行を表示',
sideChatConnDisconnected: '接続が切断されました。クリックで再接続',
sideChatConnReconnect: '再接続',
+3 -1
View File
@@ -352,7 +352,9 @@ export const ko: Record<string, string> = {
sideChatBlockFailed: '실패',
sideChatBlockDone: '완료',
sideChatBlockNoOutput: '(출력 없음)',
sideChatBlockFiles: '파일 {count}개',
codeBlockTitle: '코드 블록',
codeBlockWrap: '줄 바꿈',
codeBlockUnwrap: '줄 바꿈 해제',
sideChatBlockWindow: '전체 {total}줄 중 {shown}줄 표시',
sideChatConnDisconnected: '연결이 끊겼습니다',
sideChatConnReconnect: '다시 연결',
+3 -1
View File
@@ -358,7 +358,9 @@ export const nl: Record<string, string> = {
sideChatBlockFailed: 'Mislukt',
sideChatBlockDone: 'Voltooid',
sideChatBlockNoOutput: '(geen uitvoer)',
sideChatBlockFiles: '{count} bestanden',
codeBlockTitle: 'Codeblok',
codeBlockWrap: 'Regels afbreken',
codeBlockUnwrap: 'Regels niet afbreken',
sideChatBlockWindow: '{shown} van {total} regels',
sideChatConnDisconnected: 'Verbinding verbroken',
sideChatConnReconnect: 'Opnieuw verbinden',
+3 -1
View File
@@ -362,7 +362,9 @@ export const pl: Record<string, string> = {
sideChatBlockFailed: 'Niepowodzenie',
sideChatBlockDone: 'Zakończono',
sideChatBlockNoOutput: '(brak wyniku)',
sideChatBlockFiles: 'Pliki: {count}',
codeBlockTitle: 'Blok kodu',
codeBlockWrap: 'Zawijanie wierszy',
codeBlockUnwrap: 'Bez zawijania wierszy',
sideChatBlockWindow: '{shown} z {total} wierszy',
sideChatConnDisconnected: 'Połączenie utracone',
sideChatConnReconnect: 'Połącz ponownie',
+3 -1
View File
@@ -343,7 +343,9 @@ export const pt: Record<string, string> = {
sideChatBlockFailed: 'Falhou',
sideChatBlockDone: 'Concluído',
sideChatBlockNoOutput: '(sem saída)',
sideChatBlockFiles: '{count} arquivos',
codeBlockTitle: 'Bloco de código',
codeBlockWrap: 'Quebrar linhas',
codeBlockUnwrap: 'Não quebrar linhas',
sideChatBlockWindow: '{shown} de {total} linhas',
sideChatConnDisconnected: 'Conexão perdida',
sideChatConnReconnect: 'Reconectar',
+3 -1
View File
@@ -358,7 +358,9 @@ export const ru: Record<string, string> = {
sideChatBlockFailed: 'Ошибка',
sideChatBlockDone: 'Готово',
sideChatBlockNoOutput: '(нет вывода)',
sideChatBlockFiles: 'Файлов: {count}',
codeBlockTitle: 'Блок кода',
codeBlockWrap: 'Переносить строки',
codeBlockUnwrap: 'Не переносить строки',
sideChatBlockWindow: '{shown} из {total} строк',
sideChatConnDisconnected: 'Соединение потеряно',
sideChatConnReconnect: 'Переподключиться',
+3 -1
View File
@@ -343,7 +343,9 @@ export const sv: Record<string, string> = {
sideChatBlockFailed: 'Misslyckades',
sideChatBlockDone: 'Klar',
sideChatBlockNoOutput: '(ingen utdata)',
sideChatBlockFiles: '{count} filer',
codeBlockTitle: 'Kodblock',
codeBlockWrap: 'Radbrytning',
codeBlockUnwrap: 'Ingen radbrytning',
sideChatBlockWindow: '{shown} av {total} rader',
sideChatConnDisconnected: 'Anslutningen bröts',
sideChatConnReconnect: 'Anslut igen',
+3 -1
View File
@@ -360,7 +360,9 @@ export const th: Record<string, string> = {
sideChatBlockFailed: 'ล้มเหลว',
sideChatBlockDone: 'เสร็จสิ้น',
sideChatBlockNoOutput: '(ไม่มีเอาต์พุต)',
sideChatBlockFiles: '{count} ไฟล์',
codeBlockTitle: 'บล็อกโค้ด',
codeBlockWrap: 'ตัดบรรทัดอัตโนมัติ',
codeBlockUnwrap: 'ไม่ตัดบรรทัด',
sideChatBlockWindow: '{shown} จาก {total} บรรทัด',
sideChatConnDisconnected: 'การเชื่อมต่อขาดหาย',
sideChatConnReconnect: 'เชื่อมต่อใหม่',
+3 -1
View File
@@ -360,7 +360,9 @@ export const tr: Record<string, string> = {
sideChatBlockFailed: 'Başarısız',
sideChatBlockDone: 'Tamamlandı',
sideChatBlockNoOutput: '(çıktı yok)',
sideChatBlockFiles: '{count} dosya',
codeBlockTitle: 'Kod bloğu',
codeBlockWrap: 'Satırları kaydır',
codeBlockUnwrap: 'Satırları kaydırma',
sideChatBlockWindow: '{total} satırdan {shown} satır',
sideChatConnDisconnected: 'Bağlantı koptu',
sideChatConnReconnect: 'Yeniden bağlan',
+3 -1
View File
@@ -360,7 +360,9 @@ export const vi: Record<string, string> = {
sideChatBlockFailed: 'Thất bại',
sideChatBlockDone: 'Hoàn tất',
sideChatBlockNoOutput: '(không có đầu ra)',
sideChatBlockFiles: '{count} tệp',
codeBlockTitle: 'Khối mã',
codeBlockWrap: 'Ngắt dòng',
codeBlockUnwrap: 'Không ngắt dòng',
sideChatBlockWindow: '{shown} trong {total} dòng',
sideChatConnDisconnected: 'Mất kết nối',
sideChatConnReconnect: 'Kết nối lại',
+3 -1
View File
@@ -375,7 +375,9 @@ export const zhHK: Record<string, string> = {
sideChatBlockFailed: '失敗',
sideChatBlockDone: '完成',
sideChatBlockNoOutput: '(無輸出)',
sideChatBlockFiles: '{count} 個檔案',
codeBlockTitle: '程式碼區塊',
codeBlockWrap: '自動換行',
codeBlockUnwrap: '取消自動換行',
sideChatBlockWindow: '共 {total} 行 · 顯示 {shown} 行',
sideChatConnDisconnected: '連線已中斷',
sideChatConnReconnect: '重新連線',
+3 -1
View File
@@ -375,7 +375,9 @@ export const zhMO: Record<string, string> = {
sideChatBlockFailed: '失敗',
sideChatBlockDone: '完成',
sideChatBlockNoOutput: '(無輸出)',
sideChatBlockFiles: '{count} 個檔案',
codeBlockTitle: '程式碼區塊',
codeBlockWrap: '自動換行',
codeBlockUnwrap: '取消自動換行',
sideChatBlockWindow: '共 {total} 行 · 顯示 {shown} 行',
sideChatConnDisconnected: '連線已中斷',
sideChatConnReconnect: '重新連線',
+3 -1
View File
@@ -375,7 +375,9 @@ export const zhTW: Record<string, string> = {
sideChatBlockFailed: '失敗',
sideChatBlockDone: '完成',
sideChatBlockNoOutput: '(無輸出)',
sideChatBlockFiles: '{count} 個檔案',
codeBlockTitle: '程式碼區塊',
codeBlockWrap: '自動換行',
codeBlockUnwrap: '取消自動換行',
sideChatBlockWindow: '共 {total} 行 · 顯示 {shown} 行',
sideChatConnDisconnected: '連線已中斷',
sideChatConnReconnect: '重新連線',
+6 -2
View File
@@ -372,7 +372,9 @@ export const zh = {
sideChatBlockFailed: '失败',
sideChatBlockDone: '完成',
sideChatBlockNoOutput: '(无输出)',
sideChatBlockFiles: '{count} 个文件',
codeBlockTitle: '代码块',
codeBlockWrap: '自动换行',
codeBlockUnwrap: '取消自动换行',
sideChatBlockWindow: '共 {total} 行 · 显示 {shown} 行',
sideChatConnDisconnected: '连接已断开,点击立即重连',
sideChatConnReconnect: '重新连接',
@@ -805,7 +807,9 @@ export const en: Record<keyof typeof zh, string> = {
sideChatBlockFailed: 'Failed',
sideChatBlockDone: 'Done',
sideChatBlockNoOutput: '(no output)',
sideChatBlockFiles: '{count} files',
codeBlockTitle: 'Code block',
codeBlockWrap: 'Wrap lines',
codeBlockUnwrap: 'Do not wrap lines',
sideChatBlockWindow: '{shown} of {total} lines',
sideChatConnDisconnected: 'Disconnected, reconnect now',
sideChatConnReconnect: 'Reconnect',
+26 -4
View File
@@ -1,5 +1,5 @@
/**
* Copy-button / chrome labels for DSH's shared `MarkdownText` (DSH
* Chrome labels for DSH's shared `MarkdownText` (DSH
* 0.1.2-alpha contract): the renderer takes a REQUIRED nested `labels` prop
* — `labels.code.copyLabel` / `labels.code.copiedLabel` for the fence copy
* buttons plus a screen-reader-only `labels.footnotes` heading — and the
@@ -8,15 +8,29 @@
* dictionary's localized pair through here (re-evaluated per render).
* `footnotes` is left empty (the heading is sr-only; give it a real string
* only if a locale key ever earns its place in all 19 dictionaries).
*
* DSH 0.1.7-rc.1 added `labels.code.toolbarLabels` (language name + wrap
* toggle + copy). Supplying it is what swaps the legacy info-string banner for
* the host's code card, and the three strings are REQUIRED once supplied, so
* every site that builds the flat pair has to carry them too — the code card
* must look the same in the transcript, the diff card and the editor preview as
* it does in DSH's own chat.
*/
import type { ComponentProps } from 'react'
import { MarkdownText } from '@deepseek-ai/dsh-client-ui-primitives'
/** The flat copy-button pair the plugin threads through its own props (e.g.
* MermaidMarkdownProps.codeLabels — the chunk contract stays put). */
/** The code-card chrome the plugin threads through its own props (e.g.
* MermaidMarkdownProps.codeLabels — the chunk contract carries the same
* fields). */
export interface MarkdownCopyLabels {
copyLabel: string
copiedLabel: string
/** Code-card title for an absent or unsupported language. */
codeLabel: string
/** Code-card action that enables wrapping. */
wrapLabel: string
/** Code-card action that preserves source columns with horizontal scrolling. */
unwrapLabel: string
}
/** MarkdownText props carrying the nested chrome labels. */
@@ -24,7 +38,15 @@ export function markdownTextProps(text: string, labels: MarkdownCopyLabels): Com
return {
text,
labels: {
code: { copyLabel: labels.copyLabel, copiedLabel: labels.copiedLabel },
code: {
copyLabel: labels.copyLabel,
copiedLabel: labels.copiedLabel,
toolbarLabels: {
codeLabel: labels.codeLabel,
wrapLabel: labels.wrapLabel,
unwrapLabel: labels.unwrapLabel,
},
},
footnotes: '',
},
}
+3 -1
View File
@@ -9,6 +9,8 @@
* tests/mermaid-blocks.spec.ts).
*/
import type { MarkdownCopyLabels } from './markdown-labels.tsx'
/** One fenced mermaid diagram lifted out of the markdown source. */
export interface MermaidBlock {
kind: 'mermaid'
@@ -28,7 +30,7 @@ export type MdBlock = MarkdownBlock | MermaidBlock
export interface MermaidMarkdownProps {
/** The full markdown source (rendered in a single MarkdownText pass). */
text: string
codeLabels: { copyLabel: string; copiedLabel: string }
codeLabels: MarkdownCopyLabels
}
/** CommonMark opening fence: 0-3 spaces indent + a run of 3+ backticks or tildes. */
+40 -13
View File
@@ -34,6 +34,7 @@ import { markdownTextProps } from './markdown-labels.tsx'
import { t } from './locales.ts'
import { sanitizeSvg } from './mermaid-sanitize.ts'
import type { MermaidMarkdownProps } from './mermaid-blocks.ts'
import { splitMermaidBlocks } from './mermaid-blocks.ts'
import css from './sidebar.module.css'
/** Monotonic id seed: every render call gets a fresh, document-unique id. */
@@ -315,20 +316,45 @@ interface MermaidMount {
}
/**
* True when a rendered CodeBlock is a mermaid fence. The DSH CodeBlock has
* two bodies: the shiki path carries the `language-*` class on generated
* <code> elements, while the plain path (which is always the one mermaid
* takes — no shiki grammar) only shows the language in the banner
* infostring (first element of the banner row; CSS-module classes are
* hashed, so that match is structural).
* Fence bodies of every mermaid fence in the source, normalized for matching
* against the rendered block.
* @param text - the markdown source the same component renders.
* @returns the trailing-whitespace-normalized diagram sources.
*/
function isMermaidBlock(block: HTMLElement): boolean {
function mermaidFenceBodies(text: string): Set<string> {
const bodies = new Set<string>()
for (const block of splitMermaidBlocks(text)) {
if (block.kind === 'mermaid') bodies.add(block.code.trimEnd())
}
return bodies
}
/** The block's fence body as rendered: CodeBlock trims one trailing newline. */
function blockBody(block: HTMLElement): string {
return (block.querySelector('code')?.textContent ?? '').trimEnd()
}
/**
* True when a rendered CodeBlock is a mermaid fence.
*
* The match is by FENCE BODY, not by the header: DSH ≤ 0.1.7-alpha.1 rendered a
* banner whose info string said `mermaid`, but rc.1's code card shows
* `labels.codeLabel` for any language the highlighter does not know, and
* mermaid has no shiki grammar — so once the card is adopted the language word
* is nowhere in the DOM. The body is, in both card variants, and the component
* already has the source that produced it.
*
* The `language-mermaid` class stays as the first probe: the highlighted arm
* would carry it if a grammar ever appears for the fence.
* @param block - one rendered `.md-code-block`.
* @param bodies - {@link mermaidFenceBodies} of the source being rendered.
* @returns whether this block holds a mermaid diagram.
*/
function isMermaidBlock(block: HTMLElement, bodies: ReadonlySet<string>): boolean {
const code = block.querySelector('code')
if (code !== null && [...code.classList].some(c => c.startsWith('language-mermaid'))) return true
const infostring = block.firstElementChild?.firstElementChild?.firstElementChild
return infostring !== null
&& infostring !== undefined
&& (infostring.textContent ?? '').trim() === 'mermaid'
if (code === null) return false
if ([...code.classList].some(c => c.startsWith('language-mermaid'))) return true
return bodies.has(blockBody(block))
}
/**
@@ -350,10 +376,11 @@ export function MermaidMarkdown({ text, codeLabels }: MermaidMarkdownProps): Rea
if (container === null) return
const mounts = mountsRef.current
const seen = new Set<HTMLElement>()
const bodies = mermaidFenceBodies(text)
for (const block of container.querySelectorAll<HTMLElement>('.md-code-block')) {
const mount = mounts.get(block)
const isMermaid = isMermaidBlock(block)
const isMermaid = isMermaidBlock(block, bodies)
if (!isMermaid) {
// A previously swapped block that is no longer a mermaid fence:
// restore the CodeBlock children React still manages, so the plain
+1 -1
View File
@@ -633,7 +633,7 @@ export function matchUrlTarget(tabs: readonly TabDescriptor[], url: URL): TabDes
* The plugin version this service instance reports. Keep in lockstep with
* `package.json`'s version — `tests/service.spec.ts` asserts the pair.
*/
export const SIDEBAR_SERVICE_VERSION = '0.21.0-alpha.1'
export const SIDEBAR_SERVICE_VERSION = '0.21.0-rc.1'
/**
* Monotonic capability list consumers use to gate new API usage (features