fix local folder resource lifecycle and workspace switch

This commit is contained in:
lix-2026
2026-05-24 02:23:48 +08:00
parent 97953101a2
commit 6d9d8ce9cd
9 changed files with 374 additions and 65 deletions
+26 -13
View File
@@ -763,7 +763,7 @@ const SIDEBAR_TREE_JS: &str = r##"
function rememberCloudWorkspaceId(workspaceId) {
var normalized = String(workspaceId || '').trim();
if (!normalized || normalized === 'local-folder' || normalized === 'default') return;
if (!normalized || normalized === 'local-folder' || normalized === 'default' || normalized.indexOf('local-ws:') === 0) return;
try {
if (window.localStorage) window.localStorage.setItem(MNOTE_LAST_CLOUD_WORKSPACE_KEY, normalized);
} catch (_) {}
@@ -773,11 +773,11 @@ const SIDEBAR_TREE_JS: &str = r##"
if (currentSourceKind() === 'local_folder') return '';
var params = new URLSearchParams(window.location.search);
var fromUrl = (params.get('workspaceId') || '').trim();
if (fromUrl && fromUrl !== 'default') return fromUrl;
if (fromUrl && fromUrl !== 'default' && fromUrl.indexOf('local-ws:') !== 0) return fromUrl;
var fromDom = document.querySelector('[data-workspace-id]');
if (fromDom instanceof HTMLElement) {
var value = (fromDom.getAttribute('data-workspace-id') || '').trim();
if (value && value !== 'local-folder' && value !== 'default') return value;
if (value && value !== 'local-folder' && value !== 'default' && value.indexOf('local-ws:') !== 0) return value;
}
return '';
}
@@ -789,15 +789,15 @@ const SIDEBAR_TREE_JS: &str = r##"
function readLastCloudWorkspaceId() {
try {
var stored = window.localStorage ? window.localStorage.getItem(MNOTE_LAST_CLOUD_WORKSPACE_KEY) : '';
if (stored && stored.trim() && stored.trim() !== 'default') return stored.trim();
if (stored && stored.trim() && stored.trim() !== 'default' && stored.trim().indexOf('local-ws:') !== 0) return stored.trim();
} catch (_) {}
var params = new URLSearchParams(window.location.search);
var fromUrl = (params.get('workspaceId') || '').trim();
if (fromUrl && currentSourceKind() !== 'local_folder') return fromUrl;
if (fromUrl && currentSourceKind() !== 'local_folder' && fromUrl.indexOf('local-ws:') !== 0) return fromUrl;
var fromDom = document.querySelector('[data-workspace-id]');
if (fromDom instanceof HTMLElement) {
var value = (fromDom.getAttribute('data-workspace-id') || '').trim();
if (value && value !== 'local-folder') return value;
if (value && value !== 'local-folder' && value.indexOf('local-ws:') !== 0) return value;
}
return '';
}
@@ -925,9 +925,8 @@ const SIDEBAR_TREE_JS: &str = r##"
function switchToCloudWorkspace() {
var targetUrl = new URL('/', window.location.origin);
var workspaceId = readLastCloudWorkspaceId();
if (workspaceId) targetUrl.searchParams.set('workspaceId', workspaceId);
targetUrl.searchParams.set('sourceKind', 'convex_workspace');
targetUrl.searchParams.set('treeView', activeSidebarTreeMode() || 'filetree');
targetUrl.searchParams.set('mnoteHome', '1');
window.location.href = targetUrl.toString();
}
@@ -1311,6 +1310,7 @@ const SIDEBAR_TREE_JS: &str = r##"
if ((params.get('rootUri') || '').trim()) return false;
if ((params.get('workspaceId') || '').trim()) return false;
if ((params.get('pageId') || '').trim()) return false;
if ((params.get('mnoteHome') || '').trim() === '1') return false;
var recent = readRecentLocalRoots();
if (!recent.length) return false;
openLocalFolderRoot(recent[0]);
@@ -1346,7 +1346,7 @@ const SIDEBAR_TREE_JS: &str = r##"
cloudButton.className = 'mnote-workspace-source-menu__item';
cloudButton.setAttribute('data-testid', 'mnote-switch-cloud-workspace');
cloudButton.setAttribute('role', 'menuitem');
cloudButton.textContent = '';
cloudButton.textContent = '';
cloudButton.addEventListener('click', function(event) {
event.preventDefault();
closeWorkspaceSourceMenu();
@@ -10512,7 +10512,7 @@ pub fn PageLayout(
data-mnote-action="open-workspace-source-menu"
aria-haspopup="menu"
aria-expanded="false"
title="切换空间或本地文件夹"
title="切换我的空间或本地文件夹"
>
<span class="sidebar-workspace-name">{ws_name.clone()}</span>
<span class="wolai-sidebar-chevron" aria-hidden="true">""</span>
@@ -10810,11 +10810,24 @@ mod tests {
fn sidebar_workspace_source_switch_remembers_real_cloud_workspace_before_local_folder() {
assert!(
!SIDEBAR_TREE_JS.contains("rememberCloudWorkspaceId(resolveWorkspaceId(document.body))"),
"进入本地文件夹前不能用 document.body 推导云空间 workspaceId;它会在无 workspaceId URL 时回退成 default"
"进入本地文件夹前不能用 document.body 推导 workspaceId;它会在无 workspaceId URL 时回退成 default"
);
assert!(
SIDEBAR_TREE_JS.contains("rememberCurrentCloudWorkspaceId()"),
"进入本地文件夹前应从 URL 或 DOM 子树读取真实云空间 workspaceId"
"进入本地文件夹前应从 URL 或 DOM 子树读取真实 workspaceId"
);
assert!(
SIDEBAR_TREE_JS.contains("normalized.indexOf('local-ws:') === 0"),
"本地文件夹 workspaceId 不能写入 lastCloudWorkspaceId"
);
assert!(
SIDEBAR_TREE_JS.contains("stored.trim().indexOf('local-ws:') !== 0"),
"切回工作区时不能复用 local-ws:* 作为旧云 workspace"
);
assert!(
!SIDEBAR_TREE_JS
.contains("targetUrl.searchParams.set('sourceKind', 'convex_workspace')"),
"默认来源菜单不能跳到已退出 dev 主链的 convex_workspace"
);
}
@@ -1187,6 +1187,13 @@ function __wbg_get_imports() {
const ret = arg0.target;
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
},
__wbg_textContent_1f28330a124ec047: function(arg0, arg1) {
const ret = arg1.textContent;
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len1 = WASM_VECTOR_LEN;
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
},
__wbg_then_792e0c862b060889: function(arg0, arg1, arg2) {
const ret = arg0.then(arg1, arg2);
return ret;
@@ -1195,6 +1202,10 @@ function __wbg_get_imports() {
const ret = arg0.then(arg1);
return ret;
},
__wbg_toString_306ed0b9f320c1ca: function(arg0) {
const ret = arg0.toString();
return ret;
},
__wbg_top_158f7c4dd1427771: function(arg0) {
const ret = arg0.top;
return ret;
@@ -5261,6 +5261,34 @@ fn hovered_block_from_selection() -> Option<HoveredBlockState> {
block_state_from_index(index)
}
fn selected_local_attachment_block_index() -> Option<usize> {
let selection = window().and_then(|win| win.get_selection().ok().flatten())?;
let selected_text = String::from(selection.to_string()).trim().to_string();
if selected_text.is_empty() {
return None;
}
let root = editor_root_element()?;
let links = root.get_elements_by_tag_name("a");
for index in 0..links.length() {
let Some(link) = links.item(index) else {
continue;
};
let href = link.get_attribute("href").unwrap_or_default();
if !href.contains("/api/local-folder/files/open") {
continue;
}
let text = link.text_content().unwrap_or_default();
if text.trim() != selected_text {
continue;
}
let block = direct_block_from_element(link, &root)?;
return top_level_block_index(&root, &block);
}
None
}
fn block_state_from_index(index: usize) -> Option<HoveredBlockState> {
let root = editor_root_element()?;
let stage = editor_stage_element()?;
@@ -8708,7 +8736,9 @@ fn App(mount_options: MountOptions) -> impl IntoView {
let editor = editor;
let document_id = document_id;
let workspace_id = workspace_id;
let dirty_count = dirty_count;
let title = title;
let editor_focused = editor_focused;
let set_dirty_count = set_dirty_count;
let set_html_output = set_html_output;
let set_document_json = set_document_json;
@@ -8729,6 +8759,9 @@ fn App(mount_options: MountOptions) -> impl IntoView {
let set_color_menu_open = set_color_menu_open;
let more_menu_open = more_menu_open;
let set_more_menu_open = set_more_menu_open;
let revision = revision;
let conflict_detection_key = conflict_detection_key;
let read_only = read_only;
let set_locked_toolbar_anchor = set_locked_toolbar_anchor;
let set_text_selection_active = set_text_selection_active;
let set_floating_toolbar_anchor = set_floating_toolbar_anchor;
@@ -8752,6 +8785,59 @@ fn App(mount_options: MountOptions) -> impl IntoView {
return;
}
if !event.ctrl_key()
&& !event.meta_key()
&& !event.alt_key()
&& matches!(event.key().as_str(), "Backspace" | "Delete")
{
if let Some(block_index) = selected_local_attachment_block_index() {
event.prevent_default();
match editor.get_html() {
Ok(current_html) => {
match delete_top_level_block_html(&current_html, block_index) {
Ok(next_html) => {
apply_html_update(
editor,
&runtime_persisted_identity(document_id, workspace_id),
next_html,
document_id,
workspace_id,
dirty_count,
set_dirty_count,
set_html_output,
set_document_json,
set_json_output,
title,
hovered_block,
editor_focused,
slash_open,
turn_into_open,
color_menu_open,
more_menu_open,
revision,
conflict_detection_key,
read_only,
set_command_feedback,
"已删除附件引用",
);
let _ = set_hovered_block.try_set(None);
let _ = set_block_menu_open.try_set(false);
let _ = set_block_menu_anchor.try_set(None);
}
Err(err) => {
let _ = set_command_feedback.try_set(err);
}
}
}
Err(err) => {
let _ =
set_command_feedback.try_set(format!("读取当前 HTML 失败:{err}"));
}
}
return;
}
}
if (event.ctrl_key() || event.meta_key())
&& event.shift_key()
&& event.key().eq_ignore_ascii_case("u")