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:
@@ -175,7 +175,7 @@ jobs:
|
||||
- name: Install dsh CLI (pinned)
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
run: npm install -g @deepseek-ai/dsh@0.1.7-alpha.1
|
||||
run: npm install -g @deepseek-ai/dsh@0.1.7-rc.1
|
||||
|
||||
# lib/ is gitignored; the tarball is produced from this build.
|
||||
- name: Build and pack the npm tarball
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"id": "dsh-external/dsh-better-sidebar",
|
||||
"version": "0.21.0-alpha.1",
|
||||
"version": "0.21.0-rc.1",
|
||||
"main": "./lib/index.js",
|
||||
"description": "更好用的右侧边栏——文件树、编辑器、文件变动、任务和侧边对话都收在这一栏里,每个会话互不干扰。暴露服务供其他插件注册侧边栏页面与文件预览器",
|
||||
"engines": {
|
||||
"dsh": ">=0.1.7-alpha.1"
|
||||
"dsh": ">=0.1.7-rc.1"
|
||||
},
|
||||
"contributes": {
|
||||
"tools": [],
|
||||
|
||||
+47
-44
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dsh-better-sidebar",
|
||||
"version": "0.21.0-alpha.1",
|
||||
"version": "0.21.0-rc.1",
|
||||
"description": "Better Sidebar for DSH — file tree, editor, changes, tasks and side chat in one column, separate for every conversation. Also exposes a service other plugins use to register sidebar tabs and file viewers.",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
@@ -93,21 +93,21 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@deepseek-ai/cordis": "^4.0.3",
|
||||
"@deepseek-ai/dsh-agent": "^0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-client-locale": "^0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-client-ui-settings": "^0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-client-ui-sidebar-right": "^0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "^0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-host-webserver": "^0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-invariants": "^0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-llm": "^0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-session": "^0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-settings": "^0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-subagent": "^0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-tools": "^0.1.7-alpha.1",
|
||||
"@deepseek-ai/cordis": "^4.0.4",
|
||||
"@deepseek-ai/dsh-agent": "^0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-client-locale": "^0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-client-ui-settings": "^0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-client-ui-sidebar-right": "^0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "^0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-invariants": "^0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-llm": "^0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-session": "^0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-settings": "^0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-subagent": "^0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-tools": "^0.1.7-rc.1",
|
||||
"@huanlin/dsh-plugin-better-locale": "^0.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
@@ -142,7 +142,7 @@
|
||||
"@codemirror/search": "^6.7.1",
|
||||
"@codemirror/state": "^6.7.1",
|
||||
"@codemirror/view": "^6.43.8",
|
||||
"@deepseek-ai/schemastery": "^3.18.3",
|
||||
"@deepseek-ai/schemastery": "^3.18.4",
|
||||
"@lezer/highlight": "^1.2.3",
|
||||
"clsx": "^2.1.1",
|
||||
"dompurify": "^3.4.14",
|
||||
@@ -154,33 +154,36 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cordisjs/plugin-loader": "^1.0.0-rc.5",
|
||||
"@deepseek-ai/dsh-agent": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-agent-loop": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-attachment": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-client-locale": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-client-store": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-client-ui-conversation": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-client-ui-primitives": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-client-ui-settings": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-client-ui-sidebar-right": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-host-webserver": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-invariants": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-llm": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-ptc-runtime": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-sandbox": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-sandbox-policy": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-scope": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-session": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-session-persistence": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-session-projection": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-settings": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-subagent": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-system-prompt": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-tools": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-user-approval": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-util-time": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/dsh-util-workspace-path": "0.1.7-alpha.1",
|
||||
"@deepseek-ai/cordis": "4.0.4",
|
||||
"@deepseek-ai/cordis-plugin-group": "1.0.4",
|
||||
"@deepseek-ai/cordis-plugin-include": "1.0.9",
|
||||
"@deepseek-ai/dsh-agent": "0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-agent-loop": "0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-attachment": "0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-client-locale": "0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-client-store": "0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-client-ui-conversation": "0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-client-ui-settings": "0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-client-ui-sidebar-right": "0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-host-webserver": "0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-invariants": "0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-llm": "0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-ptc-runtime": "0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-sandbox": "0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-sandbox-policy": "0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-scope": "0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-session": "0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-session-persistence": "0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-session-projection": "0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-settings": "0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-subagent": "0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-system-prompt": "0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-tools": "0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-user-approval": "0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-util-time": "0.1.7-rc.1",
|
||||
"@deepseek-ai/dsh-util-workspace-path": "0.1.7-rc.1",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@huanlin/dsh-plugin-better-locale": "^0.1.0",
|
||||
"@playwright/test": "^1.62.1",
|
||||
|
||||
Generated
+525
-510
File diff suppressed because it is too large
Load Diff
@@ -42,9 +42,11 @@ e2e_require_cmd() {
|
||||
# 缺省值(`dsh`)由调用方从环境变量取好传入。
|
||||
#
|
||||
# 回退的 npx 版本是显式钉死的:不钉就会解析 npm 的 `latest` dist-tag,而
|
||||
# 0.1.7 线目前只有预发布(`latest` 仍是 0.1.5-rc.2),冒烟会静静地挂到一
|
||||
# 个插件已不支持的宿主上。钉版必须与 package.json 的 peer 下限同步。
|
||||
DSH_NPX_SPEC="${DSH_NPX_SPEC:-@deepseek-ai/dsh@0.1.7-alpha.1}"
|
||||
# 0.1.7 线目前只有预发布(`latest` 仍是 0.1.5-rc.3),冒烟会静静地挂到一
|
||||
# 个插件已不支持的宿主上。**必须写精确版本,不能写 `@alpha`**:0.1.7-rc.1
|
||||
# 上线时 `alpha` 指的是 0.1.7-alpha.2,rc.1 走的是 `next`。钉版必须与
|
||||
# package.json 的 peer 下限同步。
|
||||
DSH_NPX_SPEC="${DSH_NPX_SPEC:-@deepseek-ai/dsh@0.1.7-rc.1}"
|
||||
e2e_resolve_dsh_cmd() {
|
||||
local explicit="${DSH_CMD}"
|
||||
if ! command -v "$DSH_CMD" >/dev/null 2>&1; then
|
||||
@@ -66,7 +68,7 @@ e2e_resolve_dsh_cmd() {
|
||||
# 己在挂什么),只告警。
|
||||
e2e_check_dsh_version() {
|
||||
local explicit="$1" actual expected
|
||||
expected="${DSH_EXPECT_VERSION:-0.1.7-alpha.1}"
|
||||
expected="${DSH_EXPECT_VERSION:-0.1.7-rc.1}"
|
||||
# `dsh --version` 冷启动要走 npx,给足预算;拿不到版本就只告警。
|
||||
actual="$($DSH_CMD --version 2>/dev/null | tr -d '[:space:]' | head -c 64 || true)"
|
||||
case "$actual" in
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 }) },
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -361,7 +361,9 @@ export const ar: Record<string, string> = {
|
||||
sideChatBlockFailed: 'فشل',
|
||||
sideChatBlockDone: 'اكتمل',
|
||||
sideChatBlockNoOutput: '(لا يوجد إخراج)',
|
||||
sideChatBlockFiles: '{count} ملفات',
|
||||
codeBlockTitle: 'كتلة التعليمات البرمجية',
|
||||
codeBlockWrap: 'التفاف الأسطر',
|
||||
codeBlockUnwrap: 'بدون التفاف للأسطر',
|
||||
sideChatBlockWindow: '{shown} من {total} سطرًا',
|
||||
sideChatConnDisconnected: 'انقطع الاتصال',
|
||||
sideChatConnReconnect: 'إعادة الاتصال',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -360,7 +360,9 @@ export const hi: Record<string, string> = {
|
||||
sideChatBlockFailed: 'विफल',
|
||||
sideChatBlockDone: 'पूर्ण',
|
||||
sideChatBlockNoOutput: '(कोई आउटपुट नहीं)',
|
||||
sideChatBlockFiles: '{count} फ़ाइलें',
|
||||
codeBlockTitle: 'कोड ब्लॉक',
|
||||
codeBlockWrap: 'पंक्तियाँ लपेटें',
|
||||
codeBlockUnwrap: 'पंक्तियाँ न लपेटें',
|
||||
sideChatBlockWindow: 'कुल {total} में से {shown} पंक्तियाँ',
|
||||
sideChatConnDisconnected: 'कनेक्शन टूट गया',
|
||||
sideChatConnReconnect: 'फिर से कनेक्ट करें',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -360,7 +360,9 @@ export const ja: Record<string, string> = {
|
||||
sideChatBlockFailed: '失敗',
|
||||
sideChatBlockDone: '完了',
|
||||
sideChatBlockNoOutput: '(出力なし)',
|
||||
sideChatBlockFiles: '{count} 個のファイル',
|
||||
codeBlockTitle: 'コードブロック',
|
||||
codeBlockWrap: '折り返しを有効にする',
|
||||
codeBlockUnwrap: '折り返しを無効にする',
|
||||
sideChatBlockWindow: '全 {total} 行中 {shown} 行を表示',
|
||||
sideChatConnDisconnected: '接続が切断されました。クリックで再接続',
|
||||
sideChatConnReconnect: '再接続',
|
||||
|
||||
@@ -352,7 +352,9 @@ export const ko: Record<string, string> = {
|
||||
sideChatBlockFailed: '실패',
|
||||
sideChatBlockDone: '완료',
|
||||
sideChatBlockNoOutput: '(출력 없음)',
|
||||
sideChatBlockFiles: '파일 {count}개',
|
||||
codeBlockTitle: '코드 블록',
|
||||
codeBlockWrap: '줄 바꿈',
|
||||
codeBlockUnwrap: '줄 바꿈 해제',
|
||||
sideChatBlockWindow: '전체 {total}줄 중 {shown}줄 표시',
|
||||
sideChatConnDisconnected: '연결이 끊겼습니다',
|
||||
sideChatConnReconnect: '다시 연결',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -358,7 +358,9 @@ export const ru: Record<string, string> = {
|
||||
sideChatBlockFailed: 'Ошибка',
|
||||
sideChatBlockDone: 'Готово',
|
||||
sideChatBlockNoOutput: '(нет вывода)',
|
||||
sideChatBlockFiles: 'Файлов: {count}',
|
||||
codeBlockTitle: 'Блок кода',
|
||||
codeBlockWrap: 'Переносить строки',
|
||||
codeBlockUnwrap: 'Не переносить строки',
|
||||
sideChatBlockWindow: '{shown} из {total} строк',
|
||||
sideChatConnDisconnected: 'Соединение потеряно',
|
||||
sideChatConnReconnect: 'Переподключиться',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -360,7 +360,9 @@ export const th: Record<string, string> = {
|
||||
sideChatBlockFailed: 'ล้มเหลว',
|
||||
sideChatBlockDone: 'เสร็จสิ้น',
|
||||
sideChatBlockNoOutput: '(ไม่มีเอาต์พุต)',
|
||||
sideChatBlockFiles: '{count} ไฟล์',
|
||||
codeBlockTitle: 'บล็อกโค้ด',
|
||||
codeBlockWrap: 'ตัดบรรทัดอัตโนมัติ',
|
||||
codeBlockUnwrap: 'ไม่ตัดบรรทัด',
|
||||
sideChatBlockWindow: '{shown} จาก {total} บรรทัด',
|
||||
sideChatConnDisconnected: 'การเชื่อมต่อขาดหาย',
|
||||
sideChatConnReconnect: 'เชื่อมต่อใหม่',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -375,7 +375,9 @@ export const zhHK: Record<string, string> = {
|
||||
sideChatBlockFailed: '失敗',
|
||||
sideChatBlockDone: '完成',
|
||||
sideChatBlockNoOutput: '(無輸出)',
|
||||
sideChatBlockFiles: '{count} 個檔案',
|
||||
codeBlockTitle: '程式碼區塊',
|
||||
codeBlockWrap: '自動換行',
|
||||
codeBlockUnwrap: '取消自動換行',
|
||||
sideChatBlockWindow: '共 {total} 行 · 顯示 {shown} 行',
|
||||
sideChatConnDisconnected: '連線已中斷',
|
||||
sideChatConnReconnect: '重新連線',
|
||||
|
||||
@@ -375,7 +375,9 @@ export const zhMO: Record<string, string> = {
|
||||
sideChatBlockFailed: '失敗',
|
||||
sideChatBlockDone: '完成',
|
||||
sideChatBlockNoOutput: '(無輸出)',
|
||||
sideChatBlockFiles: '{count} 個檔案',
|
||||
codeBlockTitle: '程式碼區塊',
|
||||
codeBlockWrap: '自動換行',
|
||||
codeBlockUnwrap: '取消自動換行',
|
||||
sideChatBlockWindow: '共 {total} 行 · 顯示 {shown} 行',
|
||||
sideChatConnDisconnected: '連線已中斷',
|
||||
sideChatConnReconnect: '重新連線',
|
||||
|
||||
@@ -375,7 +375,9 @@ export const zhTW: Record<string, string> = {
|
||||
sideChatBlockFailed: '失敗',
|
||||
sideChatBlockDone: '完成',
|
||||
sideChatBlockNoOutput: '(無輸出)',
|
||||
sideChatBlockFiles: '{count} 個檔案',
|
||||
codeBlockTitle: '程式碼區塊',
|
||||
codeBlockWrap: '自動換行',
|
||||
codeBlockUnwrap: '取消自動換行',
|
||||
sideChatBlockWindow: '共 {total} 行 · 顯示 {shown} 行',
|
||||
sideChatConnDisconnected: '連線已中斷',
|
||||
sideChatConnReconnect: '重新連線',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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: '',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -23,7 +23,9 @@ const media: MarkdownHtmlMedia = {
|
||||
path: '/ws/docs/README.md',
|
||||
origin: 'http://gui.origin',
|
||||
}
|
||||
const codeLabels = { copyLabel: 'Copy', copiedLabel: 'Copied' }
|
||||
const codeLabels = {
|
||||
copyLabel: 'Copy', copiedLabel: 'Copied', codeLabel: 'Code block', wrapLabel: 'Wrap lines', unwrapLabel: 'Do not wrap lines',
|
||||
}
|
||||
|
||||
async function renderDocument(text: string): Promise<{ container: HTMLElement; root: Root }> {
|
||||
const container = document.createElement('div')
|
||||
|
||||
@@ -114,4 +114,24 @@ describe('DSH community-market manifest compatibility', () => {
|
||||
it('declares an exact SemVer version (no range or tag; prerelease allowed for the alpha track)', () => {
|
||||
expect(pkg.version).toMatch(EXACT_SEMVER)
|
||||
})
|
||||
|
||||
it('keeps every DSH peer a floating range the host runtime can satisfy', () => {
|
||||
// DSH 0.1.7-rc.1 added a startup compatibility preflight
|
||||
// (packages/boot/app-boot/src/plugin-compatibility.ts): for every peer
|
||||
// named `@deepseek-ai/dsh` or `@deepseek-ai/dsh-*`, the host evaluates
|
||||
// `semver.satisfies(runtimeVersion, range, { includePrerelease: true })`
|
||||
// and DISABLES the whole profile row when it fails — one stderr line in a
|
||||
// log nobody reads, no dialog, and `peerDependenciesMeta.optional` grants
|
||||
// no exemption. An exact pin (the style devDependencies use) or a
|
||||
// comparator carrying no prerelease (`^0.1.7`) would therefore make the
|
||||
// plugin silently stop loading, so the published ranges must stay carets
|
||||
// on the baseline tuple. `pnpm peers check` cannot see this: it validates
|
||||
// the resolved dev tree, not the range shape a host will read.
|
||||
const peers = pkg.peerDependencies ?? {}
|
||||
const dshPeers = Object.keys(peers).filter(name => name === '@deepseek-ai/dsh' || name.startsWith('@deepseek-ai/dsh-'))
|
||||
expect(dshPeers.length).toBeGreaterThan(10)
|
||||
for (const name of dshPeers) {
|
||||
expect(peers[name], name).toMatch(/^\^0\.1\.7-[a-z0-9.]+$/)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -27,7 +27,9 @@ vi.mock('mermaid', () => ({
|
||||
import mermaid from 'mermaid'
|
||||
import { MermaidMarkdown } from '../src/client/mermaid.tsx'
|
||||
|
||||
const codeLabels = { copyLabel: 'Copy', copiedLabel: 'Copied' }
|
||||
const codeLabels = {
|
||||
copyLabel: 'Copy', copiedLabel: 'Copied', codeLabel: 'Code block', wrapLabel: 'Wrap lines', unwrapLabel: 'Do not wrap lines',
|
||||
}
|
||||
|
||||
async function renderMarkdown(text: string): Promise<{ container: HTMLElement; root: Root }> {
|
||||
const container = document.createElement('div')
|
||||
|
||||
Reference in New Issue
Block a user