diff --git a/design/10-review/process/16-mnote-web-runtime-module-maintainability-checklist-v1.md b/design/10-review/process/16-mnote-web-runtime-module-maintainability-checklist-v1.md index fc210625..879c90d7 100644 --- a/design/10-review/process/16-mnote-web-runtime-module-maintainability-checklist-v1.md +++ b/design/10-review/process/16-mnote-web-runtime-module-maintainability-checklist-v1.md @@ -205,7 +205,7 @@ cargo test --manifest-path rust/Cargo.toml -p mnote-web --lib ssr::pages::layout 拆分项: - [ ] D1. `tree-shell-state-runtime.js`:state hydration、serialization、patch dispatch。 -- [ ] D2. `tree-shell-page-runtime.js`:page tree keyboard、expand、focus、drag/drop intent。 +- [x] D2. `tree-shell-page-runtime.js`:page tree keyboard、expand、focus、drag/drop intent。 - [ ] D3. `tree-shell-filetree-runtime.js`:filetree row normalization、resource meta、open target。 - [ ] D4. `tree-shell-picker-runtime.js`:picker search/focus/pick root。 - [ ] D5. `tree-shell-dom-runtime.js`:DOM patch/render helpers。 diff --git a/rust/crates/mnote-web/browser/tree-shell-page-runtime.js b/rust/crates/mnote-web/browser/tree-shell-page-runtime.js new file mode 100644 index 00000000..fb037c15 --- /dev/null +++ b/rust/crates/mnote-web/browser/tree-shell-page-runtime.js @@ -0,0 +1,55 @@ +// MNote debug /tree shell page-mode runtime helpers. + +export async function reducePageActionWithRuntime(runtimeContext, action, item) { + if (runtimeContext.mode !== "page" || !runtimeContext.runtimeReduceEndpoint) { + return null; + } + const runtimeAction = runtimeContext.buildPageRuntimeAction(action, item); + if (!runtimeAction) return null; + const response = await fetch(runtimeContext.runtimeReduceEndpoint, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + mode: "page", + requestId: `page-runtime-${Date.now()}`, + environment: runtimeContext.buildPageRuntimeEnvironment(), + state: runtimeContext.readPageRuntimeState(action, item), + action: runtimeAction, + }), + }); + if (!response.ok) { + throw new Error(await runtimeContext.readErrorMessage(response)); + } + return response.json(); +} + +export function reconcilePageRuntimeResult(runtimeContext, runtimeResult, item) { + const result = runtimeContext.normalizePageRuntimeResult(runtimeResult); + if (!result) return false; + const previousExpanded = new Set(runtimeContext.expanded); + let shouldPatchTree = false; + if (Array.isArray(result.expandedIds)) { + shouldPatchTree = + runtimeContext.commitPageExpandedIds(result.expandedIds) || shouldPatchTree; + } + const currentFocusedNodeId = runtimeContext.getFocusedNodeId(); + if (result.focusedId && result.focusedId !== currentFocusedNodeId) { + runtimeContext.setFocusedNodeId(result.focusedId); + runtimeContext.postPageFocusChange(result.focusedId); + shouldPatchTree = true; + } + const itemNodeId = runtimeContext.normalizeText(item?.nodeId); + if ( + itemNodeId && + previousExpanded.has(itemNodeId) !== runtimeContext.expanded.has(itemNodeId) + ) { + runtimeContext.postPageExpandChange(itemNodeId, runtimeContext.expanded.has(itemNodeId)); + } + if (shouldPatchTree) { + runtimeContext.patchPageTreeAfterRuntimeState( + Array.isArray(result.expandedIds) ? [itemNodeId, ...result.expandedIds] : [itemNodeId], + result.focusedId || runtimeContext.getFocusedNodeId(), + ); + } + return shouldPatchTree; +} diff --git a/rust/crates/mnote-web/browser/tree-shell-runtime.js b/rust/crates/mnote-web/browser/tree-shell-runtime.js index d857c400..a753b27e 100644 --- a/rust/crates/mnote-web/browser/tree-shell-runtime.js +++ b/rust/crates/mnote-web/browser/tree-shell-runtime.js @@ -1,6 +1,11 @@ // MNote debug /tree shell 浏览器运行时外置模块。 // 该文件由 tree.rs 原 inline runtime 同构迁出,语义仍由 Rust tree_shell reducer 主导。 +import { + reconcilePageRuntimeResult, + reducePageActionWithRuntime, +} from "./tree-shell-page-runtime.js"; + function buildFileTreeRuntimeEnvironment(runtimeContext) { const fileTreeRowById = runtimeContext.fileTreeRowById || new Map(); return { @@ -17,60 +22,6 @@ function buildFileTreeRuntimeEnvironment(runtimeContext) { }; } -async function reducePageActionWithRuntime(runtimeContext, action, item) { - if (runtimeContext.mode !== "page" || !runtimeContext.runtimeReduceEndpoint) { - return null; - } - const runtimeAction = runtimeContext.buildPageRuntimeAction(action, item); - if (!runtimeAction) return null; - const response = await fetch(runtimeContext.runtimeReduceEndpoint, { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ - mode: "page", - requestId: `page-runtime-${Date.now()}`, - environment: runtimeContext.buildPageRuntimeEnvironment(), - state: runtimeContext.readPageRuntimeState(action, item), - action: runtimeAction, - }), - }); - if (!response.ok) { - throw new Error(await runtimeContext.readErrorMessage(response)); - } - return response.json(); -} - -function reconcilePageRuntimeResult(runtimeContext, runtimeResult, item) { - const result = runtimeContext.normalizePageRuntimeResult(runtimeResult); - if (!result) return false; - const previousExpanded = new Set(runtimeContext.expanded); - let shouldPatchTree = false; - if (Array.isArray(result.expandedIds)) { - shouldPatchTree = - runtimeContext.commitPageExpandedIds(result.expandedIds) || shouldPatchTree; - } - const currentFocusedNodeId = runtimeContext.getFocusedNodeId(); - if (result.focusedId && result.focusedId !== currentFocusedNodeId) { - runtimeContext.setFocusedNodeId(result.focusedId); - runtimeContext.postPageFocusChange(result.focusedId); - shouldPatchTree = true; - } - const itemNodeId = runtimeContext.normalizeText(item?.nodeId); - if ( - itemNodeId && - previousExpanded.has(itemNodeId) !== runtimeContext.expanded.has(itemNodeId) - ) { - runtimeContext.postPageExpandChange(itemNodeId, runtimeContext.expanded.has(itemNodeId)); - } - if (shouldPatchTree) { - runtimeContext.patchPageTreeAfterRuntimeState( - Array.isArray(result.expandedIds) ? [itemNodeId, ...result.expandedIds] : [itemNodeId], - result.focusedId || runtimeContext.getFocusedNodeId(), - ); - } - return shouldPatchTree; -} - function startTreeShellRuntime() { const stateElement = document.getElementById("tree-shell-state"); const appElement = document.getElementById("tree-shell-app"); diff --git a/rust/crates/mnote-web/src/routes/mod.rs b/rust/crates/mnote-web/src/routes/mod.rs index 8cff99d1..9d18d845 100644 --- a/rust/crates/mnote-web/src/routes/mod.rs +++ b/rust/crates/mnote-web/src/routes/mod.rs @@ -166,6 +166,10 @@ pub fn build_router(state: AppState) -> Router { "/api/mnote-browser-runtime/tree-shell-runtime.js", get(web_shell::tree_shell_runtime_asset), ) + .route( + "/api/mnote-browser-runtime/tree-shell-page-runtime.js", + get(web_shell::tree_shell_page_runtime_asset), + ) .route( "/api/mnote-browser-runtime/document-conflict-panel-runtime.js", get(web_shell::document_conflict_panel_runtime_asset), @@ -617,6 +621,7 @@ mod tests { "/api/mnote-browser-runtime/sidebar-tree-runtime.js", "/api/mnote-browser-runtime/tree-live-controller.js", "/api/mnote-browser-runtime/tree-shell-runtime.js", + "/api/mnote-browser-runtime/tree-shell-page-runtime.js", "/api/mnote-browser-runtime/document-conflict-panel-runtime.js", "/api/mnote-browser-runtime/document-pane-runtime.js", "/api/mnote-browser-runtime/document-mindmap-host-runtime.js", diff --git a/rust/crates/mnote-web/src/routes/web_shell.rs b/rust/crates/mnote-web/src/routes/web_shell.rs index 48edf165..fbfc751c 100644 --- a/rust/crates/mnote-web/src/routes/web_shell.rs +++ b/rust/crates/mnote-web/src/routes/web_shell.rs @@ -938,6 +938,20 @@ pub async fn tree_shell_runtime_asset() -> Response { .unwrap_or_else(|_| Response::new(Body::empty())) } +pub async fn tree_shell_page_runtime_asset() -> Response { + const JS: &str = include_str!("../../browser/tree-shell-page-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 document_conflict_panel_runtime_asset() -> Response { // include_str! 路径相对于当前源文件 (src/routes/web_shell.rs -> ../../browser/) const JS: &str = include_str!("../../browser/document-conflict-panel-runtime.js");