fix: 修复本地文件夹垃圾箱恢复聚焦

This commit is contained in:
lix-2026
2026-05-21 14:39:38 +08:00
parent b0fa1e53d8
commit 83ad32dd67
17 changed files with 680 additions and 39 deletions
+34 -5
View File
@@ -60,6 +60,7 @@ pub(crate) struct RootEntryQuery {
workspace_id: Option<String>,
source_kind: Option<String>,
root_uri: Option<String>,
restore_focus_row_id: Option<String>,
}
pub async fn gateway_health(State(state): State<AppState>) -> Response {
@@ -298,8 +299,16 @@ pub async fn root_entry(
);
let sidebar_tree_html =
render_local_sidebar_tree_html(root_uri, selected_active_page_id.as_deref())?;
let file_tree_html =
render_local_file_tree_html(root_uri, selected_active_page_id.as_deref())?;
let restore_focus_row_id = query
.restore_focus_row_id
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty());
let file_tree_html = render_local_file_tree_html(
root_uri,
selected_active_page_id.as_deref(),
restore_focus_row_id,
)?;
(
workspace_id,
workspace_projection,
@@ -353,7 +362,7 @@ pub async fn root_entry(
let sidebar_tree_html =
render_local_sidebar_tree_html(&root_uri, selected_active_page_id.as_deref())?;
let file_tree_html =
render_local_file_tree_html(&root_uri, selected_active_page_id.as_deref())?;
render_local_file_tree_html(&root_uri, selected_active_page_id.as_deref(), None)?;
(
workspace_id,
workspace_projection,
@@ -567,7 +576,7 @@ pub async fn trash_entry(
let workspace_id =
crate::routes::local_folder_source::local_workspace_id_from_root_uri(root_uri)?;
let sidebar_tree_html = render_local_sidebar_tree_html(root_uri, None).unwrap_or_default();
let file_tree_html = render_local_file_tree_html(root_uri, None).unwrap_or_default();
let file_tree_html = render_local_file_tree_html(root_uri, None, None).unwrap_or_default();
let workspace_projection = build_workspace_shell_projection(
&json!({
"active_workspace_id": workspace_id,
@@ -944,8 +953,15 @@ fn render_local_trash_workbench_html(
let original = escape_html(&entry.original_relative_path);
let trash_path = escape_html(&entry.trash_relative_path);
let kind = escape_html(&entry.resource_kind);
let filetree_row_id = if entry.resource_kind == "markdown"
|| entry.resource_kind == "markdown_bundle"
{
format!("doc:{}", entry.document_id)
} else {
format!("local:asset:{}", entry.original_relative_path)
};
let row = format!(
r#"<article class="mnote-trash-row" data-trash-row="local" data-trash-entry-id="{entry_id}" data-resource-kind="{kind}">
r#"<article class="mnote-trash-row" data-trash-row="local" data-trash-entry-id="{entry_id}" data-resource-kind="{kind}" data-filetree-row-id="{filetree_row_id}">
<div class="mnote-trash-row-main">
<span class="mnote-trash-kind">{kind}</span>
<span class="mnote-trash-title">{original}</span>
@@ -958,6 +974,7 @@ fn render_local_trash_workbench_html(
</article>"#,
entry_id = escape_html(entry_id),
kind = kind,
filetree_row_id = escape_html(&filetree_row_id),
original = original,
trash_path = trash_path,
deleted_at = escape_html(&deleted_at),
@@ -1062,6 +1079,16 @@ fn render_local_trash_workbench_html(
workspaceId: workspaceId,
documentId: entryId
}}).then(function() {{
if (action === 'local-restore') {{
var row = button.closest('[data-trash-row="local"]');
var filetreeRowId = row ? (row.getAttribute('data-filetree-row-id') || '') : '';
if (filetreeRowId && window.localStorage) {{
window.localStorage.setItem('mnote.pendingLocalFolderRestoreFiletreeRowId', filetreeRowId);
}}
if (filetreeRowId) {{
window.name = 'mnote.pendingLocalFolderRestoreFiletreeRowId=' + encodeURIComponent(filetreeRowId);
}}
}}
return refresh();
}}).then(function() {{
setStatus(action === 'local-restore' ? '已恢复项目' : '已彻底删除项目', false);
@@ -2223,6 +2250,8 @@ mod tests {
assert!(html.contains("Deleted"));
assert!(html.contains(r#"data-trash-action="local-restore""#));
assert!(html.contains(r#"data-trash-action="local-purge""#));
assert!(html.contains(r#"data-filetree-row-id="doc:local-md:Deleted~2FDeleted.md""#));
assert!(html.contains("mnote.pendingLocalFolderRestoreFiletreeRowId"));
assert!(html.contains(r#"data-trash-action="local-empty-documents""#));
assert!(html.contains(r#"data-trash-action="local-empty-resources" disabled"#));
@@ -58,7 +58,7 @@ pub async fn mindmap_object_shell(
let sidebar_tree_html =
render_local_sidebar_tree_html(root_uri, Some(&doc_id)).unwrap_or_default();
let file_tree_html =
render_local_file_tree_html(root_uri, Some(&doc_id)).unwrap_or_default();
render_local_file_tree_html(root_uri, Some(&doc_id), None).unwrap_or_default();
let workspace_projection = load_workspace_shell_projection(
state.config(),
&context,
@@ -125,7 +125,7 @@ pub async fn document_page_shell(
let root_uri = query.root_uri.as_deref().unwrap_or_default();
(
render_local_sidebar_tree_html(root_uri, Some(&document_id)).unwrap_or_default(),
render_local_file_tree_html(root_uri, Some(&document_id)).unwrap_or_default(),
render_local_file_tree_html(root_uri, Some(&document_id), None).unwrap_or_default(),
)
} else {
(
@@ -4446,9 +4446,10 @@ pub(crate) fn render_local_sidebar_tree_html(
pub(crate) fn render_local_file_tree_html(
root_uri: &str,
active_document_id: Option<&str>,
active_row_id: Option<&str>,
) -> Result<String, WebError> {
let snapshot = load_local_folder_file_tree_snapshot(root_uri)?;
let rows = collect_filetree_render_rows(&snapshot.projection, active_document_id, None);
let rows = collect_filetree_render_rows(&snapshot.projection, active_document_id, active_row_id);
Ok(render_initial_filetree_html(&FileTreeInitialRenderInput {
rows,
}))
@@ -127,6 +127,8 @@ const SIDEBAR_TREE_JS: &str = r##"
}
function currentFileTreeActiveRowId() {
var explicitRowId = new URL(window.location.href).searchParams.get('restoreFocusRowId') || '';
if (explicitRowId) return explicitRowId;
var mindmapMatch = window.location.pathname.match(/^\/mindmap\/([^\/]+)\/([^\/]+)/);
if (mindmapMatch) return 'asset:' + decodeURIComponent(mindmapMatch[2]);
var documentId = currentDocumentId();
@@ -1983,6 +1985,7 @@ const SIDEBAR_TREE_JS: &str = r##"
var renderedFile = filePayload ? renderFileProjection(filePayload.result || filePayload) : false;
if (!renderedPage && !renderedFile) return false;
syncSidebarFileTreeSelection();
schedulePendingLocalFolderRestoreFocus();
restoreSidebarTreeTab();
document.documentElement.setAttribute('data-mnote-local-folder-watch-applied', 'projection');
return true;
@@ -4159,6 +4162,86 @@ const SIDEBAR_TREE_JS: &str = r##"
return true;
}
function selectSidebarFileTreeRowById(rowId, options) {
var id = String(rowId || '').trim();
if (!id) return false;
var row = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(id) + '"]');
if (!(row instanceof HTMLElement)) return false;
selectSidebarFileTreeRow(row, { ctrlKey: false, metaKey: false, shiftKey: false });
document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-active="true"]').forEach(function(activeRow) {
if (activeRow instanceof HTMLElement) activeRow.setAttribute('data-active', 'false');
});
row.setAttribute('data-active', 'true');
if (options && options.scrollIntoView !== false) {
try {
row.scrollIntoView({ block: 'nearest' });
} catch (_) {
row.scrollIntoView();
}
}
return true;
}
function pendingLocalFolderRestoreRowId() {
if (window.__mnotePendingLocalFolderRestoreFiletreeRowId) {
return String(window.__mnotePendingLocalFolderRestoreFiletreeRowId || '').trim();
}
if (!window.localStorage) return '';
var rowId = '';
try {
rowId = String(window.localStorage.getItem('mnote.pendingLocalFolderRestoreFiletreeRowId') || '').trim();
} catch (_) {
rowId = '';
}
if (!rowId && typeof window.name === 'string' && window.name.indexOf('mnote.pendingLocalFolderRestoreFiletreeRowId=') === 0) {
rowId = decodeURIComponent(window.name.slice('mnote.pendingLocalFolderRestoreFiletreeRowId='.length));
}
if (rowId) window.__mnotePendingLocalFolderRestoreFiletreeRowId = rowId;
return rowId;
}
function clearPendingLocalFolderRestoreRowId() {
window.__mnotePendingLocalFolderRestoreFiletreeRowId = '';
try {
window.localStorage.removeItem('mnote.pendingLocalFolderRestoreFiletreeRowId');
} catch (_) {}
if (typeof window.name === 'string' && window.name.indexOf('mnote.pendingLocalFolderRestoreFiletreeRowId=') === 0) {
window.name = '';
}
}
function applyPendingLocalFolderRestoreFocusOnce() {
var rowId = pendingLocalFolderRestoreRowId();
if (!rowId) return false;
document.documentElement.setAttribute('data-mnote-local-folder-restore-pending-row-id', rowId);
if (!selectSidebarFileTreeRowById(rowId, { scrollIntoView: true })) {
document.documentElement.setAttribute('data-mnote-local-folder-restore-focus-status', 'row-missing');
return false;
}
document.documentElement.setAttribute('data-mnote-local-folder-restore-focused-row-id', rowId);
document.documentElement.setAttribute('data-mnote-local-folder-restore-focus-status', 'focused');
return true;
}
function schedulePendingLocalFolderRestoreFocus() {
if (!pendingLocalFolderRestoreRowId()) return false;
if (window.__mnotePendingLocalFolderRestoreFocusTimer) return false;
var attempts = 0;
var focused = false;
focused = applyPendingLocalFolderRestoreFocusOnce() || focused;
window.__mnotePendingLocalFolderRestoreFocusTimer = window.setInterval(function() {
attempts += 1;
focused = applyPendingLocalFolderRestoreFocusOnce() || focused;
if (attempts >= 20) {
if (focused) clearPendingLocalFolderRestoreRowId();
else document.documentElement.setAttribute('data-mnote-local-folder-restore-focus-status', 'timeout');
window.clearInterval(window.__mnotePendingLocalFolderRestoreFocusTimer);
window.__mnotePendingLocalFolderRestoreFocusTimer = 0;
}
}, 250);
return focused;
}
function selectedSidebarFileTreeRowIdsForDrag(row) {
var rowId = row instanceof HTMLElement ? row.getAttribute('data-row-id') || '' : '';
if (rowId && sidebarFileTreeSelection.selectedRowIds.has(rowId)) {
@@ -9273,6 +9356,7 @@ const SIDEBAR_TREE_JS: &str = r##"
sidebarFileTreeSelection.focusedRowId = rowId;
});
syncSidebarFileTreeSelection();
schedulePendingLocalFolderRestoreFocus();
window.addEventListener('mnote:primary-document-activated', function(event) {
var detail = event && event.detail ? event.detail : {};
selectSidebarFileTreeDocument(detail.documentId, { scrollIntoView: false });
@@ -9910,6 +9994,13 @@ mod tests {
assert!(SIDEBAR_TREE_JS.contains("wolai:local-assets-changed"));
}
#[test]
fn sidebar_tree_runtime_focuses_restored_local_folder_row() {
assert!(SIDEBAR_TREE_JS.contains("applyPendingLocalFolderRestoreFocusOnce"));
assert!(SIDEBAR_TREE_JS.contains("mnote.pendingLocalFolderRestoreFiletreeRowId"));
assert!(SIDEBAR_TREE_JS.contains("data-mnote-local-folder-restore-focused-row-id"));
}
#[test]
fn sidebar_runtime_routes_local_mindmap_and_office_assets() {
assert!(SIDEBAR_TREE_JS.contains("withLocalMindmapSourceParams"));