Commit Graph
7 Commits
Author SHA1 Message Date
Atlas Zeng 5e416eccef feat(mermaid): Markdown 预览安全渲染 Mermaid 图表(strict 清洗 + 懒加载 chunk + 点击放大) (#164)
* feat(mermaid): 新增 mermaid 懒加载 chunk(构建链四处同步 + mermaid 依赖)

tsdown CHUNKS / bundle-route CHUNK_NAMES / chunk-loader ChunkName /
package.json files 同步注册 mermaid chunk;mermaid ^11.16.1 进依赖与
lockfile;chunk 构建注入 uuid 浏览器入口别名,绕开 node:crypto 纯度门。

* feat(mermaid): Markdown 预览安全渲染 mermaid 图表 + 点击放大弹窗

- splitMermaidBlocks 纯函数按 fence 切块,无 mermaid 文件零行为变化;
- mermaid.initialize 走 securityLevel strict + htmlLabels:false,不应用
  bindFunctions(静态图);渲染 SVG 经 mermaid-sanitize 二次清洗:
  删 foreignObject/script/外来 HTML 元素、剥 @*/on* 属性、移除全部
  href/xlink:href,仅接受 SVG 根,解析失败返回空串;
- 点击图表弹出放大弹窗(滚轮缩放/拖拽平移/快捷键,借鉴 PR #75 交互,
  基于已清洗 SVG 克隆重写,不新增攻击面);
- 深浅色跟随重渲、失败回退错误条 + 原码、CodeBlock 同款 chrome。

* test(mermaid): splitter/sanitize 单测 + chunk 清单同步 + 挂载冒烟 diagram.md

- mermaid-blocks.spec 10 例 fence 切块语义;
- mermaid-sanitize.spec 8 例 XSS(foreignObject/on*/@*/href/xlink:href/
  script/外来元素/畸形 XML/非 SVG 根);
- bundle-route / chunk-artifact / manifest-consistency 清单加入 mermaid
  (chunk-artifact 补 addEventListener stub);
- 挂载冒烟新增 diagram.md:client-mermaid.js 往返、SVG 节点文字、
  点击弹窗 Esc 关闭、preview 模式编辑器隐藏。

* docs(mermaid): 安全渲染设计文档 + lazy-chunks 表 + README 双语更新

设计文档记录基线决策(PR #99 架构 + PR #75 点击放大)、安全模型
(strict/htmlLabels/清洗/href 剥离/无 bindFunctions)、验证与取舍。

* fix(mermaid): sanitizer 属性名大小写归一,堵住混合大小写属性绕过(CR P1)

XML 属性名保留大小写,而输出会经 HTML 解析归一化(dangerouslySetInnerHTML),
oNload/HREF/xlink:HREF 这类混合大小写属性此前会漏过清洗。比较前先
toLowerCase(),按原名删除;新增混合大小写事件/链接属性回归用例。

* fix(mermaid): sanitizer 元素黑名单大小写归一,堵住混合大小写元素绕过(CR P1)

XML 元素名保留大小写,querySelectorAll('script') 等选择器匹配不到
<sCrIpT>/<foreignobject>,而输出经 HTML 解析会被归一化为 SVG
script/foreignObject(可执行/可注入 HTML)。改为遍历全部元素按
localName.toLowerCase() 命中小写黑名单后整节点删除;新增混合大小写
script/foreignObject/外来 HTML 元素回归用例(jsdom 实证:旧选择器
命中 0,新逻辑命中 2)。

* fix(mermaid): 单次 Markdown 解析 + 占位交换(CR P1)+ CommonMark fence 规则(CR P2)

P1(跨 fence 语义):弃用逐块 MarkdownText 拆分,改为整篇文档单次
MarkdownText 渲染,布局后把 language-mermaid 代码块交换为 MermaidDiagram
(React 管理的 .md-code-block 宿主保留在树中、display:contents 抑制盒子,
协调安全;块不再 mermaid 时还原原 children)。新增
tests/mermaid-markdown.spec.tsx 与 e2e 跨 fence 引用链接断言。

P2(fence 匹配):支持 3+ 反引号与波浪号开 fence;闭 fence 须同字符且
长度不小于开 fence;反引号 info string 含反引号不视为开 fence;修复
带缩进开 fence 的 info 提取。blocks 单测 +3 例。
2026-08-18 01:22:28 +08:00
Atlas Zeng 6ee1ba11d0 feat(editor): 文件窗口与资源管理器二合一 + 声明式设置 select 行 (#151)
* feat(editor): merged explorer mode — path input, docked file tree, global name search

New editorExplorer pref (default on, editor card gear): the editor tab gains
a path-input header and a toggleable right-docked tree panel (per-tab state
in tab.meta.treeOpen; file tabs default closed, the seeded path-less home
tab defaults open). Fresh sessions seed that empty editor tab instead of
the explorer tab when merged mode is on; the standalone explorer tab stays
in the + menu unchanged.

- Extract FileTree.tsx from ExplorerView (shared by both hosts)
- Host fs.search route: recursive case-insensitive name search rooted at
  the session cwd, budget-capped (200 matches / 100k visited), skips .git,
  never descends symlink dirs
- EditorHost reads the pref reactively (boolean snapshot) and keeps the
  pre-merge layout verbatim when off

* feat(settings): select row type for declarative settings; editorExplorer as iconed select

- SidebarSettingToggle gains type 'select' with options {value,title,desc,icon}
  and multi (default false; stores the picked values as an array in options
  order). Any option carrying an icon renders big-icon option cards in the
  dropdown and the icon in the closed anchor; icon-less rows are one-line text.
- SelectRow in SideCardSection rides the primitives Menu (portal); single-pick
  commits and closes, multi toggles and stays open. Wired for both host-prefs
  toggles and plugin-owned pluginToggles via the new onSelectValue seam.
- The editorExplorer merged-mode pref is now an iconed select (merged vs
  separate) instead of a switch; the stored value stays boolean.
- Advertise the capability as 'settingSelect' in SIDEBAR_FEATURES.

* feat(editor): unify explorer into the editor surface, resizable tree dock, hoisted toolbar

- One tree implementation, two presentations: the shared TreePanel (global
  name search + refresh + FileTree) docks as the editor tab's right panel
  and — in merged mode — fills the explorer tab's window; the pref-off
  explorer keeps its legacy header shell.
- The docked panel drag-resizes from its left edge (160-480px clamp),
  persisted as tab.meta.treeWidth alongside treeOpen.
- The text editor's preview/edit/save toolbar moves into the path-input
  header row: FileViewerProps gains internal toolbar fields ('host' mode
  skips the viewer's own row, reports EditorToolbarState and registers
  EditorToolbarControls); non-merged mode is byte-identical to before.

* feat(editor): single files window with in-place switching; explorer descriptor removed

- editor tab replaces explorer on the + menu (order 10, Files); persisted
  explorer tabs migrate to the editor home tab via sanitizeState
- editorExplorer now only controls open behavior: merged = in-place
  updateTab, independent = openSidebarFile; both modes share the merged
  chrome (path input + hoisted preview/edit/save toolbar + tree toggle)
- file tree context menu gains 'open in new tab' / 'open to the side'
  (mintTabId avoids the id safety net; insertLeafAt splits right)
- tree dock resize uses pointer capture (touch-action none, 6px handle)
- defaults: openByDefault off, sidebar width 35%
- file tree clips horizontal overflow (no sideways scroll)
- e2e: mount lane arms the editor-chunk wait before goto and clicks the
  tree row's left edge (hover @-reference button sits mid-row); drag lane
  expands the panel first (collapsed push writes 0px), waits for the
  slide-in to settle, and picks the leftmost col-resize strip; 'Files'
  title assertions require draggable to skip the bottom panel's welcome
  cards

* feat(editor): split mode turns the path-less window into the standalone explorer

In independent (split) mode a path-less files window renders ONLY the
tree panel (search + FileTree, full-window form) — no path input, no
hoisted toolbar, no tree toggle. File clicks open per-path editor tabs
(full chrome, tree docked closed by default) through openSidebarFile,
unchanged. Merged mode is untouched (chrome + docked tree, in-place
switching). TreePanel's previously reserved 'full' prop now has its
consumer.
2026-08-17 15:00:03 +08:00
Logan 1f9af78c3c fix: enable selection-to-conversation popup in the markdown editor
The "add to conversation" selection popup was registered only for the
catch-all code viewer (viewerId === 'code'), so selecting text in the
CodeMirror editor of a markdown file produced no popup; insertion only
worked in markdown preview mode via the separate DOM-selection handler.

Extend the CodeMirror selection popup to the markdown editor
(viewerId === 'markdown') so line-exact selections can be injected into
the composer draft (\`\`\`rel/path:start-end\`\`\` payload), matching
preview-mode behavior and the code viewer.
2026-08-14 12:59:17 +08:00
Menghuan1918 b31a9a601f fix: md 预览代码块复制按钮跟随 DSH i18n(MarkdownText 补传 codeLabels) 2026-08-13 10:26:57 +08:00
Menghuan1918 ce052faee8 feat: 预览区划选文字浮窗「添加到对话」(md 预览 + 兜底 code 查看器) 2026-08-13 01:39:59 +08:00
Menghuan1918 302c20109f feat: 内置 HTML 预览器 + 沙箱浏览器 tab(v0.5.0)
- html viewer:.html/.htm 沙箱 iframe 预览(相对资源经 /sidebar/html 路径编码路由加载,CSP sandbox + 信任围栏 + cwd 边界),预览/编辑切换
- browser tab:+ 菜单多开(browser:<n>),地址栏后退/前进/刷新/在浏览器中打开(15px 图标),URL 与标题按会话持久化(patchTab + nextBrowser)
- 安全:iframe 一律不透明源沙箱(无 allow-same-origin/top-navigation、no-referrer、权限策略全禁、禁 srcdoc);地址栏仅 http(s)、拒绝 localhost 等本机地址;界面实时沙箱状态(关闭红色警示)+ 临时解锁/恢复
- 设置:htmlViewerNoSandbox / htmlViewerDefaultUnsafe / browserNoSandbox / browserInterceptLinks 四个声明式二级设置(默认安全)
- 外链拦截:聊天/界面 http(s) 外链默认在侧边栏打开(openTab seed.url),Ctrl/Cmd 点击放行
- 嵌入拒绝面板:browser.probe 路由探测 X-Frame-Options/frame-ancestors,被拒站点显示原因 + 在浏览器中打开/仍然加载
- 设置页可滚动;内置 7 tab + 9 viewer;新增设计文档;268 个测试全绿
2026-08-11 19:28:48 +08:00
Menghuan1918 0467101faf feat(editor): 拆分 EditorHost/TextEditor — code/markdown 成为注册 viewer,fs.read 增加 head 字节
- 新 src/client/EditorHost.tsx:'editor' tab 组件。matchFileViewer →
  按 fetchStrategy 分发(纯逻辑在 editor-load.ts)→ 渲染 viewer.component;
  binary 无匹配时渲染共享 BinaryDownload。标题栏保留在 host
- 新 src/client/TextEditor.tsx:code/markdown 的 viewer 组件(CodeMirror +
  保存/脏点/截断横幅 + md 预览/编辑切换),内容由 host 经 fsRead 传入,
  自身不再 fetch;按 viewerId === 'markdown' 分支(不再解析扩展名)
- 新 src/client/editor-load.ts:planFirstMatch / planFsReadOutcome /
  decodeHead 纯函数,策略分发可单测
- 新 src/client/binary-download.tsx:共享下载兜底组件(viewer 注册与
  host fallback 同一 UI)
- host src/index.ts:fs.read 二进制响应增加 head 字段(base64,前 4KB,
  唯一 host 触碰,设计文档 detect 机制所需);客户端 FsBinaryResult 同步
- builtins:注册 8 个 viewer(新增 markdown exts md/markdown、code
  catch-all -100);binary-download 增加 NUL detect + 复用 BinaryDownload
- 删除 src/client/EditorView.tsx(职责一分为二);陈旧注释同步
- 新增 tests/editor-load.spec.ts(12 用例)
2026-08-11 11:16:36 +08:00