design: plan browser runtime extraction

This commit is contained in:
lix-2026
2026-05-24 21:03:33 +08:00
parent cafd29a963
commit b61b56b5b5
6 changed files with 936 additions and 0 deletions
@@ -0,0 +1,539 @@
# 3-19 Rust Web 浏览器 Runtime 模块外置化重构 v1
> 创建时间:2026-05-24
> 状态:`process`
>
> 触发背景:
> - CodeGraph 对 `mnote-web` 前端内嵌 JS 的符号定位效果一般。
> - 字面搜索命中太多,尤其是 `layout.rs` / `tree.rs` 内的大型 raw string。
> - 需要判断是更换 graph 工具,还是调整源码组织方式。
>
> 参考实现:
> - `/mnt/Data1T/mnote/reference-code/sidex-main/src/vs/workbench/contrib/files/browser/views/explorerView.ts`
> - `/mnt/Data1T/mnote/reference-code/sidex-main/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts`
> - `/mnt/Data1T/mnote/reference-code/sidex-main/src/vs/workbench/contrib/files/browser/explorerService.ts`
> - `/mnt/Data1T/mnote/reference-code/sidex-main/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts`
## 1. 结论
当前不应优先更换 CodeGraph。主要问题不是 graph 项目选型,而是 `mnote-web` 的大量浏览器 runtime 仍作为 Rust raw string / HTML inline script 存在。对 CodeGraph、IDE、ast-grep、浏览器 stack trace 来说,这些逻辑首先是 Rust 字符串,不是一等 JavaScript / TypeScript 源文件。
短期可以做“内嵌 JS 提取索引”作为定位辅助,但它只解决查询体验,不改变维护边界。长期重构方向应是:保留 Rust SSR / projection / reducer 的语义主导权,把浏览器端 DOM 绑定、host bridge、transport controller、DND、context menu、resource open adapter 等 runtime 分阶段迁出 Rust raw string,变成可索引、可测试、可复用的 `.js``.ts` 模块。
补充结论(2026-05-24):最近 `bugs/0524.md` 的修复耗时说明,低效并不只发生在 `layout.rs` 的 tree/filetree runtime,也发生在 `web_shell.rs` 的文档页 host adapter 和 `leptos-tiptap-spike/src/lib.rs` 的编辑器 runtime 巨型文件。本设计只覆盖 `03-rust-web` 的浏览器 shell runtime 外置化;`leptos-tiptap` 编辑器命令、附件、history、DOM bridge 需要并行进入 `05-editor-mainline` 设计,不应塞进本设计里一起实施。
## 2. 当前代码基线
### 2.1 主壳内嵌 runtime
- `rust/crates/mnote-web/src/ssr/pages/layout.rs`
- `SIDEBAR_TREE_JS` 从文件顶部开始,是当前主壳最大浏览器 runtime。
- 职责混合了:侧栏宽度、工作区切换、page tree / file tree 事件、文件树选择、拖拽、资源打开、附件上传、Page AI panel、账号菜单、本地文件夹 watcher。
- `TREE_LIVE_CONTROLLER_JS` 负责 tree live bootstrap、WS / SSE / local-folder event transport、snapshot / delta / resync 分发。
- `PageLayout` 通过 `<script inner_html={...}>` 直接注入这些 runtime。
### 2.2 Debug tree shell 内嵌 runtime
- `rust/crates/mnote-web/src/routes/tree.rs`
- `build_tree_shell_html()` 内嵌整页 HTML、CSS、JS。
- JS 负责 page/filetree/picker 模式、DOM render、keyboard、DND、context menu、runtime reduce endpoint 调用、local fallback。
- `/api/tree/runtime/reduce` 已存在,入口为 `reduce_tree_shell_runtime()`
### 2.3 已经存在的 Rust 侧良好边界
- `rust/crates/mnote-web/src/tree_shell/runtime_api.rs`
- 已定义 `TreeShellRuntimeRequest``TreeShellRuntimeResult``TreeShellDomPatch``TreeShellHostEvent``TreeShellCommandEvent`
- `rust/crates/mnote-web/src/tree_shell/filetree_selection.rs`
- 已定义文件树选择 reducer contract。
- `rust/crates/mnote-web/src/tree_shell/filetree_renderer.rs` / `page_renderer.rs`
- 已有初始 HTML renderer 与测试。
这说明重构不需要推翻 Rust 主线。相反,应该把现有 Rust reducer / renderer 合同作为稳定边界,让浏览器 runtime 外置后继续消费这些合同。
### 2.4 需要另线处理的编辑器 runtime 巨型文件
- `rust/spikes/leptos-tiptap-spike/src/lib.rs`
- 约一万行,虽然是 Rust 源码而不是 raw string,但混合了 Tiptap bridge、编辑器命令、附件链接增强、上传、history、mindmap node view、DOM overlay、page option、AI surface。
- CodeGraph 能索引 Rust 符号,但单文件职责过大,worker 仍会反复字面搜索和误判边界。
- 该文件不属于 `03-rust-web` shell runtime 外置化范围,应由 `05-editor-mainline` 拆出编辑器命令和附件/history 相关模块。
## 3. Sidex / VSCode Explorer 对照结论
Sidex 参考实现的关键价值不是具体 UI,而是模块边界:
- `ExplorerView`:视图容器,持有 tree、renderer、context key、focus、layout。
- `ExplorerService`:模型、文件变更、refresh、cut/copy 状态、view 注册。
- `FileDragAndDrop`DND 独立类,包含 `onDragOver``drop``getDragURI``onDragStart`
- `fileActions.contribution.ts`:命令和快捷键独立注册,通过 context condition 控制可用性。
- `IExplorerView` / `IExplorerService`:视图与服务之间通过接口通信。
MNote 不应照搬 VSCode 的完整 DI / Workbench 架构;当前产品主线也不是 BlockNote-first 或 VSCode clone。但应采用同一类边界原则:
- 视图绑定与 DOM patch 是浏览器 runtime 职责。
- 树语义、资源归属、projection、command 语义仍由 Rust kernel / mnote-web reducer 主导。
- DND、context menu、keyboard、transport、resource open adapter 不应继续混在单个全局 IIFE 中。
## 4. 目标
- 让主壳浏览器 runtime 成为一等源文件,能被 CodeGraph / IDE / ast-grep / lint 正常解析。
-`layout.rs` 回到 SSR layout 与 bootstrap 注入职责,不再承载万行浏览器行为。
-`/tree` debug shell 与主壳 sidebar 复用同一套 tree/filetree runtime 模块,减少两套行为漂移。
- 保留 Rust `tree_shell` reducer / renderer / contract 作为语义边界,不把树真相迁到前端。
- 改善浏览器调试:错误堆栈能落到稳定 JS 模块文件和行号。
- 为后续 tree live cache、filetree DND、resource tab、local folder watcher 的继续收口降低定位成本。
## 5. 非目标
- 不更换 CodeGraph。
- 不引入完整 React / Next / BlockNote 主链。
- 不把 Rust kernel / Rust projection 的语义迁回前端。
- 不一次性重写整个 sidebar。
- 不在本阶段改变现有 UI 形态、Wolai 对齐目标、tree command 协议或 resource object identity 合同。
- 不把 debug `/tree` 重新提升为默认产品入口;它仍是显式 debug/internal 边界。
## 6. 方案选择
### 6.1 方案 A:只做内嵌 JS 提取索引
做一个脚本从 Rust raw string 中提取 `<script>` / `*_JS` 常量为临时 `.js` 文件,并带回源文件行号映射。
优点:
- 改动小。
- 对定位问题见效快。
- 不影响 runtime。
缺点:
- 只是辅助索引,真实维护边界仍然是 Rust 字符串。
- 浏览器 stack trace、代码 review、模块复用、单测边界不会根本改善。
- 提取器会成为额外维护成本。
### 6.2 方案 B:直接整体重写 sidebar runtime
一次性把 `SIDEBAR_TREE_JS` 全部迁出并重写成新模块体系。
优点:
- 最终形态干净。
- 可一次性消除万行 inline script。
缺点:
- 风险过大。
- 当前 sidebar 混合了 tree、资源、AI、账号、本地文件夹、多 pane 等多个活跃主线,整体重写容易引入回归。
- smoke 覆盖面需要非常大,不适合作为第一阶段。
### 6.3 推荐方案 C:分阶段外置化,先提取边界清楚的 runtime
先从职责边界最清晰、状态输入最明确、验证最容易的模块开始迁出。每个阶段都保持旧行为,完成后跑对应 smoke,再进入下一阶段。
推荐顺序:
1. resource open / local upload / attachment action adapter
2. `TREE_LIVE_CONTROLLER_JS`
3. debug `/tree` shell runtime 的 bridge 层
4. 主壳 filetree selection / keyboard / DND bridge
5. Page AI panel 等低相关大块 runtime 另立后续设计,不混入本次 tree runtime 重构
推荐理由:
- 每步可独立验收。
- 不破坏当前 Rust reducer / projection 主线。
- 能最快验证 CodeGraph 定位改善。
- 便于回滚单一模块,而不是回滚整个 sidebar。
- 最近缺陷集中在上传目标、附件打开、secondary pane、资源 tab、OnlyOffice/PDF/code 打开路径。先拆 resource/local-upload adapter,比先拆 tree live 更能直接降低后续 bug 修复成本。
- `TREE_LIVE_CONTROLLER_JS` 仍适合作为低风险模板切片:边界清楚、smoke 现成、能验证 asset route 和 ES module 注入方式。
## 7. 目标架构
### 7.1 文件布局建议
新增浏览器 runtime 源文件目录:
```text
rust/crates/mnote-web/browser/
resource-open-runtime.js
local-upload-runtime.js
tree-live-controller.js
tree-shell-runtime.js
sidebar-tree-runtime.js
filetree-selection-runtime.js
filetree-dnd-runtime.js
filetree-context-menu-runtime.js
local-folder-events-runtime.js
```
如后续决定引入 TypeScript,可迁移为:
```text
rust/crates/mnote-web/browser/src/
rust/crates/mnote-web/browser/dist/
```
第一阶段建议先用 plain ES module `.js`,避免为了重构引入额外 build chain。待模块边界稳定后,再评估是否升级 TypeScript。
### 7.2 Rust 注入边界
Rust 继续负责:
- SSR HTML layout。
- bootstrap JSON script,例如 `__MNOTE_TREE_LIVE_BOOTSTRAP__`
- initial page tree / file tree HTML。
- `/api/tree/runtime/reduce`
- `/api/tree/commands`
- `/api/realtime/ws``/api/tree/events``/api/local-folder/events`
- mnote-web 静态 runtime asset 路由。
Rust 不再负责:
- 在 raw string 中维护大块浏览器行为。
- 在 inline script 中拼出完整 DOM runtime。
-`layout.rs` 中混合 sidebar、AI、resource、tree live 的所有事件处理。
-`web_shell.rs` 中继续堆叠资源打开和 pane host adapter 的长期浏览器逻辑;文档页 host adapter 应逐步迁到外置模块,但编辑器内部 Tiptap 命令仍归 `05-editor-mainline`
### 7.3 浏览器模块职责
浏览器 runtime 负责:
- 读取 bootstrap JSON。
- 绑定 DOM event。
- 收集 DOM 当前可见状态,构造 reducer request。
- 调用 Rust runtime reduce endpoint。
- 应用 `domPatches`
- 执行 `hostEvents`,例如 open page、open resource、context menu、upload files。
- 连接 tree live transport 并向页面分发 `tree:snapshot` / `tree:delta` / `tree:resync`
浏览器 runtime 不负责:
- 决定页面/资源父子真相。
- 决定资源归属。
- 决定 tree command 语义。
- 维护第二套 file tree projection。
## 8. 合同草案
### 8.1 Runtime asset manifest
新增或复用类似 `leptos_tiptap_manifest()` 的 runtime manifest
```json
{
"schema": "mnote.browser_runtime_manifest.v1",
"assets": {
"treeLiveController": "/api/mnote-browser-runtime/tree-live-controller.js",
"sidebarTreeRuntime": "/api/mnote-browser-runtime/sidebar-tree-runtime.js",
"treeShellRuntime": "/api/mnote-browser-runtime/tree-shell-runtime.js"
}
}
```
### 8.2 Tree live bootstrap
沿用当前 `mnote.tree_live_bootstrap.v1`,但由外置模块读取:
```json
{
"schema": "mnote.tree_live_bootstrap.v1",
"transport": "convex-command-log-ws",
"endpoint": "/api/tree/events",
"wsEndpoint": "/api/realtime/ws",
"views": ["page-tree", "file-tree"]
}
```
### 8.3 Host bridge
主壳提供最小 host bridge
```js
window.__mnoteTreeHost = {
openPage(detail) {},
openResource(detail) {},
dispatchCommand(command) {},
applyTreeSnapshot(payload) {},
applyTreeDelta(payload) {},
reportRuntimeError(error, context) {}
};
```
该 bridge 只是浏览器适配层,不是树真相。
### 8.4 Worker 任务切片合同
外置模块后,ClaudeCode / Reasonix 任务书必须优先指向单一模块和单一 smoke,不再让 worker 直接搜索整个 `layout.rs` / `web_shell.rs`
```text
允许读取:
- rust/crates/mnote-web/browser/resource-open-runtime.js
- rust/crates/mnote-web/src/ssr/pages/layout.rs 中 module bootstrap 小段
- scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js
禁止:
- 重写 sidebar 全局 IIFE
- 修改 Rust tree command 语义
- 修改不相关 Page AI panel
```
采纳 worker diff 的最低条件:
- diff 落在指定模块和测试内。
- handoff 说明调用链、未覆盖边界和验证命令。
- 主控能用 CodeGraph / `rg` 直接定位被修改符号。
- 浏览器 smoke 或截图证据可复核。
## 9. 执行策略
本设计按 Codex 主控、Reasonix 短跑 worker 协同执行:
- Codex 负责设计口径、模块边界、任务拆分、最终 diff 复核、测试和提交。
- Reasonix 只承接窄任务,每个 run 必须有任务书、允许文件、禁止事项、输出表格和 handoff。
- 同时最多启动 2 个 Reasonix worker;每批结束后 Codex 先读取 `process-handoff.md/json` 和 Hindsight `reasonix` recall,再决定是否采纳。
- 每个 worker 完成或超时后都应结束该 run,不在同一个长会话里连续追加不相关任务。
- Batch 0 只做只读审计,不修改文件、不跑 git 写操作,用来生成热点归属表和风险清单。
- Batch 1 起才允许实施单模块搬迁;每个实施 worker 只能修改一个 runtime 模块和对应 smoke。
当前建议批次:
- Batch 0 / R1:只读审计 `resource open / local upload / attachment action adapter`,输出函数归属表、目标模块、风险和 smoke 建议。
- Batch 0 / R2:只读审计 `leptos-tiptap` history / `set_content` 风险,结果写回 `05-editor-mainline` 设计,不混入本设计实现。
- Batch 1:外置 `resource-open-runtime.js` 的最小可验证切片,优先迁出纯 URL builder 和 layout bridge,不一次迁走 `web_shell.rs` 的完整 resource tab/session 生命周期。
- Batch 2:外置 `TREE_LIVE_CONTROLLER_JS`
- Batch 3:按审计结果选择 debug `/tree` bridge 或主壳 filetree runtime。
Batch 0 验收标准:
- R1 能列出 `layout.rs` / `web_shell.rs` 中资源打开、上传、附件动作相关入口和调用位置。
- R1 能给出“先拆哪个模块”的具体建议,并绑定现有 smoke。
- R2 的 editor 侧结论必须只作为跨设计参考;不得要求 3-19 修改 Tiptap 内部命令。
- Codex 必须把可采纳结论整合进本设计或 `5-28`,未采纳项记录原因。
### 9.1 Batch 0 执行记录(2026-05-24
Reasonix R1 run
- run id: `reasonix-2026-05-24T12-38-22-672Z-6ffcfa23`
- handoff: `/home/lix/.codex/runtime/reasonix-coding-worker/reasonix-2026-05-24T12-38-22-672Z-6ffcfa23/process-handoff.md`
- 类型:只读审计,无工作区改动。
- Codex 复核:已抽样用 `rg` 核对关键符号位置;Hindsight `reasonix` bank 可 recall 到该 handoff。
可采纳结论:
- `resource-open-runtime.js` 是 Batch 1 更合适的首个实施目标,但第一刀应只迁 `layout.rs` 中资源打开的纯函数和 bridge,例如:
- `buildOnlyOfficeOpenPath`
- `buildLocalFileOpenUrl`
- `openLocalOfficeFileInActiveTab`
- `openLocalResourceInActiveTab`
- `openConvexAssetFromFileTree`
- `openEditorAttachmentDetail`
- `openEditorAttachmentEditTab`
- `web_shell.rs``openResourceInActiveTab``createResourceTabDom``openTiptapResourceTab``replacePaneDocument``createEditorViewBinding` 等实际属于 resource tab / document pane host 生命周期,风险高,不应塞进 Batch 1 的同一个 worker。
- `local-upload-runtime.js` 是第二个高价值目标;真实入口是 `uploadFileToMediaAsset``insertUploadedAssetIntoEditor` 和 debug shell 的 `executeLocalFileTreeExternalDrop`,不是 `uploadFilesToLocalFolderTarget`
- `TREE_LIVE_CONTROLLER_JS` 仍适合作为 Batch 2:边界清楚,现有 smoke 对 WS/SSE fallback 覆盖更明确。
本轮发现的 worker 协同问题:
- Reasonix 能产出有用的只读审计表,但 runner 在产出 `completed` 结果后底层 `reasonix acp` 进程没有自动退出;主控必须显式检查并清理本轮启动的 worker 进程。
- Hindsight recall 可找到 handoff,但召回结果会混入同日其它 Reasonix 经验;主控仍必须读取本次 `process-handoff.md/json` 原文。
### 9.2 Batch 1 执行记录(2026-05-24
Reasonix implementation run
- run id: `reasonix-2026-05-24T12-49-55-633Z-be2844cb`
- worktree: `/mnt/Data1T/mnote-wt-3-19-resource-open-b1`
- handoff: `/home/lix/.codex/runtime/reasonix-coding-worker/reasonix-2026-05-24T12-49-55-633Z-be2844cb/process-handoff.md`
- Codex 采纳方式:从隔离 worktree 选择性合入主工作区;未提交 worker branch。
已落地的最小切片:
- 新增 `rust/crates/mnote-web/browser/resource-open-runtime.js`
- 新增固定 asset route`GET /api/mnote-browser-runtime/resource-open-runtime.js`
- `PageLayout``SIDEBAR_TREE_JS` 前注入 `type="module"` 外置脚本。
- `SIDEBAR_TREE_JS` 中以下 helper 先做 runtime 代理,保留原实现 fallback
- `buildOnlyOfficeOpenPath`
- `buildLocalFileOpenUrl`
- `localFilePathFromAssetId`
采纳边界:
- 本批次没有迁移 `openConvexAssetFromFileTree``openEditorAttachmentDetail``openEditorAttachmentEditTab` 等复杂行为函数。
- 本批次没有迁移 `web_shell.rs``openResourceInActiveTab`、resource tab DOM、session、pane host 生命周期。
- 固定 route 使用 `include_str!` 暴露单个 JS 文件,不提供动态 path,因此本批次不需要引入通用路径校验。
验证:
```bash
cargo fmt --manifest-path rust/Cargo.toml --all --check
cargo test -p mnote-web sidebar_tree_runtime_opens_office_assets_through_resource_shell
cargo test -p mnote-web sidebar_tree_runtime_opens_pdf_and_code_assets_with_builtin_tools
node --check rust/crates/mnote-web/browser/resource-open-runtime.js
```
全部通过。
## 10. 分阶段执行清单
### P0:定位辅助与基线保护
- [ ] 记录当前 CodeGraph 对内嵌 JS 的限制,确认不更换 graph 项目。
- [ ] 可选新增 `scripts/extract-embedded-js-for-index.js`,生成临时索引文件,不进入 runtime。
- [ ] 先生成一份“热点函数归属表”,把最近 bug 中高频搜索的函数归到待拆模块:
- `openResource` / `openLocalOfficeFileInActiveTab` / `openEditorAttachmentEditTab` -> `resource-open-runtime.js`
- `executeLocalFileTreeExternalDrop` / `uploadFileToMediaAsset` / `insertUploadedAssetIntoEditor` -> `local-upload-runtime.js`
- `startWithWebSocket` / `startWithSseFallback` -> `tree-live-controller.js`
- `selectFileTreeRow` / `openFileTreeContextMenu` -> `filetree-selection-runtime.js` / `filetree-context-menu-runtime.js`
- `createEditorViewBinding` / `replacePaneDocument` -> `document-pane-host-runtime.js`,归 `03-rust-web``05-editor-mainline` 交界,需单独切片。
- [ ]`layout.rs` / `tree.rs` 中计划迁出的 script 增加最小字符串快照测试,防止迁移时漏挂载。
- [ ] 建立 smoke 基线:
- `node scripts/task487-local-folder-tree-live-consumer-smoke.js`
- `node scripts/task446-tree-rename-dual-browser-live-smoke.js`
- `node scripts/task472-side-target-secondary-pane-smoke.js`
- `node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js`
### P1:外置 resource open / local upload adapter
- [x] 新增 `rust/crates/mnote-web/browser/resource-open-runtime.js`
- [ ] 新增 `rust/crates/mnote-web/browser/local-upload-runtime.js`,或先与 resource open 合并为单个不超过 800 行的 `resource-open-runtime.js`
- [x] 第一刀只迁 `layout.rs` 中 resource open 的纯函数和 bridge`web_shell.rs` 的 resource tab DOM、session、pane host 生命周期保持在后续 `document-pane-host-runtime.js` 批次。
- [ ]`SIDEBAR_TREE_JS` / `web_shell.rs` 中迁出资源打开路径适配,不改变现有 open intent
- page row -> 当前 pane page tab
- filetree doc row -> Markdown page tab
- local Markdown asset -> active resource tab / secondary pane
- pdf/code -> 对应 lightweight viewer
- office -> OnlyOffice path builder
- [ ]`SIDEBAR_TREE_JS` 中迁出本地文件夹上传目标计算和 adapter 调用;资源归属语义仍由 Rust API / kernel 合同决定。
- [ ] 保持现有 smoke
- `node scripts/task472-side-target-secondary-pane-smoke.js`
- `node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js`
- [ ] 验证 CodeGraph 或等价结构搜索能定位 `openResource``buildOnlyOfficeOpenPath``executeLocalFileTreeExternalDrop`
### P2:外置 `TREE_LIVE_CONTROLLER_JS`
- [ ] 新增 `rust/crates/mnote-web/browser/tree-live-controller.js`
- [ ] `layout.rs` 只注入 `__MNOTE_TREE_LIVE_BOOTSTRAP__``<script type="module" src="...">`
- [ ] 新增 `/api/mnote-browser-runtime/tree-live-controller.js` 路由,返回 `application/javascript; charset=utf-8`
- [ ] 保持现有 WS 优先、SSE fallback、local-folder event fallback 行为。
- [ ] 浏览器断言:
- `document.documentElement.dataset.mnoteTreeLiveTransport` 正确。
- WS 推送仍更新 page/filetree。
- WS 不可用时仍 fallback 到 SSE。
### P3:外置 debug `/tree` shell runtime bridge
- [ ]`tree.rs``tree-shell-state` 后的大块 JS 迁到 `browser/tree-shell-runtime.js`
- [ ] `build_tree_shell_html()` 只渲染 HTML、CSS、bootstrap JSON 和 module script。
- [ ] 保持 `/api/tree/runtime/reduce` 合同不变。
- [ ] debug shell page/filetree/picker 三个模式 smoke 保持通过。
- [ ] 验证 CodeGraph 能定位 `reducePageActionWithRuntime``buildFileTreeRuntimeEnvironment``reconcilePageRuntimeResult` 等符号。
### P4:抽出主壳 filetree runtime
- [ ]`SIDEBAR_TREE_JS` 中拆出文件树选择、keyboard、clipboard、DND、context menu。
- [ ] 先拆纯函数和 reducer bridge,再拆 DOM 绑定。
- [ ] 主壳保留 `window.__mnoteSidebarTreeRuntimeStarted` 一类启动防重保护,但启动函数迁到外置模块。
- [ ] 与 Rust `FileTreeSelectionState` 合同对齐,不在前端新增第二套选择真相。
- [ ] 覆盖:
- 多选、shift range、ctrl/meta toggle。
- cut/copy/paste。
- 内部拖拽 move/copy。
- 外部文件拖入 local folder。
- readonly target 拒绝。
### P5:抽出 document pane host adapter
- [ ]`web_shell.rs` 的 inline module 中拆出文档 pane host adapter,例如:
- `document-pane-host-runtime.js`
- `document-session-runtime.js`
- `document-conflict-panel-runtime.js`
- [ ] 只迁 host/session/pane 生命周期,不迁 Tiptap 编辑器内部命令。
- [ ]`05-editor-mainline` 的编辑器 runtime 拆分保持边界:host 负责挂载、pane、session、资源 tabeditor 负责命令、history、附件链接、DOM overlay。
- [ ] 覆盖:
- primary / secondary pane 切换
- conflict panel unmount 清理
- attachment tab 打开
- resource tab error placeholder
### P6:收尾与旧 inline script 瘦身
- [ ] `layout.rs` 中只保留小型 bootstrap 和脚本引用。
- [ ] `tree.rs` 中不再内嵌大型 runtime JS。
- [ ] `web_shell.rs` 中只保留 page aggregate/bootstrap JSON 和 module script 引用。
- [ ] 更新 CodeGraph 索引,确认新增 JS 模块被索引。
- [ ] 删除或降级 P0 提取脚本:如果真实模块已覆盖主要 runtime,提取脚本只保留为历史/临时工具。
## 11. 测试与验收
### 11.1 Rust 测试
```bash
cd /mnt/Data1T/mnote/rust
cargo test -p mnote-web tree_shell
cargo test -p mnote-web filetree
cargo test -p mnote-web runtime
```
### 11.2 Browser smoke
```bash
node scripts/task446-tree-rename-dual-browser-live-smoke.js
node scripts/task447-tree-move-order-dual-browser-live-smoke.js
node scripts/task448-tree-resync-recovery-dual-browser-smoke.js
node scripts/task449-tree-sse-reconnect-snapshot-recovery-smoke.js
node scripts/task472-side-target-secondary-pane-smoke.js
node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js
node scripts/task487-local-folder-tree-live-consumer-smoke.js
```
### 11.3 CodeGraph 验收
迁移后必须能通过 CodeGraph 或等价结构搜索定位以下浏览器符号:
- `startWithWebSocket`
- `startWithSseFallback`
- `buildOnlyOfficeOpenPath`
- `openEditorAttachmentEditTab`
- `reducePageActionWithRuntime`
- `buildFileTreeRuntimeEnvironment`
- `selectFileTreeRow`
- `executeLocalFileTreeExternalDrop`
- `openFileTreeContextMenu`
- `openResource`
验收标准:
- 这些符号不再只出现在 Rust raw string 中。
- `codegraph_status` 显示新增 JS 文件被索引。
- 字面搜索命中数量减少,定位结果能落到具体 runtime 模块。
- worker 任务书能以“单模块 + 单 smoke”派发,不再要求 worker 搜索整个 `layout.rs` / `web_shell.rs`
## 12. 风险与控制
- 风险:模块加载路径错误导致主壳首屏无交互。
- 控制:第一阶段沿用现有 no-store asset route;每次切片保留 smoke。
- 风险:外置模块改变执行时机。
- 控制:保留 `DOMContentLoaded` / immediate start 逻辑;迁移前后记录启动 DOM 标记。
- 风险:debug shell 与主壳复用时把 debug 行为带进主路径。
- 控制:host bridge 分为 `debugTreeShellHost``workspaceSidebarHost`,共享纯 runtime,不共享 debug UI。
- 风险:前端模块继续膨胀成新的巨文件。
- 控制:单文件超过约 800 行时必须拆职责;DND、selection、transport、resource open、context menu 分文件。
- 风险:外置 JS 无类型约束导致合同漂移。
- 控制:第一阶段用 Rust JSON contract 测试 + browser smoke;稳定后再评估 TypeScript 或 JSDoc typedef。
- 风险:resource open adapter 迁出时顺手改变 open target 行为。
- 控制:先建立 open intent 表,迁移只做同构搬迁;secondary pane、active tab、new window fallback 必须由 smoke 覆盖。
- 风险:`03-rust-web``05-editor-mainline` 边界混淆。
- 控制:本设计只处理 shell/browser host runtimeTiptap 命令、history、附件链接增强、编辑器 DOM overlay 由 `05-editor-mainline` 设计处理。
## 13. Done Gate
本设计移动到 `done/` 前必须满足:
- [ ] `TREE_LIVE_CONTROLLER_JS` 已从 `layout.rs` 迁出。
- [ ] debug `/tree` shell runtime 大块 JS 已从 `tree.rs` 迁出,或已有明确豁免说明。
- [ ] 主壳 filetree runtime 的核心 selection / DND / context menu 至少完成一个模块外置切片。
- [ ] resource open / local upload adapter 至少完成一个高频 bug 模块外置切片。
- [ ] 新增 JS runtime 文件能被 CodeGraph 索引。
- [ ] 至少一组 tree realtime smoke 和一组 local folder filetree/resource smoke 通过。
- [ ] `layout.rs` / `web_shell.rs` 不再继续新增大型 inline browser runtime。
## 14. 后续问题
- 是否引入 TypeScript:建议等 P1-P3 完成后再决定,不作为本设计前置。
- 是否引入 bundler:当前不需要;plain ES module 足够完成第一阶段。
- 是否把 Page AI panel 一并迁出:不建议混在本设计中。Page AI panel 应另起 `07-ai``05-editor-mainline` 设计,避免 tree runtime 重构扩大范围。
- 是否保留内嵌 JS 提取脚本:如果 P1-P3 推进顺利,可只作为临时诊断工具;如果短期没有资源迁出大块 runtime,则保留脚本辅助 CodeGraph。
@@ -0,0 +1,311 @@
# 5-28 Leptos Tiptap 编辑器 Runtime 模块拆分 v1
> 创建时间:2026-05-24
> 状态:`process`
>
> 触发背景:
> - `bugs/0524.md` 第 4 条删除本地 Markdown 附件链接后 `Ctrl+Z/Ctrl+Y` 不可用,根因是编辑器删除路径绕过 Tiptap/ProseMirror history。
> - 修复过程中 CodeGraph 能索引 Rust 符号,但 `rust/spikes/leptos-tiptap-spike/src/lib.rs` 单文件约一万行,职责过多,主控和 worker 都需要反复字面搜索。
> - `design/03-rust-web/process/3-19-rust-web-browser-runtime-module-extraction-v1.md` 解决 Rust Web shell/browser host runtime 外置化;本设计补齐编辑器主线拆分。
## 1. 结论
`leptos-tiptap-spike/src/lib.rs` 的问题不是 raw string,而是编辑器 runtime 仍以单个巨型 Rust 文件承载过多职责:Tiptap 初始化、命令执行、附件上传、附件链接增强、history、块菜单、DOM overlay、mindmap node view、page options、AI surface 都混在一起。CodeGraph 对这种文件只能给出符号位置,不能给出清晰模块边界。
后续不应继续在该文件中追加新的编辑器行为。应分阶段把编辑器命令、附件链接/上传、history 安全命令、DOM bridge/overlay、mindmap node view 拆入独立 Rust 模块;保持 `leptos-tiptap` island 仍是当前文档页主编辑器 runtime,但让每个行为域能被单独搜索、测试、派发给 worker。
## 2. 当前代码基线
- `rust/spikes/leptos-tiptap-spike/src/lib.rs`
- 约一万行。
- 同时包含 CSS、Tiptap extension 列表、slash menu、toolbar、block menu、table toolbar、attachment upload、attachment link handling、local folder path、history/undo、DOM selection、mindmap node view、runtime bridge。
- 最近第 4 条 bug 的关键函数和逻辑散落在同一文件中,例如附件链接识别、keydown 处理、`apply_html_update``sync_persisted_editor_command``delete_top_level_block_html`
- `reference-code/leptos-tiptap/`
- 提供底层 Tiptap Rust/JS bridge。
- 已有 `TiptapEditorHandle``TiptapExtension``TiptapRange``delete_selection``delete_range``undo``redo` 等命令。
- MNote 不应把这些底层 bridge 再复制一遍,而应在 spike runtime 上方建立 MNote 编辑器行为模块。
## 3. 与 3-19 的边界
`03-rust-web` 负责:
- Rust Web SSR shell。
- document pane host adapter。
- resource tab / secondary pane / URL / conflict panel 生命周期。
- filetree / resource open / local upload 的 shell 侧适配。
`05-editor-mainline` 负责:
- Tiptap 编辑器内部命令。
- 编辑器内附件链接和上传块行为。
- undo/redo/history 安全边界。
- 编辑器 DOM selection / overlay / toolbar / block menu。
- Tiptap node view,例如 mindmap block。
交界规则:
- Shell 可以告诉 editor “打开某个文档/资源/保存状态/挂载 pane”。
- Editor 可以告诉 shell “内容变更/附件打开 intent/资源上传完成/冲突状态”。
- Shell 不应直接改 Tiptap document 内部结构。
- Editor 不应决定 workspace tree/resource 归属真相。
## 4. 目标
-`src/lib.rs` 从“巨型编辑器 runtime”收敛为组件入口和模块装配层。
- 让附件、history、命令、DOM bridge 等高频 bug 区域成为独立 Rust 模块。
- 修 bug 时能把 worker 限定到单一模块和单一 smoke。
- 统一编辑器命令合同:能用 Tiptap command/transaction 的行为,不再通过 `set_content`/HTML 整文替换实现用户编辑动作。
- 建立 history 安全基线:删除、插入、重排、格式转换、附件操作都要明确是否进入 undo stack。
## 5. 非目标
- 不替换 Tiptap 或 `leptos-tiptap` bridge。
- 不把文档事实源迁回前端。
- 不一次性重写编辑器 UI。
- 不在本设计中实现 Phase C 流式 apply / review session。
- 不把 Page AI panel 或 Hermes/Reasonix 工具链混入本次编辑器 runtime 拆分。
## 6. 目标模块布局
建议新增目录:
```text
rust/spikes/leptos-tiptap-spike/src/
lib.rs
editor_runtime/
mod.rs
extensions.rs
command_sync.rs
history_safe_commands.rs
attachment_links.rs
attachment_upload.rs
local_markdown_paths.rs
dom_selection.rs
overlays.rs
block_menu.rs
table_toolbar.rs
mindmap_node_view.rs
bridge_events.rs
persistence.rs
```
第一阶段可以只拆高频问题模块,不强求一次建完所有文件。
## 7. 分阶段执行清单
### P0:热点函数归属表和基线保护
- [ ] 列出当前 `src/lib.rs` 中最近 bug 高频函数的归属模块:
- `selected_local_attachment_link_in_editor_state` -> `attachment_links.rs`
- `selected_local_attachment_link_text` -> `attachment_links.rs` / `dom_selection.rs`
- `sync_persisted_editor_command` -> `command_sync.rs`
- `apply_html_update` / `apply_document_update` -> `command_sync.rs`,并标记 history 风险
- `delete_top_level_block_html` / `reorder_top_level_block_html` -> 待替换为 `history_safe_commands.rs`
- upload slash action / local asset path -> `attachment_upload.rs` / `local_markdown_paths.rs`
- toolbar/block menu DOM anchor -> `overlays.rs` / `block_menu.rs`
- [ ] 建立 history 风险清单:所有用户编辑动作中仍调用 `set_content` 或 HTML 整文替换的路径必须登记。
- [ ] 保留当前已通过的 `scripts/task488-local-attachment-link-delete-undo-smoke.js` 作为 RED/GREEN 基线。
### P1:抽出附件链接和 history-safe delete
- [ ] 新增 `editor_runtime/attachment_links.rs`
- [ ] 迁出本地附件链接识别:
- DOM selection fallback。
- Tiptap editor state link attributes 判断。
- `/api/local-folder/files/open` 链接识别。
- [ ] 新增 `editor_runtime/history_safe_commands.rs`
- [ ] 将“选中附件链接文本后 Delete/Backspace”收口为命名函数,例如 `delete_selected_attachment_link_with_history(...)`
- [ ] 验证 `task488` 仍通过。
### P2:抽出 command sync / persistence
- [ ] 新增 `editor_runtime/command_sync.rs`
- [ ] 迁出:
- `read_editor_snapshot`
- `sync_editor_outputs`
- `sync_persisted_editor_command`
- change/status event dispatch 的编辑器侧包装
- [ ] 明确 `set_content` 只允许用于:
- 初始加载
- host 替换文档
- 外部同步/恢复
- 非用户编辑的兼容兜底
- [ ] 用户编辑动作必须优先走 Tiptap command/transaction。
### P3:抽出附件上传与本地 Markdown 路径
- [ ] 新增 `editor_runtime/attachment_upload.rs`
- [ ] 新增 `editor_runtime/local_markdown_paths.rs`
- [ ] 迁出 slash 上传附件、图片、Office/PDF/code 文件的编辑器内插入逻辑。
- [ ] 明确 editor 只负责插入附件链接/块,资源保存位置和归属仍由 shell/Rust API 合同决定。
- [ ] 覆盖:
- 上传第一个/第二个附件后链接仍可点击。
- 刷新后链接不退化为普通外链。
- secondary pane 上传与 primary pane 隔离。
### P4:抽出 DOM bridge / overlay / block menu
- [ ] 新增 `editor_runtime/dom_selection.rs``overlays.rs``block_menu.rs`
- [ ] 迁出 selection、toolbar anchor、slash menu anchor、block menu anchor、image/table toolbar anchor。
- [ ] 对所有 keydown/click 捕获建立“谁先处理、谁 stop propagation”的规则,避免 Tiptap 原生快捷键与外层 window listener 互相吞事件。
- [ ] 覆盖:
- `Ctrl+Z/Ctrl+Y`
- slash menu
- block menu
- table toolbar
- attachment context menu
### P5:抽出 mindmap node view
- [ ] 新增 `editor_runtime/mindmap_node_view.rs`
- [ ] 迁出 mindmap block 的 node view、runtime mount、projection refresh、simplemindmap event bridge。
- [ ] 保持 mindmap 只是编辑器块视图,不成为正文事实源。
### P6:收尾
- [ ] `src/lib.rs` 只保留:
- module imports
- top-level component装配
- 少量常量和 view glue
- [ ] 单个模块超过约 800 行时继续拆分。
- [ ] 运行 CodeGraph sync/index,确认新增 Rust 模块被索引。
- [ ] 更新 worker skill/task template:编辑器 bug 默认先查 `editor_runtime/*`,再回到 `lib.rs`
## 8. History 安全规则
用户直接触发的编辑动作:
- 必须优先使用 Tiptap command/transaction。
- 如果临时必须使用 `set_content`,必须在 bug/design 中标明为什么不能走 command,并补一个 undo/redo 不支持或受限的说明。
- 新增编辑动作的 smoke 或单测应明确检查 undo/redo,除非该动作按产品定义不可撤销。
非用户编辑动作:
- 初始加载、host 替换文档、外部文件 watcher 重载、冲突恢复可以使用 `set_content`,但必须避免污染用户 undo stack。
- 外部同步应与 working copy 冲突模型结合,不能悄悄覆盖当前用户编辑。
## 9. Worker 协同规则
拆分后给 ClaudeCode / Reasonix 的任务应限定为:
```text
允许读取/修改:
- rust/spikes/leptos-tiptap-spike/src/editor_runtime/attachment_links.rs
- rust/spikes/leptos-tiptap-spike/src/editor_runtime/history_safe_commands.rs
- scripts/task488-local-attachment-link-delete-undo-smoke.js
禁止:
- 修改 shell resource open 逻辑
- 修改 Rust tree command 语义
- 整文重写 src/lib.rs
```
采纳条件:
- worker handoff 明确命中哪个模块。
- diff 不跨越无关模块。
- 主控能运行对应 smoke。
- UI/浏览器行为必须有截图或 result.json 可审计证据。
## 10. 执行策略
本设计与 `3-19` 同步采用 Codex 主控、Reasonix 短跑 worker
- Codex 负责编辑器主线边界、history 规则、最终 diff 复核和 smoke 验收。
- Reasonix 只处理短任务,不在一个 run 里同时做架构判断、代码迁移和浏览器验收。
- 同时最多 2 个 Reasonix worker;每批完成后必须先读取 `process-handoff.md/json` 和 Hindsight `reasonix` recall,再进入下一批。
- Batch 0 只读,不修改文件,用来生成 `src/lib.rs` 热点函数归属表和 `set_content` / HTML 整文替换风险表。
- 实施批次必须优先放在 `editor_runtime/*`,避免继续向 `src/lib.rs` 追加大块逻辑。
当前建议批次:
- Batch 0 / R2:只读审计 `rust/spikes/leptos-tiptap-spike/src/lib.rs` 中 history、`set_content`、附件链接删除、上传插入相关路径,输出函数归属表和风险等级。
- Batch 1:先抽出 `attachment_links.rs` 和最小 `history_safe_commands.rs`,保持 `task488` 通过;随后优先处理块菜单 delete 仍走 HTML 整文替换的 history 风险。
- Batch 2:抽出 `command_sync.rs` / `persistence.rs`,登记仍允许 `set_content` 的 host 替换和外部同步路径。
- Batch 3:抽出 `attachment_upload.rs` / `local_markdown_paths.rs`,覆盖第一个/第二个附件上传和点击打开 smoke。
- Batch 4:抽出 DOM selection / overlay / block menu,建立 keydown/click 捕获顺序规则。
Batch 0 验收标准:
- R2 必须列出搜索过的函数、文件位置、当前职责、建议目标模块、history 风险和 smoke 建议。
- R2 不允许修改源码、脚本、文档或运行 git 写操作。
- Codex 只采纳能被本地源码定位和现有 bug 证据支持的结论。
- 若 R2 输出泛化建议但无法对应具体函数,视为不可采纳,只记录为 worker 质量问题。
### 10.1 Batch 0 执行记录(2026-05-24
Reasonix R2 run
- run id: `reasonix-2026-05-24T12-38-22-683Z-3826b3b6`
- handoff: `/home/lix/.codex/runtime/reasonix-coding-worker/reasonix-2026-05-24T12-38-22-683Z-3826b3b6/process-handoff.md`
- 类型:只读审计,无工作区改动。
- Codex 复核:已抽样用 `rg` 核对关键符号位置;Hindsight `reasonix` bank 可 recall 到该 handoff。
可采纳结论:
- 最近已修复的附件链接 `Delete/Backspace` 路径现在使用 `editor.delete_selection()`,属于可迁入 `history_safe_commands.rs` 的正向模板。
- `selected_local_attachment_link_text``selected_local_attachment_link_in_editor_state` 可优先迁入 `attachment_links.rs`,迁移本身应保持行为不变并继续跑 `task488`
- 仍需登记的高风险用户编辑路径:
- 块菜单 delete`delete_top_level_block_html` -> `apply_html_update` -> `editor.set_content(TiptapContent::html(...))`,用户删除动作绕过 history,后续应替换为 Tiptap command/transaction。
- 块类型转换或 slash action 中经过 `apply_document_update` 的路径:`editor.set_content(TiptapContent::json(...))` 可能绕过 history,需要逐调用点确认。
- `run_block_turn_into_page_action` / `run_block_turn_into_page`:整文 JSON 替换,是否可撤销属于产品决策,不在第一刀默认修改。
- 可接受的 `set_content` 路径:
- `apply_host_document_payload`
- 外部 block delta listener
- 编辑器初始化恢复本地草稿
这些是非用户直接编辑动作,允许保留,但必须避免污染用户 undo stack 和覆盖当前用户编辑。
本轮发现的 worker 协同问题:
- Reasonix 对大文件只读审计有效,但输出中的“行号和风险分级”只能作为候选事实;主控必须用本地 `rg` / 源码抽样复核。
- Reasonix runner 产出 `completed` 结果后底层 `reasonix acp` 进程未自动退出;主控必须在每批结束时检查并清理本轮启动进程。
## 11. 测试与验收
### Rust 检查
```bash
cargo fmt --manifest-path rust/Cargo.toml --all --check
cargo check --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml
cargo build --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml --target wasm32-unknown-unknown --release
wasm-bindgen rust/spikes/leptos-tiptap-spike/target/wasm32-unknown-unknown/release/mnote_leptos_tiptap_spike.wasm --target web --out-dir rust/spikes/leptos-tiptap-spike/generated/island --out-name mnote-leptos-tiptap-spike-island
```
### Browser smoke
```bash
node scripts/task488-local-attachment-link-delete-undo-smoke.js
node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js
node scripts/task472-side-target-secondary-pane-smoke.js
```
后续拆到 block menu / table toolbar 时,应补对应 smoke,不复用附件 smoke 代替。
## 12. 风险与控制
- 风险:拆模块时改变 Leptos signal 捕获和生命周期。
- 控制:每次只迁一个行为域;迁移后立即跑 wasm build 和浏览器 smoke。
- 风险:为了通过编译把函数参数扩得过大。
- 控制:只在边界稳定后引入小型 context struct;不要先抽象大而全 runtime context。
- 风险:把 shell/resource 语义搬进 editor 模块。
- 控制:editor 只发出 intent,不决定 workspace/resource 真相。
- 风险:继续产生新的巨型模块。
- 控制:单模块超过约 800 行必须拆出子模块。
## 13. Done Gate
本设计移动到 `done/` 前必须满足:
- [ ] `attachment_links.rs``history_safe_commands.rs` 已落地。
- [ ] `command_sync.rs` 已落地,用户编辑动作与 host 替换动作边界明确。
- [ ] 至少一个仍使用 `set_content` 的用户编辑路径已迁到 Tiptap command/transaction。
- [ ] `task488` 通过,且至少覆盖一次真实键盘 `Ctrl+Z/Ctrl+Y`
- [ ] 新增 editor_runtime Rust 模块能被 CodeGraph 定位。
- [ ] `src/lib.rs` 不再新增附件/history/DOM bridge 大块逻辑。
## 14. 后续问题
- 是否把 `leptos-tiptap-spike` 从 spike 正式改名:不作为本设计前置,等模块拆分后再评估。
- 是否回 upstream `reference-code/leptos-tiptap` 增加命令:只有当底层 bridge 缺少通用 Tiptap command 时才考虑;MNote 特有行为优先留在 `editor_runtime`
- 是否引入更强类型的 editor command contractP1-P3 完成后再评估,不作为第一阶段阻塞项。
@@ -0,0 +1,54 @@
// MNote 资源打开运行时外置模块。
// 当前先承接 SIDEBAR_TREE_JS 中的低风险纯辅助函数。
// 本文件以 type="module" 加载,执行时机可能晚于 inline script。
// SIDEBAR_TREE_JS 会在运行时优先委托到 window.__mnoteResourceOpenRuntime。
// 模块尚未加载或加载失败时,inline script 保留原实现兜底。
function localFilePathFromAssetId(assetId) {
var value = String(assetId || '').trim();
return value.indexOf('local-file:') === 0
? value.slice('local-file:'.length)
: value.indexOf('local:asset:') === 0
? value.slice('local:asset:'.length)
: '';
}
function buildLocalFileOpenUrl(relativePath, download) {
var rootUri = currentRootUri();
if (!rootUri || !relativePath) return '';
var url = new URL('/api/local-folder/files/open', window.location.origin);
url.searchParams.set('rootUri', rootUri);
url.searchParams.set('path', relativePath);
if (download) url.searchParams.set('download', 'true');
return url.toString();
}
function buildOnlyOfficeOpenPath(input) {
var params = new URLSearchParams();
params.set('fileUrl', input.fileUrl || '');
params.set('fileName', input.fileName || '未命名资源');
params.set('fileType', input.fileType || 'docx');
if (input.assetId) params.set('assetId', input.assetId);
if (input.documentId) params.set('documentId', input.documentId);
if (input.userId) params.set('userId', input.userId);
params.set('mode', input.mode || 'view');
if (input.documentId && input.assetId) {
return '/office/' + encodeURIComponent(input.documentId) + '/' + encodeURIComponent(input.assetId) + '?' + params.toString();
}
return '/onlyoffice?' + params.toString();
}
function currentRootUri() {
var params = new URLSearchParams(window.location.search);
var fromUrl = (params.get('rootUri') || '').trim();
if (fromUrl) return fromUrl;
return document.body instanceof HTMLElement
? (document.body.getAttribute('data-mnote-root-uri') || '').trim()
: '';
}
window.__mnoteResourceOpenRuntime = {
localFilePathFromAssetId: localFilePathFromAssetId,
buildLocalFileOpenUrl: buildLocalFileOpenUrl,
buildOnlyOfficeOpenPath: buildOnlyOfficeOpenPath
};
+4
View File
@@ -94,6 +94,10 @@ pub fn build_router(state: AppState) -> Router {
"/api/editor/image-placeholder.svg",
get(web_shell::editor_image_placeholder_asset),
)
.route(
"/api/mnote-browser-runtime/resource-open-runtime.js",
get(web_shell::resource_open_runtime_asset),
)
.route("/api/search/documents", post(search::documents))
.route(
"/api/search/local-index/refresh",
@@ -4366,6 +4366,21 @@ pub async fn editor_image_placeholder_asset() -> Response {
.unwrap_or_else(|_| Response::new(Body::empty()))
}
pub async fn resource_open_runtime_asset() -> Response {
// include_str! 路径相对于当前源文件 (src/routes/web_shell.rs -> ../../browser/)
const JS: &str = include_str!("../../browser/resource-open-runtime.js");
Response::builder()
.status(StatusCode::OK)
.header(
header::CONTENT_TYPE,
"application/javascript; charset=utf-8",
)
.header(header::CACHE_CONTROL, "no-store")
.header(HEADER_MNOTE_WEB_OWNER, "mnote-web")
.body(Body::from(JS))
.unwrap_or_else(|_| Response::new(Body::empty()))
}
pub async fn leptos_tiptap_manifest() -> Response {
let manifest = json!({
"entryAssetPath": "mnote-leptos-tiptap-spike-island.js",
@@ -2562,6 +2562,10 @@ const SIDEBAR_TREE_JS: &str = r##"
}
function buildOnlyOfficeOpenPath(input) {
var _rto_ = window.__mnoteResourceOpenRuntime;
if (_rto_ && typeof _rto_.buildOnlyOfficeOpenPath === 'function') {
return _rto_.buildOnlyOfficeOpenPath(input);
}
var params = new URLSearchParams();
params.set('fileUrl', input.fileUrl || '');
params.set('fileName', input.fileName || '');
@@ -2671,11 +2675,19 @@ const SIDEBAR_TREE_JS: &str = r##"
}
function localFilePathFromAssetId(assetId) {
var _rto_ = window.__mnoteResourceOpenRuntime;
if (_rto_ && typeof _rto_.localFilePathFromAssetId === 'function') {
return _rto_.localFilePathFromAssetId(assetId);
}
var value = String(assetId || '').trim();
return value.indexOf('local-file:') === 0 ? value.slice('local-file:'.length) : value.indexOf('local:asset:') === 0 ? value.slice('local:asset:'.length) : '';
}
function buildLocalFileOpenUrl(relativePath, download) {
var _rto_ = window.__mnoteResourceOpenRuntime;
if (_rto_ && typeof _rto_.buildLocalFileOpenUrl === 'function') {
return _rto_.buildLocalFileOpenUrl(relativePath, download);
}
var rootUri = currentRootUri();
if (!rootUri || !relativePath) return '';
var url = new URL('/api/local-folder/files/open', window.location.origin);
@@ -10639,6 +10651,7 @@ pub fn PageLayout(
<div hidden data-testid="mnote-admin-access-policy-template-admin" inner_html={admin_access_policy_template}></div>
<div hidden data-testid="mnote-admin-access-policy-template-user" inner_html={user_access_policy_template}></div>
<script inner_html={crate::ssr::pages::admin::ADMIN_POLICY_SCRIPT.to_string()}></script>
<script type="module" src="/api/mnote-browser-runtime/resource-open-runtime.js"></script>
<script inner_html={SIDEBAR_TREE_JS.to_string()}></script>
<script id="mnote-tree-live-controller" inner_html={TREE_LIVE_CONTROLLER_JS.to_string()}></script>
</aside>