refactor: split sidebar filetree command runtime

This commit is contained in:
lix-2026
2026-05-26 02:20:46 +08:00
parent 7816ab7ae2
commit 7909b3f9be
6 changed files with 1780 additions and 1553 deletions
@@ -166,7 +166,7 @@ cargo test --manifest-path rust/Cargo.toml -p mnote-web --lib routes::web_shell
- [x] C1. `sidebar-workspace-runtime.js`workspace source switch、sidebar tabs、collapse/resize 状态。 - [x] C1. `sidebar-workspace-runtime.js`workspace source switch、sidebar tabs、collapse/resize 状态。
- [ ] C2. `sidebar-page-tree-runtime.js`page tree row selection、rename title patch、breadcrumb/title sync。 - [ ] C2. `sidebar-page-tree-runtime.js`page tree row selection、rename title patch、breadcrumb/title sync。
- [x] C3. `sidebar-filetree-open-runtime.js`local markdown / resource open target、active resource tab dispatch。 - [x] C3. `sidebar-filetree-open-runtime.js`local markdown / resource open target、active resource tab dispatch。
- [ ] C4. `sidebar-filetree-command-runtime.js`create/rename/delete/copy/move/trash/restore/purge command payload。 - [x] C4. `sidebar-filetree-command-runtime.js`create/rename/delete/copy/move/trash/restore/purge command payload。
- [ ] C5. `sidebar-filetree-upload-runtime.js`local upload target plan、drop/paste preflight、readonly guard。 - [ ] C5. `sidebar-filetree-upload-runtime.js`local upload target plan、drop/paste preflight、readonly guard。
- [x] C6. `sidebar-attachment-open-runtime.js`OnlyOffice/PDF/code/image open mode guard。 - [x] C6. `sidebar-attachment-open-runtime.js`OnlyOffice/PDF/code/image open mode guard。
- [x] C7. `sidebar-tree-live-apply-runtime.js`WS/SSE snapshot/delta/resync DOM apply。 - [x] C7. `sidebar-tree-live-apply-runtime.js`WS/SSE snapshot/delta/resync DOM apply。
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+5
View File
@@ -138,6 +138,10 @@ pub fn build_router(state: AppState) -> Router {
"/api/mnote-browser-runtime/sidebar-filetree-open-runtime.js", "/api/mnote-browser-runtime/sidebar-filetree-open-runtime.js",
get(web_shell::sidebar_filetree_open_runtime_asset), get(web_shell::sidebar_filetree_open_runtime_asset),
) )
.route(
"/api/mnote-browser-runtime/sidebar-filetree-command-runtime.js",
get(web_shell::sidebar_filetree_command_runtime_asset),
)
.route( .route(
"/api/mnote-browser-runtime/sidebar-attachment-open-runtime.js", "/api/mnote-browser-runtime/sidebar-attachment-open-runtime.js",
get(web_shell::sidebar_attachment_open_runtime_asset), get(web_shell::sidebar_attachment_open_runtime_asset),
@@ -598,6 +602,7 @@ mod tests {
"/api/mnote-browser-runtime/sidebar-workspace-runtime.js", "/api/mnote-browser-runtime/sidebar-workspace-runtime.js",
"/api/mnote-browser-runtime/sidebar-tree-live-apply-runtime.js", "/api/mnote-browser-runtime/sidebar-tree-live-apply-runtime.js",
"/api/mnote-browser-runtime/sidebar-filetree-open-runtime.js", "/api/mnote-browser-runtime/sidebar-filetree-open-runtime.js",
"/api/mnote-browser-runtime/sidebar-filetree-command-runtime.js",
"/api/mnote-browser-runtime/sidebar-attachment-open-runtime.js", "/api/mnote-browser-runtime/sidebar-attachment-open-runtime.js",
"/api/mnote-browser-runtime/sidebar-tree-runtime.js", "/api/mnote-browser-runtime/sidebar-tree-runtime.js",
"/api/mnote-browser-runtime/tree-live-controller.js", "/api/mnote-browser-runtime/tree-live-controller.js",
@@ -784,6 +784,20 @@ pub async fn sidebar_filetree_open_runtime_asset() -> Response {
.unwrap_or_else(|_| Response::new(Body::empty())) .unwrap_or_else(|_| Response::new(Body::empty()))
} }
pub async fn sidebar_filetree_command_runtime_asset() -> Response {
const JS: &str = include_str!("../../browser/sidebar-filetree-command-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 sidebar_attachment_open_runtime_asset() -> Response { pub async fn sidebar_attachment_open_runtime_asset() -> Response {
const JS: &str = include_str!("../../browser/sidebar-attachment-open-runtime.js"); const JS: &str = include_str!("../../browser/sidebar-attachment-open-runtime.js");
Response::builder() Response::builder()
+99 -72
View File
@@ -203,6 +203,8 @@ mod tests {
include_str!("../../../browser/sidebar-tree-live-apply-runtime.js"); include_str!("../../../browser/sidebar-tree-live-apply-runtime.js");
const SIDEBAR_FILETREE_OPEN_RUNTIME_JS: &str = const SIDEBAR_FILETREE_OPEN_RUNTIME_JS: &str =
include_str!("../../../browser/sidebar-filetree-open-runtime.js"); include_str!("../../../browser/sidebar-filetree-open-runtime.js");
const SIDEBAR_FILETREE_COMMAND_RUNTIME_JS: &str =
include_str!("../../../browser/sidebar-filetree-command-runtime.js");
const SIDEBAR_ATTACHMENT_OPEN_RUNTIME_JS: &str = const SIDEBAR_ATTACHMENT_OPEN_RUNTIME_JS: &str =
include_str!("../../../browser/sidebar-attachment-open-runtime.js"); include_str!("../../../browser/sidebar-attachment-open-runtime.js");
const SIDEBAR_SHELL_RUNTIME_JS: &str = const SIDEBAR_SHELL_RUNTIME_JS: &str =
@@ -313,7 +315,7 @@ mod tests {
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("tree.filetree.internal-drop")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("tree.filetree.internal-drop"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("tree.filetree.external-drop")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("tree.filetree.external-drop"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("beginFileTreeInlineRename")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("beginFileTreeInlineRename"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("tree-rename-input")); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("tree-rename-input"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("sidebarFileTreeClipboard")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("sidebarFileTreeClipboard"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("pasteSidebarFileTreeClipboard")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("pasteSidebarFileTreeClipboard"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("MNOTE_RECENT_LOCAL_ROOTS_KEY_PREFIX")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("MNOTE_RECENT_LOCAL_ROOTS_KEY_PREFIX"));
@@ -578,16 +580,18 @@ mod tests {
fn sidebar_filetree_runtime_helpers_are_externalized_with_inline_fallback() { fn sidebar_filetree_runtime_helpers_are_externalized_with_inline_fallback() {
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function fileTreeRuntimeFunction")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function fileTreeRuntimeFunction"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("window.__mnoteFileTreeRuntime")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("window.__mnoteFileTreeRuntime"));
assert!( assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
SIDEBAR_TREE_RUNTIME_JS.contains("fileTreeRuntimeFunction('fileTreeRowDocumentId')") .contains("fileTreeRuntimeFunction('fileTreeRowDocumentId')"));
); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("fileTreeRuntimeFunction('fileTreeRowAssetId')")); .contains("fileTreeRuntimeFunction('fileTreeRowAssetId')"));
assert!(SIDEBAR_TREE_RUNTIME_JS assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
.contains("fileTreeRuntimeFunction('fileTreeRowLocalRelativePath')")); .contains("fileTreeRuntimeFunction('fileTreeRowLocalRelativePath')"));
assert!(SIDEBAR_TREE_RUNTIME_JS assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
.contains("fileTreeRuntimeFunction('fileTreeAssetDownloadDetail')")); .contains("fileTreeRuntimeFunction('fileTreeAssetDownloadDetail')"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("fileTreeRuntimeFunction('fileTreeRowsByRowIds')")); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("fileTreeRuntimeFunction('fileTreeChildCount')")); .contains("fileTreeRuntimeFunction('fileTreeRowsByRowIds')"));
assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
.contains("fileTreeRuntimeFunction('fileTreeChildCount')"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("fileTreeRuntimeFunction('revealFileTreeRow')")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("fileTreeRuntimeFunction('revealFileTreeRow')"));
assert!( assert!(
SIDEBAR_TREE_RUNTIME_JS.contains("fileTreeRuntimeFunction('revealFileTreeAssetRow')") SIDEBAR_TREE_RUNTIME_JS.contains("fileTreeRuntimeFunction('revealFileTreeAssetRow')")
@@ -631,7 +635,8 @@ mod tests {
.contains("fileTreeRuntimeFunction('applyLocalFolderSidebarSnapshot')")); .contains("fileTreeRuntimeFunction('applyLocalFolderSidebarSnapshot')"));
assert!(SIDEBAR_TREE_LIVE_APPLY_RUNTIME_JS assert!(SIDEBAR_TREE_LIVE_APPLY_RUNTIME_JS
.contains("fileTreeRuntimeFunction('markLocalFolderWatchApplied')")); .contains("fileTreeRuntimeFunction('markLocalFolderWatchApplied')"));
let document_id_body = js_function_body(SIDEBAR_TREE_RUNTIME_JS, "fileTreeRowDocumentId"); let document_id_body =
js_function_body(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS, "fileTreeRowDocumentId");
assert!( assert!(
document_id_body.contains("data-document-id") document_id_body.contains("data-document-id")
&& document_id_body.contains("data-doc-id"), && document_id_body.contains("data-doc-id"),
@@ -759,24 +764,29 @@ mod tests {
fn sidebar_filetree_selection_runtime_helpers_are_externalized_with_inline_fallback() { fn sidebar_filetree_selection_runtime_helpers_are_externalized_with_inline_fallback() {
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function fileTreeSelectionRuntimeFunction")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function fileTreeSelectionRuntimeFunction"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("window.__mnoteFileTreeSelectionRuntime")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("window.__mnoteFileTreeSelectionRuntime"));
assert!(SIDEBAR_TREE_RUNTIME_JS assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
.contains("fileTreeSelectionRuntimeFunction('visibleFileTreeRows')")); .contains("fileTreeSelectionRuntimeFunction('visibleFileTreeRows')"));
assert!(SIDEBAR_TREE_RUNTIME_JS assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
.contains("fileTreeSelectionRuntimeFunction('syncSidebarFileTreeSelection')")); .contains("fileTreeSelectionRuntimeFunction('syncSidebarFileTreeSelection')"));
assert!(SIDEBAR_TREE_RUNTIME_JS assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
.contains("fileTreeSelectionRuntimeFunction('selectSidebarFileTreeRow')")); .contains("fileTreeSelectionRuntimeFunction('selectSidebarFileTreeRow')"));
assert!(SIDEBAR_TREE_RUNTIME_JS assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
.contains("fileTreeSelectionRuntimeFunction('selectedSidebarFileTreeRowIdsForDrag')")); .contains("fileTreeSelectionRuntimeFunction('selectedSidebarFileTreeRowIdsForDrag')"));
assert!(SIDEBAR_TREE_RUNTIME_JS assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
.contains("fileTreeSelectionRuntimeFunction('selectedSidebarFileTreeRows')")); .contains("fileTreeSelectionRuntimeFunction('selectedSidebarFileTreeRows')"));
let select_body = js_function_body(SIDEBAR_TREE_RUNTIME_JS, "selectSidebarFileTreeRow"); let select_body = js_function_body(
SIDEBAR_FILETREE_COMMAND_RUNTIME_JS,
"selectSidebarFileTreeRow",
);
assert!( assert!(
select_body.contains("shiftKey && sidebarFileTreeSelection.anchorRowId") select_body.contains("shiftKey && sidebarFileTreeSelection.anchorRowId")
&& select_body.contains("ctrlKey"), && select_body.contains("ctrlKey"),
"inline fallback 必须保留 shift range 与 ctrl/meta toggle" "inline fallback 必须保留 shift range 与 ctrl/meta toggle"
); );
let select_document_body = let select_document_body = js_function_body(
js_function_body(SIDEBAR_TREE_RUNTIME_JS, "selectSidebarFileTreeDocument"); SIDEBAR_FILETREE_COMMAND_RUNTIME_JS,
"selectSidebarFileTreeDocument",
);
assert!( assert!(
select_document_body select_document_body
.contains("selectSidebarFileTreeRow(row, { ctrlKey: false, metaKey: false, shiftKey: false })"), .contains("selectSidebarFileTreeRow(row, { ctrlKey: false, metaKey: false, shiftKey: false })"),
@@ -800,8 +810,10 @@ mod tests {
#[test] #[test]
fn sidebar_tree_runtime_focuses_restored_local_folder_row() { fn sidebar_tree_runtime_focuses_restored_local_folder_row() {
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("applyPendingLocalFolderRestoreFocusOnce")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("applyPendingLocalFolderRestoreFocusOnce"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("mnote.pendingLocalFolderRestoreFiletreeRowId")); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("data-mnote-local-folder-restore-focused-row-id")); .contains("mnote.pendingLocalFolderRestoreFiletreeRowId"));
assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
.contains("data-mnote-local-folder-restore-focused-row-id"));
} }
#[test] #[test]
@@ -885,29 +897,29 @@ mod tests {
#[test] #[test]
fn sidebar_tree_runtime_renders_context_menu_and_scoped_title_updates() { fn sidebar_tree_runtime_renders_context_menu_and_scoped_title_updates() {
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("openTreeContextMenu")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("openTreeContextMenu"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("mnote-tree-context-menu")); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("mnote-tree-context-menu"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("复制访问链接")); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("复制访问链接"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("删除到垃圾桶")); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("删除到垃圾桶"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function createFileTreeFolder")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function createFileTreeFolder"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("action: 'create_folder'")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("action: 'create_folder'"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function fileTreeMenuTargetParentId")); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("function fileTreeMenuTargetParentId"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("if (action === 'new-folder')")); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("if (action === 'new-folder')"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("fileTreeCopyPath(detail, trigger)")); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("fileTreeCopyPath(detail, trigger)"));
assert!(SIDEBAR_TREE_RUNTIME_JS assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
.contains("if (detail.contextKind === 'filetree' && action === 'download')")); .contains("if (detail.contextKind === 'filetree' && action === 'download')"));
assert!( assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
SIDEBAR_TREE_RUNTIME_JS.contains("downloadSelectedFileTreeAssetRows(detail, trigger)") .contains("downloadSelectedFileTreeAssetRows(detail, trigger)"));
); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function selectedSidebarFileTreeRowsForDownload")); .contains("function selectedSidebarFileTreeRowsForDownload"));
assert!(SIDEBAR_TREE_RUNTIME_JS assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
.contains("function selectedSidebarFileTreeAssetRowsForDownload")); .contains("function selectedSidebarFileTreeAssetRowsForDownload"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("data-local-relative-path")); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("data-local-relative-path"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("targetRelativePath")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("targetRelativePath"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains( assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains(
"recordFileTreeAction(downloads.length > 1 ? 'bulk-download' : 'download', primary)" "recordFileTreeAction(downloads.length > 1 ? 'bulk-download' : 'download', primary)"
)); ));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("data-mnote-filetree-download-count")); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("data-mnote-filetree-download-count"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function triggerBrowserDownload")); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("function triggerBrowserDownload"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("openEditorAttachmentMenu(editorAttachmentLink, editorAttachmentLink, { x: event.clientX, y: event.clientY });")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("openEditorAttachmentMenu(editorAttachmentLink, editorAttachmentLink, { x: event.clientX, y: event.clientY });"));
assert!(SIDEBAR_TREE_RUNTIME_JS assert!(SIDEBAR_TREE_RUNTIME_JS
.contains("var localFilePath = localFilePathFromAssetId(assetId);")); .contains("var localFilePath = localFilePathFromAssetId(assetId);"));
@@ -974,36 +986,39 @@ mod tests {
#[test] #[test]
fn sidebar_tree_delete_to_trash_dispatches_archive_not_purge() { fn sidebar_tree_delete_to_trash_dispatches_archive_not_purge() {
let delete_trash_start = SIDEBAR_TREE_RUNTIME_JS let delete_trash_start = SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
.find("if (action === 'delete-trash'") .find("if (action === 'delete-trash'")
.expect("delete-trash branch"); .expect("delete-trash branch");
let delete_trash_end = SIDEBAR_TREE_RUNTIME_JS[delete_trash_start..] let delete_trash_end = SIDEBAR_FILETREE_COMMAND_RUNTIME_JS[delete_trash_start..]
.find("function appendTreeContextMenuButton") .find("function appendTreeContextMenuButton")
.map(|offset| delete_trash_start + offset) .map(|offset| delete_trash_start + offset)
.expect("delete-trash branch end"); .expect("delete-trash branch end");
let delete_trash_branch = &SIDEBAR_TREE_RUNTIME_JS[delete_trash_start..delete_trash_end]; let delete_trash_branch =
&SIDEBAR_FILETREE_COMMAND_RUNTIME_JS[delete_trash_start..delete_trash_end];
assert!(delete_trash_branch.contains("action: 'archive'")); assert!(delete_trash_branch.contains("action: 'archive'"));
assert!(!delete_trash_branch.contains("action: 'purge'")); assert!(!delete_trash_branch.contains("action: 'purge'"));
} }
#[test] #[test]
fn sidebar_filetree_delete_keys_support_mixed_doc_and_asset_selection() { fn sidebar_filetree_delete_keys_support_mixed_doc_and_asset_selection() {
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function deleteSelectedSidebarFileTreeRows")); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
.contains("function deleteSelectedSidebarFileTreeRows"));
assert!( assert!(
SIDEBAR_TREE_RUNTIME_JS.contains("event.key === 'Delete' || event.key === 'Backspace'") SIDEBAR_TREE_RUNTIME_JS.contains("event.key === 'Delete' || event.key === 'Backspace'")
); );
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("selectedFileTreeRows.length > 1")); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("selectedFileTreeRows.length > 1"));
assert!(SIDEBAR_TREE_RUNTIME_JS assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
.contains("deleteSelectedSidebarFileTreeRows(trigger || document.body)")); .contains("deleteSelectedSidebarFileTreeRows(trigger || document.body)"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains( assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains(
"currentSourceKind() === 'local_folder' && fileTreeRowKind(row) === 'asset'" "currentSourceKind() === 'local_folder' && fileTreeRowKind(row) === 'asset'"
)); ));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("if (currentSourceKind() === 'local_folder')")); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("documentId: fileAssetIds[lf]")); .contains("if (currentSourceKind() === 'local_folder')"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("/api/media/batch")); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("documentId: fileAssetIds[lf]"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("/api/mindmap/")); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("/api/media/batch"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("/api/tables/")); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("/api/mindmap/"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("确认删除选中的 ")); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("/api/tables/"));
assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("确认删除选中的 "));
} }
#[test] #[test]
@@ -1011,23 +1026,31 @@ mod tests {
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function blockReadonlyFileTreeAction")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function blockReadonlyFileTreeAction"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("data-mnote-filetree-readonly-blocked")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("data-mnote-filetree-readonly-blocked"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("data-mnote-filetree-readonly-message")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("data-mnote-filetree-readonly-message"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("recordFileTreeActionStatus('blocked'")); assert!(
SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("recordFileTreeActionStatus('blocked'")
);
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("/api/tree/filetree/drop-preflight")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("/api/tree/filetree/drop-preflight"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("ensureFileTreeWritableTarget('paste'")); assert!(
SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("ensureFileTreeWritableTarget('paste'")
);
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("ensureFileTreeWritableTarget('drop'")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("ensureFileTreeWritableTarget('drop'"));
} }
#[test] #[test]
fn sidebar_filetree_command_context_helper_functions_exist() { fn sidebar_filetree_command_context_helper_functions_exist() {
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function buildSidebarFileTreeContext")); assert!(
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function evaluateSidebarFileTreeWhen")); SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("function buildSidebarFileTreeContext")
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("__mnoteFileTreeContextMenuRuntime")); );
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("'workspace.sourceKind'")); assert!(
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("'workspace.readonly'")); SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("function evaluateSidebarFileTreeWhen")
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("'tree.focusKind'")); );
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("'tree.selectionCount'")); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("__mnoteFileTreeContextMenuRuntime"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("'tree.selectionResourceKind'")); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("'workspace.sourceKind'"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("data-mnote-workspace-readonly")); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("'workspace.readonly'"));
assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("'tree.focusKind'"));
assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("'tree.selectionCount'"));
assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("'tree.selectionResourceKind'"));
assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("data-mnote-workspace-readonly"));
} }
#[test] #[test]
@@ -1045,11 +1068,11 @@ mod tests {
fn sidebar_filetree_context_menu_items_have_when_for_readonly() { fn sidebar_filetree_context_menu_items_have_when_for_readonly() {
// FileTree 分支的写操作必须带 when: '!workspace.readonly'。 // FileTree 分支的写操作必须带 when: '!workspace.readonly'。
assert!( assert!(
SIDEBAR_TREE_RUNTIME_JS.contains("when: '!workspace.readonly'"), SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains("when: '!workspace.readonly'"),
"上下文菜单写操作应携带 when: '!workspace.readonly'" "上下文菜单写操作应携带 when: '!workspace.readonly'"
); );
// Verify at least one delete-trash item has the when expression // Verify at least one delete-trash item has the when expression
let delete_trash_items = SIDEBAR_TREE_RUNTIME_JS let delete_trash_items = SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
.match_indices("'delete-trash'") .match_indices("'delete-trash'")
.count(); .count();
assert!(delete_trash_items > 0, "应至少有一个 delete-trash 菜单项"); assert!(delete_trash_items > 0, "应至少有一个 delete-trash 菜单项");
@@ -1066,14 +1089,14 @@ mod tests {
#[test] #[test]
fn sidebar_filetree_local_table_bulk_delete_uses_tree_command() { fn sidebar_filetree_local_table_bulk_delete_uses_tree_command() {
let table_loop_start = SIDEBAR_TREE_RUNTIME_JS let table_loop_start = SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
.find("for (var t = 0; t < plan.tableRows.length; t += 1)") .find("for (var t = 0; t < plan.tableRows.length; t += 1)")
.expect("table bulk delete loop"); .expect("table bulk delete loop");
let table_loop_end = SIDEBAR_TREE_RUNTIME_JS[table_loop_start..] let table_loop_end = SIDEBAR_FILETREE_COMMAND_RUNTIME_JS[table_loop_start..]
.find("sidebarFileTreeSelection.selectedRowIds") .find("sidebarFileTreeSelection.selectedRowIds")
.map(|offset| table_loop_start + offset) .map(|offset| table_loop_start + offset)
.expect("table bulk delete loop end"); .expect("table bulk delete loop end");
let table_loop = &SIDEBAR_TREE_RUNTIME_JS[table_loop_start..table_loop_end]; let table_loop = &SIDEBAR_FILETREE_COMMAND_RUNTIME_JS[table_loop_start..table_loop_end];
assert!(table_loop.contains("currentSourceKind() === 'local_folder'")); assert!(table_loop.contains("currentSourceKind() === 'local_folder'"));
assert!(table_loop.contains("dispatchTreeCommand(trigger || tableRow")); assert!(table_loop.contains("dispatchTreeCommand(trigger || tableRow"));
assert!(table_loop.contains("action: 'archive'")); assert!(table_loop.contains("action: 'archive'"));
@@ -1138,21 +1161,25 @@ mod tests {
assert!( assert!(
SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains("params.set('mode', input.mode || 'view');") SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains("params.set('mode', input.mode || 'view');")
); );
assert!(SIDEBAR_TREE_RUNTIME_JS.contains( assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS.contains(
"{ action: 'open-edit-mode', icon: 'edit_note', label: '使用编辑模式打开' }" "{ action: 'open-edit-mode', icon: 'edit_note', label: '使用编辑模式打开' }"
)); ));
assert!(SIDEBAR_TREE_RUNTIME_JS assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
.contains("{ action: 'open-edit-mode', icon: 'edit_note', label: '弹窗编辑' }")); .contains("{ action: 'open-edit-mode', icon: 'edit_note', label: '弹窗编辑' }"));
assert!(SIDEBAR_TREE_RUNTIME_JS assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
.contains("{ action: 'new-window-edit', icon: 'open_in_new', label: '新窗口编辑' }")); .contains("{ action: 'new-window-edit', icon: 'open_in_new', label: '新窗口编辑' }"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function withOfficeEditModeGuard(callback)")); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("data-mnote-last-office-edit-mode-requested")); .contains("function withOfficeEditModeGuard(callback)"));
assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
.contains("data-mnote-last-office-edit-mode-requested"));
assert!(SIDEBAR_ATTACHMENT_OPEN_RUNTIME_JS assert!(SIDEBAR_ATTACHMENT_OPEN_RUNTIME_JS
.contains("function openEditorAttachmentEditTab(detail)")); .contains("function openEditorAttachmentEditTab(detail)"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("void openEditorAttachmentEditTab(detail);")); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
.contains("void openEditorAttachmentEditTab(detail);"));
assert!(SIDEBAR_ATTACHMENT_OPEN_RUNTIME_JS assert!(SIDEBAR_ATTACHMENT_OPEN_RUNTIME_JS
.contains("openLocalOfficeFileInActiveTab(detail, 'edit')")); .contains("openLocalOfficeFileInActiveTab(detail, 'edit')"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("openEditorAttachmentNewWindow(detail, 'edit')")); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS
.contains("openEditorAttachmentNewWindow(detail, 'edit')"));
assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains("forceEditMode ? 'edit' : 'view'")); assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains("forceEditMode ? 'edit' : 'view'"));
assert!(SIDEBAR_ATTACHMENT_OPEN_RUNTIME_JS assert!(SIDEBAR_ATTACHMENT_OPEN_RUNTIME_JS
.contains("url.searchParams.set('mode', requestedMode);")); .contains("url.searchParams.set('mode', requestedMode);"));