refactor: split tree shell page runtime

This commit is contained in:
lix-2026
2026-05-26 02:42:07 +08:00
parent c5e0396eba
commit d47a7d80ee
5 changed files with 80 additions and 55 deletions
@@ -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。
@@ -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;
}
@@ -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");
+5
View File
@@ -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",
@@ -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");