refactor: split tree shell state runtime
This commit is contained in:
+1
-1
@@ -204,7 +204,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。
|
- [x] D1. `tree-shell-state-runtime.js`:state hydration、serialization、patch dispatch。
|
||||||
- [x] 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。
|
- [ ] D3. `tree-shell-filetree-runtime.js`:filetree row normalization、resource meta、open target。
|
||||||
- [ ] D4. `tree-shell-picker-runtime.js`:picker search/focus/pick root。
|
- [ ] D4. `tree-shell-picker-runtime.js`:picker search/focus/pick root。
|
||||||
|
|||||||
@@ -10,6 +10,15 @@ import {
|
|||||||
createTreeShellActionButton,
|
createTreeShellActionButton,
|
||||||
createTreeShellKindBadge as createKindBadge,
|
createTreeShellKindBadge as createKindBadge,
|
||||||
} from "./tree-shell-icons-runtime.js";
|
} from "./tree-shell-icons-runtime.js";
|
||||||
|
import {
|
||||||
|
normalizeTreeShellNumber as normalizeNumber,
|
||||||
|
normalizeTreeShellStringArray as normalizeStringArray,
|
||||||
|
normalizeTreeShellText as normalizeText,
|
||||||
|
parseTreeShellState,
|
||||||
|
parseTreeShellStateFromHtml,
|
||||||
|
resolveTreeShellMode,
|
||||||
|
resolveTreeShellTargetOrigin,
|
||||||
|
} from "./tree-shell-state-runtime.js";
|
||||||
|
|
||||||
function buildFileTreeRuntimeEnvironment(runtimeContext) {
|
function buildFileTreeRuntimeEnvironment(runtimeContext) {
|
||||||
const fileTreeRowById = runtimeContext.fileTreeRowById || new Map();
|
const fileTreeRowById = runtimeContext.fileTreeRowById || new Map();
|
||||||
@@ -37,15 +46,7 @@ function startTreeShellRuntime() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parseState = () => {
|
const state = parseTreeShellState(stateElement);
|
||||||
try {
|
|
||||||
return JSON.parse(stateElement.textContent || "{}");
|
|
||||||
} catch {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const state = parseState();
|
|
||||||
const rendererInput =
|
const rendererInput =
|
||||||
state.rendererInput && typeof state.rendererInput === "object"
|
state.rendererInput && typeof state.rendererInput === "object"
|
||||||
? state.rendererInput
|
? state.rendererInput
|
||||||
@@ -77,12 +78,6 @@ function startTreeShellRuntime() {
|
|||||||
runtimeArtifact.runtimeApi && typeof runtimeArtifact.runtimeApi === "object"
|
runtimeArtifact.runtimeApi && typeof runtimeArtifact.runtimeApi === "object"
|
||||||
? runtimeArtifact.runtimeApi
|
? runtimeArtifact.runtimeApi
|
||||||
: {};
|
: {};
|
||||||
const normalizeStringArray = (value) =>
|
|
||||||
Array.isArray(value)
|
|
||||||
? value
|
|
||||||
.map((item) => (typeof item === "string" ? item.trim() : ""))
|
|
||||||
.filter(Boolean)
|
|
||||||
: [];
|
|
||||||
const hostOverride =
|
const hostOverride =
|
||||||
window.__MNOTE_TREE_SHELL_OVERRIDE__ &&
|
window.__MNOTE_TREE_SHELL_OVERRIDE__ &&
|
||||||
typeof window.__MNOTE_TREE_SHELL_OVERRIDE__ === "object"
|
typeof window.__MNOTE_TREE_SHELL_OVERRIDE__ === "object"
|
||||||
@@ -128,13 +123,7 @@ function startTreeShellRuntime() {
|
|||||||
: typeof state.activePickerItemKey === "string" && state.activePickerItemKey.trim()
|
: typeof state.activePickerItemKey === "string" && state.activePickerItemKey.trim()
|
||||||
? state.activePickerItemKey.trim()
|
? state.activePickerItemKey.trim()
|
||||||
: "";
|
: "";
|
||||||
const mode = (() => {
|
const mode = resolveTreeShellMode(state);
|
||||||
const rawMode =
|
|
||||||
typeof state.mode === "string" ? state.mode.trim() : "";
|
|
||||||
if (rawMode === "picker") return "picker";
|
|
||||||
if (rawMode === "filetree") return "filetree";
|
|
||||||
return "page";
|
|
||||||
})();
|
|
||||||
const allowRootPick = state.allowRootPick === true;
|
const allowRootPick = state.allowRootPick === true;
|
||||||
const excludedIds = new Set(
|
const excludedIds = new Set(
|
||||||
normalizeStringArray(rendererInput.excludedPickerIds).length > 0
|
normalizeStringArray(rendererInput.excludedPickerIds).length > 0
|
||||||
@@ -171,20 +160,7 @@ function startTreeShellRuntime() {
|
|||||||
const titleElement = document.getElementById("tree-shell-title");
|
const titleElement = document.getElementById("tree-shell-title");
|
||||||
const summaryElement = document.getElementById("tree-shell-summary");
|
const summaryElement = document.getElementById("tree-shell-summary");
|
||||||
const toolbarElement = document.getElementById("tree-shell-toolbar");
|
const toolbarElement = document.getElementById("tree-shell-toolbar");
|
||||||
const targetOrigin = (() => {
|
const targetOrigin = resolveTreeShellTargetOrigin(document);
|
||||||
try {
|
|
||||||
if (!document.referrer) return "*";
|
|
||||||
return new URL(document.referrer).origin || "*";
|
|
||||||
} catch {
|
|
||||||
return "*";
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
const normalizeText = (value, fallback = "") => {
|
|
||||||
if (typeof value !== "string") return fallback;
|
|
||||||
const trimmed = value.trim();
|
|
||||||
return trimmed || fallback;
|
|
||||||
};
|
|
||||||
const initialRenameRowId = (() => {
|
const initialRenameRowId = (() => {
|
||||||
try {
|
try {
|
||||||
return normalizeText(new URL(window.location.href).searchParams.get("renameRowId"));
|
return normalizeText(new URL(window.location.href).searchParams.get("renameRowId"));
|
||||||
@@ -202,10 +178,6 @@ function startTreeShellRuntime() {
|
|||||||
return normalized || null;
|
return normalized || null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const normalizeNumber = (value, fallback = Number.MAX_SAFE_INTEGER) => {
|
|
||||||
return Number.isFinite(value) ? Number(value) : fallback;
|
|
||||||
};
|
|
||||||
|
|
||||||
const normalizeRowKind = (value) => {
|
const normalizeRowKind = (value) => {
|
||||||
const normalized = normalizeText(value).toLowerCase();
|
const normalized = normalizeText(value).toLowerCase();
|
||||||
if (normalized === "index") return "index";
|
if (normalized === "index") return "index";
|
||||||
@@ -1544,17 +1516,6 @@ function startTreeShellRuntime() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseTreeShellStateFromHtml = (html) => {
|
|
||||||
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
||||||
const nextStateElement = doc.getElementById("tree-shell-state");
|
|
||||||
if (!nextStateElement) return null;
|
|
||||||
try {
|
|
||||||
return JSON.parse(nextStateElement.textContent || "{}");
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const applyTreeShellStateSnapshot = (nextState, options = {}) => {
|
const applyTreeShellStateSnapshot = (nextState, options = {}) => {
|
||||||
if (!nextState || typeof nextState !== "object") return false;
|
if (!nextState || typeof nextState !== "object") return false;
|
||||||
mediaAssets = Array.isArray(nextState.mediaAssets) ? nextState.mediaAssets : [];
|
mediaAssets = Array.isArray(nextState.mediaAssets) ? nextState.mediaAssets : [];
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
// MNote debug /tree shell state parsing and normalization helpers.
|
||||||
|
|
||||||
|
export function parseTreeShellState(stateElement) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(stateElement?.textContent || "{}");
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeTreeShellText(value, fallback = "") {
|
||||||
|
if (typeof value !== "string") return fallback;
|
||||||
|
const trimmed = value.trim();
|
||||||
|
return trimmed || fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeTreeShellStringArray(value) {
|
||||||
|
return Array.isArray(value)
|
||||||
|
? value
|
||||||
|
.map((item) => (typeof item === "string" ? item.trim() : ""))
|
||||||
|
.filter(Boolean)
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeTreeShellNumber(value, fallback = Number.MAX_SAFE_INTEGER) {
|
||||||
|
return Number.isFinite(value) ? Number(value) : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveTreeShellMode(state) {
|
||||||
|
const rawMode = typeof state?.mode === "string" ? state.mode.trim() : "";
|
||||||
|
if (rawMode === "picker") return "picker";
|
||||||
|
if (rawMode === "filetree") return "filetree";
|
||||||
|
return "page";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveTreeShellTargetOrigin(documentRef) {
|
||||||
|
try {
|
||||||
|
if (!documentRef?.referrer) return "*";
|
||||||
|
return new URL(documentRef.referrer).origin || "*";
|
||||||
|
} catch {
|
||||||
|
return "*";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseTreeShellStateFromHtml(html) {
|
||||||
|
const doc = new DOMParser().parseFromString(html, "text/html");
|
||||||
|
const nextStateElement = doc.getElementById("tree-shell-state");
|
||||||
|
if (!nextStateElement) return null;
|
||||||
|
try {
|
||||||
|
return JSON.parse(nextStateElement.textContent || "{}");
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -170,6 +170,10 @@ pub fn build_router(state: AppState) -> Router {
|
|||||||
"/api/mnote-browser-runtime/tree-shell-page-runtime.js",
|
"/api/mnote-browser-runtime/tree-shell-page-runtime.js",
|
||||||
get(web_shell::tree_shell_page_runtime_asset),
|
get(web_shell::tree_shell_page_runtime_asset),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/api/mnote-browser-runtime/tree-shell-state-runtime.js",
|
||||||
|
get(web_shell::tree_shell_state_runtime_asset),
|
||||||
|
)
|
||||||
.route(
|
.route(
|
||||||
"/api/mnote-browser-runtime/tree-shell-icons-runtime.js",
|
"/api/mnote-browser-runtime/tree-shell-icons-runtime.js",
|
||||||
get(web_shell::tree_shell_icons_runtime_asset),
|
get(web_shell::tree_shell_icons_runtime_asset),
|
||||||
@@ -626,6 +630,7 @@ mod tests {
|
|||||||
"/api/mnote-browser-runtime/tree-live-controller.js",
|
"/api/mnote-browser-runtime/tree-live-controller.js",
|
||||||
"/api/mnote-browser-runtime/tree-shell-runtime.js",
|
"/api/mnote-browser-runtime/tree-shell-runtime.js",
|
||||||
"/api/mnote-browser-runtime/tree-shell-page-runtime.js",
|
"/api/mnote-browser-runtime/tree-shell-page-runtime.js",
|
||||||
|
"/api/mnote-browser-runtime/tree-shell-state-runtime.js",
|
||||||
"/api/mnote-browser-runtime/tree-shell-icons-runtime.js",
|
"/api/mnote-browser-runtime/tree-shell-icons-runtime.js",
|
||||||
"/api/mnote-browser-runtime/document-conflict-panel-runtime.js",
|
"/api/mnote-browser-runtime/document-conflict-panel-runtime.js",
|
||||||
"/api/mnote-browser-runtime/document-pane-runtime.js",
|
"/api/mnote-browser-runtime/document-pane-runtime.js",
|
||||||
|
|||||||
@@ -952,6 +952,20 @@ pub async fn tree_shell_page_runtime_asset() -> Response {
|
|||||||
.unwrap_or_else(|_| Response::new(Body::empty()))
|
.unwrap_or_else(|_| Response::new(Body::empty()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn tree_shell_state_runtime_asset() -> Response {
|
||||||
|
const JS: &str = include_str!("../../browser/tree-shell-state-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 tree_shell_icons_runtime_asset() -> Response {
|
pub async fn tree_shell_icons_runtime_asset() -> Response {
|
||||||
const JS: &str = include_str!("../../browser/tree-shell-icons-runtime.js");
|
const JS: &str = include_str!("../../browser/tree-shell-icons-runtime.js");
|
||||||
Response::builder()
|
Response::builder()
|
||||||
|
|||||||
Reference in New Issue
Block a user