diff --git a/design/10-review/process/16-mnote-web-runtime-module-maintainability-checklist-v1.md b/design/10-review/process/16-mnote-web-runtime-module-maintainability-checklist-v1.md
index 885b47e9..f84765f4 100644
--- a/design/10-review/process/16-mnote-web-runtime-module-maintainability-checklist-v1.md
+++ b/design/10-review/process/16-mnote-web-runtime-module-maintainability-checklist-v1.md
@@ -163,7 +163,7 @@ cargo test --manifest-path rust/Cargo.toml -p mnote-web --lib routes::web_shell
拆分项:
-- [ ] 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。
- [ ] 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。
diff --git a/rust/crates/mnote-web/browser/sidebar-tree-runtime.js b/rust/crates/mnote-web/browser/sidebar-tree-runtime.js
index 82dea3d2..258160e5 100644
--- a/rust/crates/mnote-web/browser/sidebar-tree-runtime.js
+++ b/rust/crates/mnote-web/browser/sidebar-tree-runtime.js
@@ -1,3 +1,5 @@
+import { createSidebarWorkspaceRuntime } from './sidebar-workspace-runtime.js';
+
(function(){
if (window.__mnoteSidebarTreeRuntimeStarted) return;
window.__mnoteSidebarTreeRuntimeStarted = true;
@@ -620,979 +622,34 @@
}
}
- function normalizeSidebarTreeMode(value) {
- var mode = String(value || '').trim();
- return mode === 'filetree' ? 'filetree' : 'page';
- }
-
- function readStoredSidebarTreeMode() {
- var runtimeFn = sidebarShellRuntimeFunction('readStoredSidebarTreeMode');
- if (runtimeFn) return runtimeFn();
- var params = new URLSearchParams(window.location.search);
- var fromUrl = normalizeSidebarTreeMode(params.get('treeView') || params.get('sidebarTree'));
- if (params.has('treeView') || params.has('sidebarTree')) return fromUrl;
- try {
- var stored = window.sessionStorage ? window.sessionStorage.getItem(MNOTE_SIDEBAR_TREE_MODE_KEY) : '';
- return normalizeSidebarTreeMode(stored);
- } catch (_) {
- return 'page';
- }
- }
-
- function persistSidebarTreeMode(mode) {
- var runtimeFn = sidebarShellRuntimeFunction('persistSidebarTreeMode');
- if (runtimeFn) return runtimeFn(mode);
- var normalized = normalizeSidebarTreeMode(mode);
- document.documentElement.setAttribute('data-mnote-sidebar-tree-mode', normalized);
- try {
- if (window.sessionStorage) window.sessionStorage.setItem(MNOTE_SIDEBAR_TREE_MODE_KEY, normalized);
- } catch (_) {}
- return normalized;
- }
-
- function activeSidebarTreeMode() {
- var runtimeFn = sidebarShellRuntimeFunction('activeSidebarTreeMode');
- if (runtimeFn) return runtimeFn();
- var active = document.querySelector('[data-mnote-sidebar-tree-tab][aria-selected="true"]');
- if (active instanceof HTMLElement) {
- return normalizeSidebarTreeMode(active.getAttribute('data-mnote-sidebar-tree-tab'));
- }
- return readStoredSidebarTreeMode();
- }
-
- function resolveWorkspaceId(trigger) {
- var direct = (trigger.getAttribute('data-workspace-id') || '').trim();
- if (direct) return direct;
- var root = trigger.closest('[data-workspace-id]');
- if (root) {
- var value = (root.getAttribute('data-workspace-id') || '').trim();
- if (value) return value;
- }
- var documentId = currentDocumentId();
- if (documentId) {
- var shell = document.querySelector('.document-shell[data-document-id="' + cssEscape(documentId) + '"][data-workspace-id]');
- if (shell instanceof HTMLElement) {
- var shellWorkspaceId = (shell.getAttribute('data-workspace-id') || '').trim();
- if (shellWorkspaceId) return shellWorkspaceId;
- }
- }
- var activePane = document.querySelector('[data-pane-visible="true"][data-pane-workspace-id]');
- if (activePane instanceof HTMLElement) {
- var paneWorkspaceId = (activePane.getAttribute('data-pane-workspace-id') || '').trim();
- if (paneWorkspaceId) return paneWorkspaceId;
- }
- var anyDocumentShell = document.querySelector('.document-shell[data-workspace-id]');
- if (anyDocumentShell instanceof HTMLElement) {
- var documentWorkspaceId = (anyDocumentShell.getAttribute('data-workspace-id') || '').trim();
- if (documentWorkspaceId) return documentWorkspaceId;
- }
- var fromDom = document.querySelector('[data-workspace-id]');
- if (fromDom instanceof HTMLElement) {
- var domWorkspaceId = (fromDom.getAttribute('data-workspace-id') || '').trim();
- if (domWorkspaceId) return domWorkspaceId;
- }
- return (new URLSearchParams(window.location.search).get('workspaceId') || 'default').trim() || 'default';
- }
-
- function currentWorkspaceId() {
- return resolveWorkspaceId(document.body);
- }
-
- function currentSourceKind() {
- var params = new URLSearchParams(window.location.search);
- var fromUrl = (params.get('sourceKind') || '').trim();
- if (fromUrl) return fromUrl;
- var fromBody = document.body instanceof HTMLElement ? (document.body.getAttribute('data-mnote-source-kind') || '').trim() : '';
- if (fromBody) return fromBody;
- if (
- window.location.pathname === '/' &&
- !(params.get('workspaceId') || '').trim() &&
- !(params.get('rootUri') || '').trim() &&
- !(params.get('pageId') || '').trim()
- ) {
- return 'local_folder';
- }
- return 'convex_workspace';
- }
-
- function currentRootUri() {
- var params = new URLSearchParams(window.location.search);
- var fromUrl = (params.get('rootUri') || '').trim();
- if (fromUrl) return fromUrl;
- return document.body instanceof HTMLElement ? (document.body.getAttribute('data-mnote-root-uri') || '').trim() : '';
- }
-
- function rememberCloudWorkspaceId(workspaceId) {
- var normalized = String(workspaceId || '').trim();
- 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 (_) {}
- }
-
- function readCurrentCloudWorkspaceIdFromPage() {
- if (currentSourceKind() === 'local_folder') return '';
- var params = new URLSearchParams(window.location.search);
- var fromUrl = (params.get('workspaceId') || '').trim();
- 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' && value.indexOf('local-ws:') !== 0) return value;
- }
- return '';
- }
-
- function rememberCurrentCloudWorkspaceId() {
- rememberCloudWorkspaceId(readCurrentCloudWorkspaceIdFromPage());
- }
-
- function readLastCloudWorkspaceId() {
- try {
- var stored = window.localStorage ? window.localStorage.getItem(MNOTE_LAST_CLOUD_WORKSPACE_KEY) : '';
- 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' && 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.indexOf('local-ws:') !== 0) return value;
- }
- return '';
- }
-
- function copyWorkspaceSourceParams(targetUrl) {
- var params = new URLSearchParams(window.location.search);
- ['sourceKind', 'rootUri', 'secondaryDocumentId', 'secondarySourceKind', 'secondaryRootUri'].forEach(function(name) {
- var value = (params.get(name) || '').trim();
- if (value) targetUrl.searchParams.set(name, value);
- });
- var sourceKind = currentSourceKind();
- var rootUri = currentRootUri();
- if (sourceKind && !targetUrl.searchParams.get('sourceKind')) targetUrl.searchParams.set('sourceKind', sourceKind);
- if (rootUri && !targetUrl.searchParams.get('rootUri')) targetUrl.searchParams.set('rootUri', rootUri);
- }
-
- function currentWorkspaceSourcePayload() {
- var params = new URLSearchParams(window.location.search);
- var payload = {};
- ['sourceKind', 'rootUri'].forEach(function(name) {
- var value = (params.get(name) || '').trim();
- if (value) payload[name] = value;
- });
- var sourceKind = currentSourceKind();
- var rootUri = currentRootUri();
- if (sourceKind && !payload.sourceKind) payload.sourceKind = sourceKind;
- if (rootUri && !payload.rootUri) payload.rootUri = rootUri;
- return payload;
- }
-
- function currentActorStorageId() {
- var fromBody = document.body instanceof HTMLElement ? (document.body.getAttribute('data-mnote-actor-id') || '').trim() : '';
- if (fromBody && fromBody !== 'anonymous') return fromBody;
- return '';
- }
-
- function recentLocalRootsStorageKey() {
- var actorId = currentActorStorageId();
- return actorId ? MNOTE_RECENT_LOCAL_ROOTS_KEY_PREFIX + encodeURIComponent(actorId) : '';
- }
-
- function readRecentLocalRoots() {
- try {
- var storageKey = recentLocalRootsStorageKey();
- if (!storageKey) return [];
- var raw = window.localStorage ? window.localStorage.getItem(storageKey) : '';
- var parsed = raw ? JSON.parse(raw) : [];
- return Array.isArray(parsed) ? parsed.filter(function(value) {
- return typeof value === 'string' && value.trim();
- }).slice(0, 10) : [];
- } catch (_) {
- return [];
- }
- }
-
- function rememberLocalRoot(rootUri) {
- try {
- if (!window.localStorage) return;
- var storageKey = recentLocalRootsStorageKey();
- if (!storageKey) return;
- var roots = readRecentLocalRoots().filter(function(value) { return value !== rootUri; });
- roots.unshift(rootUri);
- window.localStorage.setItem(storageKey, JSON.stringify(roots.slice(0, 10)));
- } catch (_) {}
- }
-
- function pathToFileRootUri(value) {
- var trimmed = String(value || '').trim();
- if (!trimmed) return '';
- if (/^file:\/\//i.test(trimmed)) return trimmed;
- if (trimmed.charAt(0) !== '/') return '';
- return 'file://' + trimmed.split('/').map(function(part, index) {
- return index === 0 ? '' : encodeURIComponent(part);
- }).join('/');
- }
-
- function fileRootUriToPathInput(rootUri) {
- var value = String(rootUri || '').replace(/^file:\/\//, '');
- try {
- return decodeURIComponent(value);
- } catch (_) {
- return value;
- }
- }
-
- function recentLocalRootLabel(rootUri) {
- var label = fileRootUriToPathInput(rootUri);
- return label || '本地文件夹';
- }
-
- function normalizeGrantedLocalFolderRootUri(grant) {
- var rootUri = String(grant && grant.rootUri || '').trim();
- if (rootUri && /^file:\/\//i.test(rootUri)) return rootUri;
- var rootPath = String(grant && grant.rootPath || '').trim();
- if (rootPath) return pathToFileRootUri(rootPath);
- if (rootUri) return pathToFileRootUri(rootUri);
- return '';
- }
-
- function isDefaultWorkspaceAutoGrant(grant) {
- var source = String(grant && grant.source || '').trim();
- var workspaceId = String(grant && grant.workspaceId || '').trim();
- var rootUri = String(grant && grant.rootUri || '').trim();
- var permission = String(grant && grant.permission || '').trim();
- var createdBy = String(grant && (grant.createdBy || grant.ownerUserId) || '').trim();
- var targetUser = String(grant && (grant.userId || grant.targetUserId) || '').trim();
- return source === 'auto'
- && workspaceId
- && permission === 'write'
- && createdBy === targetUser
- && /^local:\/\/users\/.+\/workspaces\/my-space$/.test(rootUri);
- }
-
- function openLocalFolderRoot(rootUri) {
- if (currentSourceKind() !== 'local_folder') {
- rememberCurrentCloudWorkspaceId();
- }
- rememberLocalRoot(rootUri);
- var targetUrl = new URL('/', window.location.origin);
- targetUrl.searchParams.set('treeView', 'filetree');
- targetUrl.searchParams.set('sourceKind', 'local_folder');
- targetUrl.searchParams.set('rootUri', rootUri);
- window.location.href = targetUrl.toString();
- }
-
- function switchToCloudWorkspace() {
- var targetUrl = new URL('/', window.location.origin);
- targetUrl.searchParams.set('treeView', activeSidebarTreeMode() || 'filetree');
- targetUrl.searchParams.set('mnoteHome', '1');
- window.location.href = targetUrl.toString();
- }
-
- function closeTrashModal() {
- var modal = document.querySelector('[data-testid="mnote-trash-modal"]');
- if (!modal) return;
- var workbench = modal.querySelector('[data-testid="mnote-trash-workbench"]');
- if (workbench && workbench.__mnoteTrashEventSource && typeof workbench.__mnoteTrashEventSource.close === 'function') {
- workbench.__mnoteTrashEventSource.close();
- }
- var fileScrollTop = Number(modal.getAttribute('data-file-scroll-top') || '0');
- var fileRoot = document.getElementById('sidebar-file-tree-root');
- if (fileRoot instanceof HTMLElement && Number.isFinite(fileScrollTop)) fileRoot.scrollTop = fileScrollTop;
- if (modal.parentElement) modal.parentElement.removeChild(modal);
- document.documentElement.removeAttribute('data-mnote-trash-modal-open');
- }
-
- function executeTrashWorkbenchScripts(parsed) {
- parsed.querySelectorAll('script').forEach(function(script) {
- var nextScript = document.createElement('script');
- Array.from(script.attributes || []).forEach(function(attr) {
- nextScript.setAttribute(attr.name, attr.value);
- });
- nextScript.textContent = script.textContent || '';
- document.body.appendChild(nextScript);
- if (nextScript.parentElement) nextScript.parentElement.removeChild(nextScript);
- });
- }
-
- function renderLocalFolderTrashPlaceholder(content, rootUri) {
- content.innerHTML = [
- '',
- '',
- '当前弹窗已保持在本地文件夹上下文:' + escapeHtml(rootUri || '未选择本地目录') + '
',
- ''
- ].join('');
- }
-
- function openTrashModal(trigger) {
- var existing = document.querySelector('[data-testid="mnote-trash-modal"]');
- if (existing) return;
- var fileRoot = document.getElementById('sidebar-file-tree-root');
- var activeRow = document.querySelector('#sidebar-file-tree-root .tree-row[data-selected="true"], #sidebar-tree-root .tree-row[data-active="true"]');
- var overlay = document.createElement('div');
- overlay.className = 'mnote-trash-modal';
- overlay.setAttribute('data-testid', 'mnote-trash-modal');
- overlay.setAttribute('role', 'dialog');
- overlay.setAttribute('aria-modal', 'true');
- overlay.setAttribute('aria-label', '垃圾箱');
- overlay.setAttribute('data-file-scroll-top', fileRoot instanceof HTMLElement ? String(fileRoot.scrollTop) : '0');
- if (activeRow instanceof HTMLElement) {
- overlay.setAttribute('data-active-row-id', activeRow.getAttribute('data-row-id') || activeRow.getAttribute('data-node-id') || '');
- }
- overlay.innerHTML = '
' +
- '';
- document.body.appendChild(overlay);
- document.documentElement.setAttribute('data-mnote-trash-modal-open', 'true');
- var content = overlay.querySelector('[data-testid="mnote-trash-modal-content"]');
- var sourceKind = currentSourceKind();
- var workspaceId = resolveWorkspaceId(trigger || document.body);
- var url = new URL('/trash', window.location.origin);
- if (sourceKind === 'local_folder') {
- var rootUri = currentRootUri();
- if (!rootUri) {
- renderLocalFolderTrashPlaceholder(content, '');
- return;
- }
- url.searchParams.set('sourceKind', 'local_folder');
- url.searchParams.set('rootUri', rootUri);
- } else if (workspaceId) {
- url.searchParams.set('workspaceId', workspaceId);
- }
- fetch(url.toString(), { headers: { 'x-mnote-trash-modal': '1' } }).then(function(response) {
- return response.text().then(function(html) {
- if (!response.ok) throw new Error('trash_modal_load_failed_' + response.status);
- var parsed = new DOMParser().parseFromString(html, 'text/html');
- var workbench = parsed.querySelector('[data-testid="mnote-trash-workbench"]');
- if (!workbench) throw new Error('trash_modal_missing_workbench');
- content.innerHTML = '';
- content.appendChild(workbench);
- executeTrashWorkbenchScripts(parsed);
- });
- }).catch(function(error) {
- content.innerHTML = '' + escapeHtml(error && error.message ? error.message : String(error)) + '
';
- });
- }
-
- function closeLocalFolderDialog() {
- var existing = document.querySelector('[data-testid="mnote-local-folder-dialog"]');
- if (existing && existing.parentElement) existing.parentElement.removeChild(existing);
- }
-
- function readPathFromDirectoryFiles(files) {
- var first = files && files.length ? files[0] : null;
- if (!first) return '';
- var rawPath = typeof first.path === 'string' ? first.path : '';
- var relative = typeof first.webkitRelativePath === 'string' ? first.webkitRelativePath : '';
- if (rawPath && relative) {
- var suffix = relative.split('/').filter(Boolean).join('/');
- if (suffix && rawPath.endsWith(suffix)) {
- return rawPath.slice(0, rawPath.length - suffix.length).replace(/[\/\\]$/, '');
- }
- }
- if (rawPath) return rawPath;
- return '';
- }
-
- function requestBrowserFolderChoice(statusNode) {
- return new Promise(function(resolve) {
- var input = document.createElement('input');
- input.type = 'file';
- input.multiple = true;
- input.setAttribute('webkitdirectory', '');
- input.setAttribute('directory', '');
- input.style.position = 'fixed';
- input.style.left = '-9999px';
- input.addEventListener('change', function() {
- var selectedPath = readPathFromDirectoryFiles(input.files || []);
- if (input.parentElement) input.parentElement.removeChild(input);
- if (!selectedPath && statusNode instanceof HTMLElement) {
- statusNode.textContent = '当前浏览器没有暴露本机绝对路径,请在下方输入路径。';
- }
- resolve(selectedPath);
- }, { once: true });
- document.body.appendChild(input);
- input.click();
- });
- }
-
- async function requestNativeFolderChoice(statusNode) {
- var desktopPicker = window.__mnoteDesktop && typeof window.__mnoteDesktop.selectLocalFolder === 'function'
- ? window.__mnoteDesktop.selectLocalFolder
- : null;
- if (desktopPicker) {
- var selected = await desktopPicker();
- return typeof selected === 'string' ? selected : '';
- }
- if (typeof window.showDirectoryPicker === 'function') {
- var handle = await window.showDirectoryPicker({ mode: 'read' });
- var handlePath = handle && (handle.path || handle.mnotePath || handle.nativePath);
- if (typeof handlePath === 'string' && handlePath.trim()) return handlePath;
- if (statusNode instanceof HTMLElement) {
- statusNode.textContent = '已选择“' + (handle && handle.name ? handle.name : '文件夹') + '”,但浏览器没有暴露本机绝对路径,请在下方确认路径。';
- }
- return '';
- }
- return requestBrowserFolderChoice(statusNode);
- }
-
- function openLocalFolderDialog(initialMessage) {
- closeLocalFolderDialog();
- var recent = readRecentLocalRoots();
- var dialog = document.createElement('div');
- dialog.className = 'mnote-local-folder-dialog';
- dialog.setAttribute('data-testid', 'mnote-local-folder-dialog');
- dialog.setAttribute('role', 'dialog');
- dialog.setAttribute('aria-modal', 'true');
- dialog.style.position = 'fixed';
- dialog.style.inset = '0';
- dialog.style.zIndex = '2147483646';
- dialog.style.background = 'rgba(15, 23, 42, 0.28)';
- dialog.style.display = 'flex';
- dialog.style.alignItems = 'center';
- dialog.style.justifyContent = 'center';
- var card = document.createElement('div');
- card.className = 'mnote-local-folder-dialog__card';
- card.style.width = 'min(640px, calc(100vw - 32px))';
- card.style.background = '#fff';
- card.style.border = '1px solid rgba(27, 28, 28, 0.12)';
- card.style.borderRadius = '8px';
- card.style.padding = '16px';
- card.style.boxShadow = '0 18px 48px rgba(15, 23, 42, 0.22)';
- card.style.display = 'grid';
- card.style.gap = '12px';
- var title = document.createElement('h2');
- title.textContent = '打开本地文件夹';
- title.style.margin = '0';
- title.style.fontSize = '18px';
- title.style.lineHeight = '1.4';
- var status = document.createElement('p');
- status.className = 'mnote-local-folder-dialog__status';
- status.setAttribute('data-testid', 'mnote-local-folder-status');
- status.textContent = initialMessage || '选择一个本机文件夹,或输入绝对路径。';
- status.style.margin = '0';
- status.style.color = '#4b5563';
- status.style.fontSize = '13px';
- var input = document.createElement('input');
- input.type = 'text';
- input.autocomplete = 'off';
- input.spellcheck = false;
- input.placeholder = '/mnt/Data1T/mnote/design/04-tree-domain/done';
- input.setAttribute('data-testid', 'mnote-local-folder-path-input');
- input.value = recent.length ? fileRootUriToPathInput(recent[0]) : '';
- input.style.width = '100%';
- input.style.boxSizing = 'border-box';
- input.style.border = '1px solid rgba(27, 28, 28, 0.18)';
- input.style.borderRadius = '6px';
- input.style.padding = '10px 12px';
- input.style.fontSize = '14px';
- var actions = document.createElement('div');
- actions.className = 'mnote-local-folder-dialog__actions';
- actions.style.display = 'flex';
- actions.style.gap = '8px';
- actions.style.justifyContent = 'flex-end';
- var choose = document.createElement('button');
- choose.type = 'button';
- choose.textContent = '选择文件夹';
- choose.setAttribute('data-testid', 'mnote-local-folder-native-picker');
- var cancel = document.createElement('button');
- cancel.type = 'button';
- cancel.textContent = '取消';
- var confirm = document.createElement('button');
- confirm.type = 'button';
- confirm.textContent = '打开';
- confirm.setAttribute('data-testid', 'mnote-local-folder-open-confirm');
- [choose, cancel, confirm].forEach(function(button) {
- button.style.border = '1px solid rgba(27, 28, 28, 0.14)';
- button.style.borderRadius = '6px';
- button.style.padding = '8px 12px';
- button.style.background = '#fff';
- button.style.cursor = 'pointer';
- button.style.fontSize = '14px';
- });
- function submit() {
- var rootUri = pathToFileRootUri(input.value);
- if (!rootUri) {
- status.textContent = '请输入以 / 开头的绝对路径,或 file:// URI。';
- input.focus();
- return;
- }
- closeLocalFolderDialog();
- openLocalFolderRoot(rootUri);
- }
- choose.addEventListener('click', function(event) {
- event.preventDefault();
- requestNativeFolderChoice(status).then(function(selectedPath) {
- if (selectedPath) {
- input.value = selectedPath;
- submit();
- }
- }).catch(function(error) {
- status.textContent = error && error.name === 'AbortError'
- ? '已取消选择。'
- : '无法打开系统文件夹选择器,请输入路径。';
- });
- });
- cancel.addEventListener('click', function(event) {
- event.preventDefault();
- closeLocalFolderDialog();
- });
- confirm.addEventListener('click', function(event) {
- event.preventDefault();
- submit();
- });
- input.addEventListener('keydown', function(event) {
- if (event.key === 'Enter') {
- event.preventDefault();
- submit();
- }
- });
- actions.appendChild(choose);
- actions.appendChild(cancel);
- actions.appendChild(confirm);
- card.appendChild(title);
- card.appendChild(status);
- card.appendChild(input);
- var authorizedSection = document.createElement('div');
- authorizedSection.className = 'mnote-local-folder-dialog__authorized';
- authorizedSection.setAttribute('data-testid', 'mnote-local-folder-authorized-roots');
- authorizedSection.hidden = true;
- card.appendChild(authorizedSection);
- fetch('/api/user/access-policy', {
- method: 'GET',
- headers: { 'accept': 'application/json' },
- credentials: 'include'
- }).then(function(response) {
- return response.ok ? response.json() : null;
- }).then(function(payload) {
- var grants = payload && Array.isArray(payload.grants) ? payload.grants : [];
- var activeGrants = grants.filter(function(grant) {
- return grant && grant.active !== false && grant.status !== 'revoked';
- });
- if (!activeGrants.length) return;
- authorizedSection.hidden = false;
- var authorizedTitle = document.createElement('div');
- authorizedTitle.style.fontSize = '12px';
- authorizedTitle.style.color = '#6b7280';
- authorizedTitle.textContent = '已授权文件夹';
- var authorizedList = document.createElement('div');
- authorizedList.className = 'mnote-local-folder-dialog__recent';
- activeGrants.slice(0, 8).forEach(function(grant) {
- if (isDefaultWorkspaceAutoGrant(grant)) return;
- var rootUri = normalizeGrantedLocalFolderRootUri(grant);
- if (!rootUri) return;
- var button = document.createElement('button');
- button.type = 'button';
- button.setAttribute('data-testid', 'mnote-local-folder-authorized-root');
- button.textContent = fileRootUriToPathInput(rootUri);
- button.addEventListener('click', function(event) {
- event.preventDefault();
- closeLocalFolderDialog();
- openLocalFolderRoot(rootUri);
- });
- authorizedList.appendChild(button);
- });
- if (!authorizedList.childElementCount) return;
- authorizedSection.appendChild(authorizedTitle);
- authorizedSection.appendChild(authorizedList);
- }).catch(function() {});
- if (recent.length) {
- var recentTitle = document.createElement('div');
- recentTitle.style.fontSize = '12px';
- recentTitle.style.color = '#6b7280';
- recentTitle.textContent = '最近使用';
- card.appendChild(recentTitle);
- var recentList = document.createElement('div');
- recentList.className = 'mnote-local-folder-dialog__recent';
- recent.slice(0, 5).forEach(function(rootUri) {
- var button = document.createElement('button');
- button.type = 'button';
- button.textContent = recentLocalRootLabel(rootUri);
- button.addEventListener('click', function(event) {
- event.preventDefault();
- closeLocalFolderDialog();
- openLocalFolderRoot(rootUri);
- });
- recentList.appendChild(button);
- });
- card.appendChild(recentList);
- }
- card.appendChild(actions);
- dialog.appendChild(card);
- dialog.addEventListener('click', function(event) {
- if (event.target === dialog) closeLocalFolderDialog();
- });
- document.body.appendChild(dialog);
- input.focus();
- input.select();
- }
-
- function requestOpenLocalFolder() {
- openLocalFolderDialog('');
- }
-
- async function createDefaultLocalWorkspace(trigger) {
- setCommandPending(trigger, true);
- try {
- var response = await fetch('/api/local-folder/workspaces/default', {
- method: 'POST',
- headers: { 'content-type': 'application/json' },
- body: '{}'
- });
- var payload = await response.json().catch(function() { return null; });
- if (!response.ok) {
- throw new Error((payload && payload.message) || ('local_workspace_create_failed_' + response.status));
- }
- var workspace = payload && payload.workspace || {};
- var rootUri = String(workspace.rootUri || '').trim();
- if (!rootUri) throw new Error('local_workspace_create_missing_root_uri');
- openLocalFolderRoot(rootUri);
- } catch (error) {
- var status = document.querySelector('[data-testid="mnote-local-folder-status"]');
- if (status instanceof HTMLElement) {
- status.textContent = error && error.message ? error.message : String(error);
- } else {
- openLocalFolderDialog(error && error.message ? error.message : String(error));
- }
- } finally {
- setCommandPending(trigger, false);
- }
- }
-
- function autoOpenRecentLocalRootOnHome() {
- if (window.location.pathname !== '/') return false;
- var params = new URLSearchParams(window.location.search);
- if ((params.get('sourceKind') || '').trim()) return false;
- 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]);
- return true;
- }
-
- function closeWorkspaceSourceMenu() {
- var existing = document.querySelector('[data-testid="mnote-workspace-source-menu"]');
- if (existing && existing.parentElement) existing.parentElement.removeChild(existing);
- document.querySelectorAll('[data-testid="mnote-workspace-source-trigger"]').forEach(function(trigger) {
- if (trigger instanceof HTMLElement) trigger.setAttribute('aria-expanded', 'false');
- });
- }
-
- function workspaceSourceLabel(rootUri) {
- var label = String(rootUri || '').replace(/^file:\/\//, '');
- try {
- label = decodeURIComponent(label);
- } catch (_) {}
- return label || '本地文件夹';
- }
-
- function openWorkspaceSourceMenu(trigger) {
- closeWorkspaceSourceMenu();
- if (!(trigger instanceof HTMLElement)) return;
- trigger.setAttribute('aria-expanded', 'true');
- var menu = document.createElement('div');
- menu.className = 'mnote-workspace-source-menu';
- menu.setAttribute('data-testid', 'mnote-workspace-source-menu');
- menu.setAttribute('role', 'menu');
- var cloudButton = document.createElement('button');
- cloudButton.type = 'button';
- cloudButton.className = 'mnote-workspace-source-menu__item';
- cloudButton.setAttribute('data-testid', 'mnote-switch-cloud-workspace');
- cloudButton.setAttribute('role', 'menuitem');
- cloudButton.textContent = '我的空间';
- cloudButton.addEventListener('click', function(event) {
- event.preventDefault();
- closeWorkspaceSourceMenu();
- switchToCloudWorkspace();
- });
- menu.appendChild(cloudButton);
- var recent = readRecentLocalRoots();
- if (recent.length) {
- var label = document.createElement('div');
- label.className = 'mnote-workspace-source-menu__label';
- label.textContent = '最近本地文件夹';
- menu.appendChild(label);
- recent.slice(0, 8).forEach(function(rootUri) {
- var button = document.createElement('button');
- button.type = 'button';
- button.className = 'mnote-workspace-source-menu__item';
- button.setAttribute('data-testid', 'mnote-recent-local-root');
- button.setAttribute('data-root-uri', rootUri);
- button.setAttribute('role', 'menuitem');
- button.textContent = workspaceSourceLabel(rootUri);
- button.addEventListener('click', function(event) {
- event.preventDefault();
- closeWorkspaceSourceMenu();
- openLocalFolderRoot(rootUri);
- });
- menu.appendChild(button);
- });
- }
- var openOther = document.createElement('button');
- openOther.type = 'button';
- openOther.className = 'mnote-workspace-source-menu__item mnote-workspace-source-menu__item--primary';
- openOther.setAttribute('data-testid', 'mnote-open-other-local-folder');
- openOther.setAttribute('role', 'menuitem');
- openOther.textContent = '打开其他本地文件夹';
- openOther.addEventListener('click', function(event) {
- event.preventDefault();
- closeWorkspaceSourceMenu();
- requestOpenLocalFolder();
- });
- menu.appendChild(openOther);
- trigger.closest('[data-testid="wolai-workspace-identity"]')?.appendChild(menu);
- }
-
- function closeAccountMenu() {
- var existing = document.querySelector('[data-testid="mnote-account-menu"]');
- if (existing && existing.parentElement) existing.parentElement.removeChild(existing);
- document.querySelectorAll('[data-testid="mnote-account-menu-trigger"]').forEach(function(trigger) {
- if (trigger instanceof HTMLElement) trigger.setAttribute('aria-expanded', 'false');
- });
- }
-
- function closeProfileDialog() {
- var existing = document.querySelector('[data-testid="mnote-profile-dialog"]');
- if (existing && existing.parentElement) existing.parentElement.removeChild(existing);
- }
-
- function closeAdminAccessPolicyDialog() {
- var existing = document.querySelector('[data-testid="mnote-admin-access-policy-modal"]');
- if (existing && existing.parentElement) existing.parentElement.removeChild(existing);
- document.documentElement.removeAttribute('data-mnote-admin-access-policy-modal-open');
- }
-
- function accountMenuFallbackSession() {
- var body = document.body instanceof HTMLElement ? document.body : null;
- var actorId = body ? String(body.getAttribute('data-mnote-actor-id') || '').trim() : '';
- return {
- ok: true,
- name: actorId || '当前用户',
- email: '',
- userId: actorId || 'anonymous',
- actorType: actorId && actorId !== 'anonymous' ? 'user' : 'anonymous',
- authMode: 'browser'
- };
- }
-
- function renderAccountInfo(menu, session) {
- var nameNode = menu.querySelector('[data-account-info="name"]');
- var emailNode = menu.querySelector('[data-account-info="email"]');
- var idNode = menu.querySelector('[data-account-info="user-id"]');
- var typeNode = menu.querySelector('[data-account-info="actor-type"]');
- if (nameNode) nameNode.textContent = session.name || session.userId || '当前用户';
- if (emailNode) emailNode.textContent = session.email || '未提供邮箱';
- if (idNode) {
- idNode.textContent = session.userId || 'anonymous';
- idNode.setAttribute('title', session.userId || 'anonymous');
- }
- if (typeNode) typeNode.textContent = session.actorType || session.authMode || 'unknown';
- var copyButton = menu.querySelector('[data-account-copy-user-id]');
- if (copyButton instanceof HTMLElement) {
- copyButton.dataset.copyValue = session.userId || 'anonymous';
- copyButton.setAttribute('title', '复制用户 ID');
- }
- }
-
- function sessionIsAdmin(session) {
- return String(session && session.actorType || '').trim() === 'admin';
- }
-
- async function loadAccountInfo(menu) {
- try {
- var response = await fetch('/api/auth/session', {
- method: 'GET',
- headers: { 'accept': 'application/json' },
- credentials: 'include'
- });
- var payload = await response.json().catch(function() { return null; });
- if (!response.ok || !payload) throw new Error('session_failed_' + response.status);
- renderAccountInfo(menu, payload);
- } catch (_) {
- renderAccountInfo(menu, accountMenuFallbackSession());
- }
- }
-
- async function fetchAccountSession() {
- try {
- var response = await fetch('/api/auth/session', {
- method: 'GET',
- headers: { 'accept': 'application/json' },
- credentials: 'include'
- });
- var payload = await response.json().catch(function() { return null; });
- if (!response.ok || !payload) throw new Error('session_failed_' + response.status);
- return payload;
- } catch (_) {
- return accountMenuFallbackSession();
- }
- }
-
- function openProfileDialog(session) {
- closeProfileDialog();
- closeAccountMenu();
- var dialog = document.createElement('div');
- dialog.className = 'mnote-profile-dialog';
- dialog.setAttribute('data-testid', 'mnote-profile-dialog');
- dialog.setAttribute('role', 'dialog');
- dialog.setAttribute('aria-modal', 'true');
- dialog.setAttribute('aria-labelledby', 'mnote-profile-dialog-title');
- var userId = escapeHtml(session.userId || 'anonymous');
- dialog.innerHTML =
- '' +
- '' +
- '' +
- '' +
- '
' + escapeHtml(String(session.name || session.email || session.userId || '用').slice(0, 1).toUpperCase()) + '
' +
- '
' + escapeHtml(session.name || session.userId || '当前用户') + '' + escapeHtml(session.email || '未提供邮箱') + '
' +
- '
' +
- '' +
- '- 用户名
- ' + escapeHtml(session.name || '未设置') + '
' +
- '- 邮箱
- ' + escapeHtml(session.email || '未提供邮箱') + '
' +
- '- 用户 ID
' + userId + ' ' +
- '- 身份
- ' + escapeHtml(session.actorType || session.authMode || 'unknown') + '
' +
- '
' +
- '';
- dialog.querySelectorAll('[data-profile-dialog-close]').forEach(function(button) {
- button.addEventListener('click', function(event) {
- event.preventDefault();
- closeProfileDialog();
- });
- });
- var copyButton = dialog.querySelector('[data-profile-copy-user-id]');
- if (copyButton) {
- copyButton.addEventListener('click', function(event) {
- event.preventDefault();
- void copyTreeContextValue(copyButton.getAttribute('data-copy-value') || '', 'profile-user-id');
- });
- }
- document.body.appendChild(dialog);
- var closeButton = dialog.querySelector('.mnote-profile-dialog__close');
- if (closeButton instanceof HTMLElement) closeButton.focus();
- }
-
- function openAdminAccessPolicyDialog(trigger) {
- closeAdminAccessPolicyDialog();
- closeAccountMenu();
- if (!(trigger instanceof HTMLElement)) return;
- var role = trigger.getAttribute('data-access-policy-role') === 'admin' ? 'admin' : 'user';
- var template = document.querySelector('[data-testid="mnote-admin-access-policy-template-' + role + '"]');
- var dialog = document.createElement('div');
- dialog.className = 'mnote-admin-policy-modal';
- dialog.setAttribute('data-testid', 'mnote-admin-access-policy-modal');
- dialog.setAttribute('role', 'dialog');
- dialog.setAttribute('aria-modal', 'true');
- dialog.setAttribute('aria-labelledby', 'mnote-admin-policy-dialog-title');
- dialog.innerHTML =
- '' +
- '';
- var content = dialog.querySelector('[data-testid="mnote-admin-access-policy-modal-content"]');
- if (content) {
- content.innerHTML = template ? template.innerHTML : '授权管理面板加载失败
';
- }
- if (typeof window.MNOTEInitAccessPolicyPanel === 'function') {
- window.MNOTEInitAccessPolicyPanel(dialog);
- }
- dialog.querySelectorAll('[data-admin-access-policy-close]').forEach(function(button) {
- button.addEventListener('click', function(event) {
- event.preventDefault();
- closeAdminAccessPolicyDialog();
- });
- });
- document.body.appendChild(dialog);
- document.documentElement.setAttribute('data-mnote-admin-access-policy-modal-open', 'true');
- var closeButton = dialog.querySelector('.mnote-admin-policy-modal__close');
- if (closeButton instanceof HTMLElement) closeButton.focus();
- }
-
- async function signOutAccount(trigger) {
- setCommandPending(trigger, true);
- try {
- var response = await fetch('/api/auth', {
- method: 'POST',
- headers: { 'content-type': 'application/json' },
- credentials: 'include',
- body: JSON.stringify({ action: 'auth:signOut', args: {} })
- });
- var payload = await response.json().catch(function() { return {}; });
- payload = payload || {};
- if (!response.ok || payload.error) {
- throw new Error(payload.error || '退出登录失败');
- }
- window.location.assign('/auth');
- } catch (error) {
- var menu = document.querySelector('[data-testid="mnote-account-menu"]');
- var errorNode = menu ? menu.querySelector('[data-account-error]') : null;
- if (errorNode instanceof HTMLElement) {
- errorNode.textContent = error && error.message ? error.message : '退出登录失败';
- errorNode.hidden = false;
- }
- setCommandPending(trigger, false);
- }
- }
-
- function openAccountMenu(trigger) {
- closeAccountMenu();
- if (!(trigger instanceof HTMLElement)) return;
- trigger.setAttribute('aria-expanded', 'true');
- var menu = document.createElement('div');
- menu.className = 'mnote-workspace-source-menu mnote-account-menu';
- menu.setAttribute('data-testid', 'mnote-account-menu');
- menu.setAttribute('role', 'menu');
- menu.innerHTML =
- '' +
- '' +
- '' +
- '';
- var sessionPromise = fetchAccountSession();
- var profileButton = menu.querySelector('[data-testid="mnote-account-profile"]');
- if (profileButton) {
- profileButton.addEventListener('click', function(event) {
- event.preventDefault();
- sessionPromise.then(openProfileDialog);
- });
- }
- var accessPolicyLink = menu.querySelector('[data-testid="mnote-account-access-policy"]');
- sessionPromise.then(function(session) {
- if (!(accessPolicyLink instanceof HTMLElement)) return;
- accessPolicyLink.hidden = false;
- accessPolicyLink.setAttribute('data-access-policy-role', sessionIsAdmin(session) ? 'admin' : 'user');
- });
- if (accessPolicyLink) {
- accessPolicyLink.addEventListener('click', function(event) {
- event.preventDefault();
- openAdminAccessPolicyDialog(accessPolicyLink);
- });
- }
- var signOutButton = menu.querySelector('[data-testid="mnote-account-sign-out"]');
- if (signOutButton) {
- signOutButton.addEventListener('click', function(event) {
- event.preventDefault();
- void signOutAccount(signOutButton);
- });
- }
- var host = trigger.closest('[data-testid="wolai-sidebar-quick-actions"]') || trigger.parentElement;
- if (host) host.appendChild(menu);
- }
-
- autoOpenRecentLocalRootOnHome();
+ const sidebarWorkspace = createSidebarWorkspaceRuntime({
+ cssEscape,
+ currentDocumentId,
+ escapeHtml,
+ parseJsonScript,
+ setCommandPending: (...args) => setCommandPending(...args),
+ sidebarShellRuntimeFunction,
+ });
+ const normalizeSidebarTreeMode = (...args) => sidebarWorkspace.normalizeSidebarTreeMode(...args);
+ const readStoredSidebarTreeMode = (...args) => sidebarWorkspace.readStoredSidebarTreeMode(...args);
+ const persistSidebarTreeMode = (...args) => sidebarWorkspace.persistSidebarTreeMode(...args);
+ const activeSidebarTreeMode = (...args) => sidebarWorkspace.activeSidebarTreeMode(...args);
+ const resolveWorkspaceId = (...args) => sidebarWorkspace.resolveWorkspaceId(...args);
+ const currentWorkspaceId = (...args) => sidebarWorkspace.currentWorkspaceId(...args);
+ const currentSourceKind = (...args) => sidebarWorkspace.currentSourceKind(...args);
+ const currentRootUri = (...args) => sidebarWorkspace.currentRootUri(...args);
+ const copyWorkspaceSourceParams = (...args) => sidebarWorkspace.copyWorkspaceSourceParams(...args);
+ const currentWorkspaceSourcePayload = (...args) => sidebarWorkspace.currentWorkspaceSourcePayload(...args);
+ const openTrashModal = (...args) => sidebarWorkspace.openTrashModal(...args);
+ const closeTrashModal = (...args) => sidebarWorkspace.closeTrashModal(...args);
+ const closeLocalFolderDialog = (...args) => sidebarWorkspace.closeLocalFolderDialog(...args);
+ const requestOpenLocalFolder = (...args) => sidebarWorkspace.requestOpenLocalFolder(...args);
+ const createDefaultLocalWorkspace = (...args) => sidebarWorkspace.createDefaultLocalWorkspace(...args);
+ const closeWorkspaceSourceMenu = (...args) => sidebarWorkspace.closeWorkspaceSourceMenu(...args);
+ const openWorkspaceSourceMenu = (...args) => sidebarWorkspace.openWorkspaceSourceMenu(...args);
+ const closeAccountMenu = (...args) => sidebarWorkspace.closeAccountMenu(...args);
+ const openAccountMenu = (...args) => sidebarWorkspace.openAccountMenu(...args);
+ sidebarWorkspace.autoOpenRecentLocalRootOnHome();
function setCommandPending(trigger, pending) {
if (!(trigger instanceof HTMLElement)) return;
diff --git a/rust/crates/mnote-web/browser/sidebar-workspace-runtime.js b/rust/crates/mnote-web/browser/sidebar-workspace-runtime.js
new file mode 100644
index 00000000..074605d2
--- /dev/null
+++ b/rust/crates/mnote-web/browser/sidebar-workspace-runtime.js
@@ -0,0 +1,1009 @@
+export const createSidebarWorkspaceRuntime = (dependencies = {}) => {
+ const {
+ cssEscape,
+ currentDocumentId,
+ escapeHtml,
+ parseJsonScript,
+ setCommandPending,
+ sidebarShellRuntimeFunction,
+ } = dependencies;
+ const MNOTE_SIDEBAR_TREE_MODE_KEY = 'mnote.sidebar.tree.mode';
+ const MNOTE_RECENT_LOCAL_ROOTS_KEY_PREFIX = 'mnote.localFolder.recentRoots:';
+ const MNOTE_LAST_CLOUD_WORKSPACE_KEY = 'mnote.workspace.lastCloudWorkspaceId';
+
+ function normalizeSidebarTreeMode(value) {
+ var mode = String(value || '').trim();
+ return mode === 'filetree' ? 'filetree' : 'page';
+ }
+
+ function readStoredSidebarTreeMode() {
+ var runtimeFn = sidebarShellRuntimeFunction('readStoredSidebarTreeMode');
+ if (runtimeFn) return runtimeFn();
+ var params = new URLSearchParams(window.location.search);
+ var fromUrl = normalizeSidebarTreeMode(params.get('treeView') || params.get('sidebarTree'));
+ if (params.has('treeView') || params.has('sidebarTree')) return fromUrl;
+ try {
+ var stored = window.sessionStorage ? window.sessionStorage.getItem(MNOTE_SIDEBAR_TREE_MODE_KEY) : '';
+ return normalizeSidebarTreeMode(stored);
+ } catch (_) {
+ return 'page';
+ }
+ }
+
+ function persistSidebarTreeMode(mode) {
+ var runtimeFn = sidebarShellRuntimeFunction('persistSidebarTreeMode');
+ if (runtimeFn) return runtimeFn(mode);
+ var normalized = normalizeSidebarTreeMode(mode);
+ document.documentElement.setAttribute('data-mnote-sidebar-tree-mode', normalized);
+ try {
+ if (window.sessionStorage) window.sessionStorage.setItem(MNOTE_SIDEBAR_TREE_MODE_KEY, normalized);
+ } catch (_) {}
+ return normalized;
+ }
+
+ function activeSidebarTreeMode() {
+ var runtimeFn = sidebarShellRuntimeFunction('activeSidebarTreeMode');
+ if (runtimeFn) return runtimeFn();
+ var active = document.querySelector('[data-mnote-sidebar-tree-tab][aria-selected="true"]');
+ if (active instanceof HTMLElement) {
+ return normalizeSidebarTreeMode(active.getAttribute('data-mnote-sidebar-tree-tab'));
+ }
+ return readStoredSidebarTreeMode();
+ }
+
+ function resolveWorkspaceId(trigger) {
+ var direct = (trigger.getAttribute('data-workspace-id') || '').trim();
+ if (direct) return direct;
+ var root = trigger.closest('[data-workspace-id]');
+ if (root) {
+ var value = (root.getAttribute('data-workspace-id') || '').trim();
+ if (value) return value;
+ }
+ var documentId = currentDocumentId();
+ if (documentId) {
+ var shell = document.querySelector('.document-shell[data-document-id="' + cssEscape(documentId) + '"][data-workspace-id]');
+ if (shell instanceof HTMLElement) {
+ var shellWorkspaceId = (shell.getAttribute('data-workspace-id') || '').trim();
+ if (shellWorkspaceId) return shellWorkspaceId;
+ }
+ }
+ var activePane = document.querySelector('[data-pane-visible="true"][data-pane-workspace-id]');
+ if (activePane instanceof HTMLElement) {
+ var paneWorkspaceId = (activePane.getAttribute('data-pane-workspace-id') || '').trim();
+ if (paneWorkspaceId) return paneWorkspaceId;
+ }
+ var anyDocumentShell = document.querySelector('.document-shell[data-workspace-id]');
+ if (anyDocumentShell instanceof HTMLElement) {
+ var documentWorkspaceId = (anyDocumentShell.getAttribute('data-workspace-id') || '').trim();
+ if (documentWorkspaceId) return documentWorkspaceId;
+ }
+ var fromDom = document.querySelector('[data-workspace-id]');
+ if (fromDom instanceof HTMLElement) {
+ var domWorkspaceId = (fromDom.getAttribute('data-workspace-id') || '').trim();
+ if (domWorkspaceId) return domWorkspaceId;
+ }
+ return (new URLSearchParams(window.location.search).get('workspaceId') || 'default').trim() || 'default';
+ }
+
+ function currentWorkspaceId() {
+ return resolveWorkspaceId(document.body);
+ }
+
+ function currentSourceKind() {
+ var params = new URLSearchParams(window.location.search);
+ var fromUrl = (params.get('sourceKind') || '').trim();
+ if (fromUrl) return fromUrl;
+ var fromBody = document.body instanceof HTMLElement ? (document.body.getAttribute('data-mnote-source-kind') || '').trim() : '';
+ if (fromBody) return fromBody;
+ if (
+ window.location.pathname === '/' &&
+ !(params.get('workspaceId') || '').trim() &&
+ !(params.get('rootUri') || '').trim() &&
+ !(params.get('pageId') || '').trim()
+ ) {
+ return 'local_folder';
+ }
+ return 'convex_workspace';
+ }
+
+ function currentRootUri() {
+ var params = new URLSearchParams(window.location.search);
+ var fromUrl = (params.get('rootUri') || '').trim();
+ if (fromUrl) return fromUrl;
+ return document.body instanceof HTMLElement ? (document.body.getAttribute('data-mnote-root-uri') || '').trim() : '';
+ }
+
+ function rememberCloudWorkspaceId(workspaceId) {
+ var normalized = String(workspaceId || '').trim();
+ 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 (_) {}
+ }
+
+ function readCurrentCloudWorkspaceIdFromPage() {
+ if (currentSourceKind() === 'local_folder') return '';
+ var params = new URLSearchParams(window.location.search);
+ var fromUrl = (params.get('workspaceId') || '').trim();
+ 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' && value.indexOf('local-ws:') !== 0) return value;
+ }
+ return '';
+ }
+
+ function rememberCurrentCloudWorkspaceId() {
+ rememberCloudWorkspaceId(readCurrentCloudWorkspaceIdFromPage());
+ }
+
+ function readLastCloudWorkspaceId() {
+ try {
+ var stored = window.localStorage ? window.localStorage.getItem(MNOTE_LAST_CLOUD_WORKSPACE_KEY) : '';
+ 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' && 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.indexOf('local-ws:') !== 0) return value;
+ }
+ return '';
+ }
+
+ function copyWorkspaceSourceParams(targetUrl) {
+ var params = new URLSearchParams(window.location.search);
+ ['sourceKind', 'rootUri', 'secondaryDocumentId', 'secondarySourceKind', 'secondaryRootUri'].forEach(function(name) {
+ var value = (params.get(name) || '').trim();
+ if (value) targetUrl.searchParams.set(name, value);
+ });
+ var sourceKind = currentSourceKind();
+ var rootUri = currentRootUri();
+ if (sourceKind && !targetUrl.searchParams.get('sourceKind')) targetUrl.searchParams.set('sourceKind', sourceKind);
+ if (rootUri && !targetUrl.searchParams.get('rootUri')) targetUrl.searchParams.set('rootUri', rootUri);
+ }
+
+ function currentWorkspaceSourcePayload() {
+ var params = new URLSearchParams(window.location.search);
+ var payload = {};
+ ['sourceKind', 'rootUri'].forEach(function(name) {
+ var value = (params.get(name) || '').trim();
+ if (value) payload[name] = value;
+ });
+ var sourceKind = currentSourceKind();
+ var rootUri = currentRootUri();
+ if (sourceKind && !payload.sourceKind) payload.sourceKind = sourceKind;
+ if (rootUri && !payload.rootUri) payload.rootUri = rootUri;
+ return payload;
+ }
+
+ function currentActorStorageId() {
+ var fromBody = document.body instanceof HTMLElement ? (document.body.getAttribute('data-mnote-actor-id') || '').trim() : '';
+ if (fromBody && fromBody !== 'anonymous') return fromBody;
+ return '';
+ }
+
+ function recentLocalRootsStorageKey() {
+ var actorId = currentActorStorageId();
+ return actorId ? MNOTE_RECENT_LOCAL_ROOTS_KEY_PREFIX + encodeURIComponent(actorId) : '';
+ }
+
+ function readRecentLocalRoots() {
+ try {
+ var storageKey = recentLocalRootsStorageKey();
+ if (!storageKey) return [];
+ var raw = window.localStorage ? window.localStorage.getItem(storageKey) : '';
+ var parsed = raw ? JSON.parse(raw) : [];
+ return Array.isArray(parsed) ? parsed.filter(function(value) {
+ return typeof value === 'string' && value.trim();
+ }).slice(0, 10) : [];
+ } catch (_) {
+ return [];
+ }
+ }
+
+ function rememberLocalRoot(rootUri) {
+ try {
+ if (!window.localStorage) return;
+ var storageKey = recentLocalRootsStorageKey();
+ if (!storageKey) return;
+ var roots = readRecentLocalRoots().filter(function(value) { return value !== rootUri; });
+ roots.unshift(rootUri);
+ window.localStorage.setItem(storageKey, JSON.stringify(roots.slice(0, 10)));
+ } catch (_) {}
+ }
+
+ function pathToFileRootUri(value) {
+ var trimmed = String(value || '').trim();
+ if (!trimmed) return '';
+ if (/^file:\/\//i.test(trimmed)) return trimmed;
+ if (trimmed.charAt(0) !== '/') return '';
+ return 'file://' + trimmed.split('/').map(function(part, index) {
+ return index === 0 ? '' : encodeURIComponent(part);
+ }).join('/');
+ }
+
+ function fileRootUriToPathInput(rootUri) {
+ var value = String(rootUri || '').replace(/^file:\/\//, '');
+ try {
+ return decodeURIComponent(value);
+ } catch (_) {
+ return value;
+ }
+ }
+
+ function recentLocalRootLabel(rootUri) {
+ var label = fileRootUriToPathInput(rootUri);
+ return label || '本地文件夹';
+ }
+
+ function normalizeGrantedLocalFolderRootUri(grant) {
+ var rootUri = String(grant && grant.rootUri || '').trim();
+ if (rootUri && /^file:\/\//i.test(rootUri)) return rootUri;
+ var rootPath = String(grant && grant.rootPath || '').trim();
+ if (rootPath) return pathToFileRootUri(rootPath);
+ if (rootUri) return pathToFileRootUri(rootUri);
+ return '';
+ }
+
+ function isDefaultWorkspaceAutoGrant(grant) {
+ var source = String(grant && grant.source || '').trim();
+ var workspaceId = String(grant && grant.workspaceId || '').trim();
+ var rootUri = String(grant && grant.rootUri || '').trim();
+ var permission = String(grant && grant.permission || '').trim();
+ var createdBy = String(grant && (grant.createdBy || grant.ownerUserId) || '').trim();
+ var targetUser = String(grant && (grant.userId || grant.targetUserId) || '').trim();
+ return source === 'auto'
+ && workspaceId
+ && permission === 'write'
+ && createdBy === targetUser
+ && /^local:\/\/users\/.+\/workspaces\/my-space$/.test(rootUri);
+ }
+
+ function openLocalFolderRoot(rootUri) {
+ if (currentSourceKind() !== 'local_folder') {
+ rememberCurrentCloudWorkspaceId();
+ }
+ rememberLocalRoot(rootUri);
+ var targetUrl = new URL('/', window.location.origin);
+ targetUrl.searchParams.set('treeView', 'filetree');
+ targetUrl.searchParams.set('sourceKind', 'local_folder');
+ targetUrl.searchParams.set('rootUri', rootUri);
+ window.location.href = targetUrl.toString();
+ }
+
+ function switchToCloudWorkspace() {
+ var targetUrl = new URL('/', window.location.origin);
+ targetUrl.searchParams.set('treeView', activeSidebarTreeMode() || 'filetree');
+ targetUrl.searchParams.set('mnoteHome', '1');
+ window.location.href = targetUrl.toString();
+ }
+
+ function closeTrashModal() {
+ var modal = document.querySelector('[data-testid="mnote-trash-modal"]');
+ if (!modal) return;
+ var workbench = modal.querySelector('[data-testid="mnote-trash-workbench"]');
+ if (workbench && workbench.__mnoteTrashEventSource && typeof workbench.__mnoteTrashEventSource.close === 'function') {
+ workbench.__mnoteTrashEventSource.close();
+ }
+ var fileScrollTop = Number(modal.getAttribute('data-file-scroll-top') || '0');
+ var fileRoot = document.getElementById('sidebar-file-tree-root');
+ if (fileRoot instanceof HTMLElement && Number.isFinite(fileScrollTop)) fileRoot.scrollTop = fileScrollTop;
+ if (modal.parentElement) modal.parentElement.removeChild(modal);
+ document.documentElement.removeAttribute('data-mnote-trash-modal-open');
+ }
+
+ function executeTrashWorkbenchScripts(parsed) {
+ parsed.querySelectorAll('script').forEach(function(script) {
+ var nextScript = document.createElement('script');
+ Array.from(script.attributes || []).forEach(function(attr) {
+ nextScript.setAttribute(attr.name, attr.value);
+ });
+ nextScript.textContent = script.textContent || '';
+ document.body.appendChild(nextScript);
+ if (nextScript.parentElement) nextScript.parentElement.removeChild(nextScript);
+ });
+ }
+
+ function renderLocalFolderTrashPlaceholder(content, rootUri) {
+ content.innerHTML = [
+ '',
+ '',
+ '当前弹窗已保持在本地文件夹上下文:' + escapeHtml(rootUri || '未选择本地目录') + '
',
+ ''
+ ].join('');
+ }
+
+ function openTrashModal(trigger) {
+ var existing = document.querySelector('[data-testid="mnote-trash-modal"]');
+ if (existing) return;
+ var fileRoot = document.getElementById('sidebar-file-tree-root');
+ var activeRow = document.querySelector('#sidebar-file-tree-root .tree-row[data-selected="true"], #sidebar-tree-root .tree-row[data-active="true"]');
+ var overlay = document.createElement('div');
+ overlay.className = 'mnote-trash-modal';
+ overlay.setAttribute('data-testid', 'mnote-trash-modal');
+ overlay.setAttribute('role', 'dialog');
+ overlay.setAttribute('aria-modal', 'true');
+ overlay.setAttribute('aria-label', '垃圾箱');
+ overlay.setAttribute('data-file-scroll-top', fileRoot instanceof HTMLElement ? String(fileRoot.scrollTop) : '0');
+ if (activeRow instanceof HTMLElement) {
+ overlay.setAttribute('data-active-row-id', activeRow.getAttribute('data-row-id') || activeRow.getAttribute('data-node-id') || '');
+ }
+ overlay.innerHTML = '' +
+ '';
+ document.body.appendChild(overlay);
+ document.documentElement.setAttribute('data-mnote-trash-modal-open', 'true');
+ var content = overlay.querySelector('[data-testid="mnote-trash-modal-content"]');
+ var sourceKind = currentSourceKind();
+ var workspaceId = resolveWorkspaceId(trigger || document.body);
+ var url = new URL('/trash', window.location.origin);
+ if (sourceKind === 'local_folder') {
+ var rootUri = currentRootUri();
+ if (!rootUri) {
+ renderLocalFolderTrashPlaceholder(content, '');
+ return;
+ }
+ url.searchParams.set('sourceKind', 'local_folder');
+ url.searchParams.set('rootUri', rootUri);
+ } else if (workspaceId) {
+ url.searchParams.set('workspaceId', workspaceId);
+ }
+ fetch(url.toString(), { headers: { 'x-mnote-trash-modal': '1' } }).then(function(response) {
+ return response.text().then(function(html) {
+ if (!response.ok) throw new Error('trash_modal_load_failed_' + response.status);
+ var parsed = new DOMParser().parseFromString(html, 'text/html');
+ var workbench = parsed.querySelector('[data-testid="mnote-trash-workbench"]');
+ if (!workbench) throw new Error('trash_modal_missing_workbench');
+ content.innerHTML = '';
+ content.appendChild(workbench);
+ executeTrashWorkbenchScripts(parsed);
+ });
+ }).catch(function(error) {
+ content.innerHTML = '' + escapeHtml(error && error.message ? error.message : String(error)) + '
';
+ });
+ }
+
+ function closeLocalFolderDialog() {
+ var existing = document.querySelector('[data-testid="mnote-local-folder-dialog"]');
+ if (existing && existing.parentElement) existing.parentElement.removeChild(existing);
+ }
+
+ function readPathFromDirectoryFiles(files) {
+ var first = files && files.length ? files[0] : null;
+ if (!first) return '';
+ var rawPath = typeof first.path === 'string' ? first.path : '';
+ var relative = typeof first.webkitRelativePath === 'string' ? first.webkitRelativePath : '';
+ if (rawPath && relative) {
+ var suffix = relative.split('/').filter(Boolean).join('/');
+ if (suffix && rawPath.endsWith(suffix)) {
+ return rawPath.slice(0, rawPath.length - suffix.length).replace(/[\/\\]$/, '');
+ }
+ }
+ if (rawPath) return rawPath;
+ return '';
+ }
+
+ function requestBrowserFolderChoice(statusNode) {
+ return new Promise(function(resolve) {
+ var input = document.createElement('input');
+ input.type = 'file';
+ input.multiple = true;
+ input.setAttribute('webkitdirectory', '');
+ input.setAttribute('directory', '');
+ input.style.position = 'fixed';
+ input.style.left = '-9999px';
+ input.addEventListener('change', function() {
+ var selectedPath = readPathFromDirectoryFiles(input.files || []);
+ if (input.parentElement) input.parentElement.removeChild(input);
+ if (!selectedPath && statusNode instanceof HTMLElement) {
+ statusNode.textContent = '当前浏览器没有暴露本机绝对路径,请在下方输入路径。';
+ }
+ resolve(selectedPath);
+ }, { once: true });
+ document.body.appendChild(input);
+ input.click();
+ });
+ }
+
+ async function requestNativeFolderChoice(statusNode) {
+ var desktopPicker = window.__mnoteDesktop && typeof window.__mnoteDesktop.selectLocalFolder === 'function'
+ ? window.__mnoteDesktop.selectLocalFolder
+ : null;
+ if (desktopPicker) {
+ var selected = await desktopPicker();
+ return typeof selected === 'string' ? selected : '';
+ }
+ if (typeof window.showDirectoryPicker === 'function') {
+ var handle = await window.showDirectoryPicker({ mode: 'read' });
+ var handlePath = handle && (handle.path || handle.mnotePath || handle.nativePath);
+ if (typeof handlePath === 'string' && handlePath.trim()) return handlePath;
+ if (statusNode instanceof HTMLElement) {
+ statusNode.textContent = '已选择“' + (handle && handle.name ? handle.name : '文件夹') + '”,但浏览器没有暴露本机绝对路径,请在下方确认路径。';
+ }
+ return '';
+ }
+ return requestBrowserFolderChoice(statusNode);
+ }
+
+ function openLocalFolderDialog(initialMessage) {
+ closeLocalFolderDialog();
+ var recent = readRecentLocalRoots();
+ var dialog = document.createElement('div');
+ dialog.className = 'mnote-local-folder-dialog';
+ dialog.setAttribute('data-testid', 'mnote-local-folder-dialog');
+ dialog.setAttribute('role', 'dialog');
+ dialog.setAttribute('aria-modal', 'true');
+ dialog.style.position = 'fixed';
+ dialog.style.inset = '0';
+ dialog.style.zIndex = '2147483646';
+ dialog.style.background = 'rgba(15, 23, 42, 0.28)';
+ dialog.style.display = 'flex';
+ dialog.style.alignItems = 'center';
+ dialog.style.justifyContent = 'center';
+ var card = document.createElement('div');
+ card.className = 'mnote-local-folder-dialog__card';
+ card.style.width = 'min(640px, calc(100vw - 32px))';
+ card.style.background = '#fff';
+ card.style.border = '1px solid rgba(27, 28, 28, 0.12)';
+ card.style.borderRadius = '8px';
+ card.style.padding = '16px';
+ card.style.boxShadow = '0 18px 48px rgba(15, 23, 42, 0.22)';
+ card.style.display = 'grid';
+ card.style.gap = '12px';
+ var title = document.createElement('h2');
+ title.textContent = '打开本地文件夹';
+ title.style.margin = '0';
+ title.style.fontSize = '18px';
+ title.style.lineHeight = '1.4';
+ var status = document.createElement('p');
+ status.className = 'mnote-local-folder-dialog__status';
+ status.setAttribute('data-testid', 'mnote-local-folder-status');
+ status.textContent = initialMessage || '选择一个本机文件夹,或输入绝对路径。';
+ status.style.margin = '0';
+ status.style.color = '#4b5563';
+ status.style.fontSize = '13px';
+ var input = document.createElement('input');
+ input.type = 'text';
+ input.autocomplete = 'off';
+ input.spellcheck = false;
+ input.placeholder = '/mnt/Data1T/mnote/design/04-tree-domain/done';
+ input.setAttribute('data-testid', 'mnote-local-folder-path-input');
+ input.value = recent.length ? fileRootUriToPathInput(recent[0]) : '';
+ input.style.width = '100%';
+ input.style.boxSizing = 'border-box';
+ input.style.border = '1px solid rgba(27, 28, 28, 0.18)';
+ input.style.borderRadius = '6px';
+ input.style.padding = '10px 12px';
+ input.style.fontSize = '14px';
+ var actions = document.createElement('div');
+ actions.className = 'mnote-local-folder-dialog__actions';
+ actions.style.display = 'flex';
+ actions.style.gap = '8px';
+ actions.style.justifyContent = 'flex-end';
+ var choose = document.createElement('button');
+ choose.type = 'button';
+ choose.textContent = '选择文件夹';
+ choose.setAttribute('data-testid', 'mnote-local-folder-native-picker');
+ var cancel = document.createElement('button');
+ cancel.type = 'button';
+ cancel.textContent = '取消';
+ var confirm = document.createElement('button');
+ confirm.type = 'button';
+ confirm.textContent = '打开';
+ confirm.setAttribute('data-testid', 'mnote-local-folder-open-confirm');
+ [choose, cancel, confirm].forEach(function(button) {
+ button.style.border = '1px solid rgba(27, 28, 28, 0.14)';
+ button.style.borderRadius = '6px';
+ button.style.padding = '8px 12px';
+ button.style.background = '#fff';
+ button.style.cursor = 'pointer';
+ button.style.fontSize = '14px';
+ });
+ function submit() {
+ var rootUri = pathToFileRootUri(input.value);
+ if (!rootUri) {
+ status.textContent = '请输入以 / 开头的绝对路径,或 file:// URI。';
+ input.focus();
+ return;
+ }
+ closeLocalFolderDialog();
+ openLocalFolderRoot(rootUri);
+ }
+ choose.addEventListener('click', function(event) {
+ event.preventDefault();
+ requestNativeFolderChoice(status).then(function(selectedPath) {
+ if (selectedPath) {
+ input.value = selectedPath;
+ submit();
+ }
+ }).catch(function(error) {
+ status.textContent = error && error.name === 'AbortError'
+ ? '已取消选择。'
+ : '无法打开系统文件夹选择器,请输入路径。';
+ });
+ });
+ cancel.addEventListener('click', function(event) {
+ event.preventDefault();
+ closeLocalFolderDialog();
+ });
+ confirm.addEventListener('click', function(event) {
+ event.preventDefault();
+ submit();
+ });
+ input.addEventListener('keydown', function(event) {
+ if (event.key === 'Enter') {
+ event.preventDefault();
+ submit();
+ }
+ });
+ actions.appendChild(choose);
+ actions.appendChild(cancel);
+ actions.appendChild(confirm);
+ card.appendChild(title);
+ card.appendChild(status);
+ card.appendChild(input);
+ var authorizedSection = document.createElement('div');
+ authorizedSection.className = 'mnote-local-folder-dialog__authorized';
+ authorizedSection.setAttribute('data-testid', 'mnote-local-folder-authorized-roots');
+ authorizedSection.hidden = true;
+ card.appendChild(authorizedSection);
+ fetch('/api/user/access-policy', {
+ method: 'GET',
+ headers: { 'accept': 'application/json' },
+ credentials: 'include'
+ }).then(function(response) {
+ return response.ok ? response.json() : null;
+ }).then(function(payload) {
+ var grants = payload && Array.isArray(payload.grants) ? payload.grants : [];
+ var activeGrants = grants.filter(function(grant) {
+ return grant && grant.active !== false && grant.status !== 'revoked';
+ });
+ if (!activeGrants.length) return;
+ authorizedSection.hidden = false;
+ var authorizedTitle = document.createElement('div');
+ authorizedTitle.style.fontSize = '12px';
+ authorizedTitle.style.color = '#6b7280';
+ authorizedTitle.textContent = '已授权文件夹';
+ var authorizedList = document.createElement('div');
+ authorizedList.className = 'mnote-local-folder-dialog__recent';
+ activeGrants.slice(0, 8).forEach(function(grant) {
+ if (isDefaultWorkspaceAutoGrant(grant)) return;
+ var rootUri = normalizeGrantedLocalFolderRootUri(grant);
+ if (!rootUri) return;
+ var button = document.createElement('button');
+ button.type = 'button';
+ button.setAttribute('data-testid', 'mnote-local-folder-authorized-root');
+ button.textContent = fileRootUriToPathInput(rootUri);
+ button.addEventListener('click', function(event) {
+ event.preventDefault();
+ closeLocalFolderDialog();
+ openLocalFolderRoot(rootUri);
+ });
+ authorizedList.appendChild(button);
+ });
+ if (!authorizedList.childElementCount) return;
+ authorizedSection.appendChild(authorizedTitle);
+ authorizedSection.appendChild(authorizedList);
+ }).catch(function() {});
+ if (recent.length) {
+ var recentTitle = document.createElement('div');
+ recentTitle.style.fontSize = '12px';
+ recentTitle.style.color = '#6b7280';
+ recentTitle.textContent = '最近使用';
+ card.appendChild(recentTitle);
+ var recentList = document.createElement('div');
+ recentList.className = 'mnote-local-folder-dialog__recent';
+ recent.slice(0, 5).forEach(function(rootUri) {
+ var button = document.createElement('button');
+ button.type = 'button';
+ button.textContent = recentLocalRootLabel(rootUri);
+ button.addEventListener('click', function(event) {
+ event.preventDefault();
+ closeLocalFolderDialog();
+ openLocalFolderRoot(rootUri);
+ });
+ recentList.appendChild(button);
+ });
+ card.appendChild(recentList);
+ }
+ card.appendChild(actions);
+ dialog.appendChild(card);
+ dialog.addEventListener('click', function(event) {
+ if (event.target === dialog) closeLocalFolderDialog();
+ });
+ document.body.appendChild(dialog);
+ input.focus();
+ input.select();
+ }
+
+ function requestOpenLocalFolder() {
+ openLocalFolderDialog('');
+ }
+
+ async function createDefaultLocalWorkspace(trigger) {
+ setCommandPending(trigger, true);
+ try {
+ var response = await fetch('/api/local-folder/workspaces/default', {
+ method: 'POST',
+ headers: { 'content-type': 'application/json' },
+ body: '{}'
+ });
+ var payload = await response.json().catch(function() { return null; });
+ if (!response.ok) {
+ throw new Error((payload && payload.message) || ('local_workspace_create_failed_' + response.status));
+ }
+ var workspace = payload && payload.workspace || {};
+ var rootUri = String(workspace.rootUri || '').trim();
+ if (!rootUri) throw new Error('local_workspace_create_missing_root_uri');
+ openLocalFolderRoot(rootUri);
+ } catch (error) {
+ var status = document.querySelector('[data-testid="mnote-local-folder-status"]');
+ if (status instanceof HTMLElement) {
+ status.textContent = error && error.message ? error.message : String(error);
+ } else {
+ openLocalFolderDialog(error && error.message ? error.message : String(error));
+ }
+ } finally {
+ setCommandPending(trigger, false);
+ }
+ }
+
+ function autoOpenRecentLocalRootOnHome() {
+ if (window.location.pathname !== '/') return false;
+ var params = new URLSearchParams(window.location.search);
+ if ((params.get('sourceKind') || '').trim()) return false;
+ 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]);
+ return true;
+ }
+
+ function closeWorkspaceSourceMenu() {
+ var existing = document.querySelector('[data-testid="mnote-workspace-source-menu"]');
+ if (existing && existing.parentElement) existing.parentElement.removeChild(existing);
+ document.querySelectorAll('[data-testid="mnote-workspace-source-trigger"]').forEach(function(trigger) {
+ if (trigger instanceof HTMLElement) trigger.setAttribute('aria-expanded', 'false');
+ });
+ }
+
+ function workspaceSourceLabel(rootUri) {
+ var label = String(rootUri || '').replace(/^file:\/\//, '');
+ try {
+ label = decodeURIComponent(label);
+ } catch (_) {}
+ return label || '本地文件夹';
+ }
+
+ function openWorkspaceSourceMenu(trigger) {
+ closeWorkspaceSourceMenu();
+ if (!(trigger instanceof HTMLElement)) return;
+ trigger.setAttribute('aria-expanded', 'true');
+ var menu = document.createElement('div');
+ menu.className = 'mnote-workspace-source-menu';
+ menu.setAttribute('data-testid', 'mnote-workspace-source-menu');
+ menu.setAttribute('role', 'menu');
+ var cloudButton = document.createElement('button');
+ cloudButton.type = 'button';
+ cloudButton.className = 'mnote-workspace-source-menu__item';
+ cloudButton.setAttribute('data-testid', 'mnote-switch-cloud-workspace');
+ cloudButton.setAttribute('role', 'menuitem');
+ cloudButton.textContent = '我的空间';
+ cloudButton.addEventListener('click', function(event) {
+ event.preventDefault();
+ closeWorkspaceSourceMenu();
+ switchToCloudWorkspace();
+ });
+ menu.appendChild(cloudButton);
+ var recent = readRecentLocalRoots();
+ if (recent.length) {
+ var label = document.createElement('div');
+ label.className = 'mnote-workspace-source-menu__label';
+ label.textContent = '最近本地文件夹';
+ menu.appendChild(label);
+ recent.slice(0, 8).forEach(function(rootUri) {
+ var button = document.createElement('button');
+ button.type = 'button';
+ button.className = 'mnote-workspace-source-menu__item';
+ button.setAttribute('data-testid', 'mnote-recent-local-root');
+ button.setAttribute('data-root-uri', rootUri);
+ button.setAttribute('role', 'menuitem');
+ button.textContent = workspaceSourceLabel(rootUri);
+ button.addEventListener('click', function(event) {
+ event.preventDefault();
+ closeWorkspaceSourceMenu();
+ openLocalFolderRoot(rootUri);
+ });
+ menu.appendChild(button);
+ });
+ }
+ var openOther = document.createElement('button');
+ openOther.type = 'button';
+ openOther.className = 'mnote-workspace-source-menu__item mnote-workspace-source-menu__item--primary';
+ openOther.setAttribute('data-testid', 'mnote-open-other-local-folder');
+ openOther.setAttribute('role', 'menuitem');
+ openOther.textContent = '打开其他本地文件夹';
+ openOther.addEventListener('click', function(event) {
+ event.preventDefault();
+ closeWorkspaceSourceMenu();
+ requestOpenLocalFolder();
+ });
+ menu.appendChild(openOther);
+ trigger.closest('[data-testid="wolai-workspace-identity"]')?.appendChild(menu);
+ }
+
+ function closeAccountMenu() {
+ var existing = document.querySelector('[data-testid="mnote-account-menu"]');
+ if (existing && existing.parentElement) existing.parentElement.removeChild(existing);
+ document.querySelectorAll('[data-testid="mnote-account-menu-trigger"]').forEach(function(trigger) {
+ if (trigger instanceof HTMLElement) trigger.setAttribute('aria-expanded', 'false');
+ });
+ }
+
+ function closeProfileDialog() {
+ var existing = document.querySelector('[data-testid="mnote-profile-dialog"]');
+ if (existing && existing.parentElement) existing.parentElement.removeChild(existing);
+ }
+
+ function closeAdminAccessPolicyDialog() {
+ var existing = document.querySelector('[data-testid="mnote-admin-access-policy-modal"]');
+ if (existing && existing.parentElement) existing.parentElement.removeChild(existing);
+ document.documentElement.removeAttribute('data-mnote-admin-access-policy-modal-open');
+ }
+
+ function accountMenuFallbackSession() {
+ var body = document.body instanceof HTMLElement ? document.body : null;
+ var actorId = body ? String(body.getAttribute('data-mnote-actor-id') || '').trim() : '';
+ return {
+ ok: true,
+ name: actorId || '当前用户',
+ email: '',
+ userId: actorId || 'anonymous',
+ actorType: actorId && actorId !== 'anonymous' ? 'user' : 'anonymous',
+ authMode: 'browser'
+ };
+ }
+
+ function renderAccountInfo(menu, session) {
+ var nameNode = menu.querySelector('[data-account-info="name"]');
+ var emailNode = menu.querySelector('[data-account-info="email"]');
+ var idNode = menu.querySelector('[data-account-info="user-id"]');
+ var typeNode = menu.querySelector('[data-account-info="actor-type"]');
+ if (nameNode) nameNode.textContent = session.name || session.userId || '当前用户';
+ if (emailNode) emailNode.textContent = session.email || '未提供邮箱';
+ if (idNode) {
+ idNode.textContent = session.userId || 'anonymous';
+ idNode.setAttribute('title', session.userId || 'anonymous');
+ }
+ if (typeNode) typeNode.textContent = session.actorType || session.authMode || 'unknown';
+ var copyButton = menu.querySelector('[data-account-copy-user-id]');
+ if (copyButton instanceof HTMLElement) {
+ copyButton.dataset.copyValue = session.userId || 'anonymous';
+ copyButton.setAttribute('title', '复制用户 ID');
+ }
+ }
+
+ function sessionIsAdmin(session) {
+ return String(session && session.actorType || '').trim() === 'admin';
+ }
+
+ async function loadAccountInfo(menu) {
+ try {
+ var response = await fetch('/api/auth/session', {
+ method: 'GET',
+ headers: { 'accept': 'application/json' },
+ credentials: 'include'
+ });
+ var payload = await response.json().catch(function() { return null; });
+ if (!response.ok || !payload) throw new Error('session_failed_' + response.status);
+ renderAccountInfo(menu, payload);
+ } catch (_) {
+ renderAccountInfo(menu, accountMenuFallbackSession());
+ }
+ }
+
+ async function fetchAccountSession() {
+ try {
+ var response = await fetch('/api/auth/session', {
+ method: 'GET',
+ headers: { 'accept': 'application/json' },
+ credentials: 'include'
+ });
+ var payload = await response.json().catch(function() { return null; });
+ if (!response.ok || !payload) throw new Error('session_failed_' + response.status);
+ return payload;
+ } catch (_) {
+ return accountMenuFallbackSession();
+ }
+ }
+
+ function openProfileDialog(session) {
+ closeProfileDialog();
+ closeAccountMenu();
+ var dialog = document.createElement('div');
+ dialog.className = 'mnote-profile-dialog';
+ dialog.setAttribute('data-testid', 'mnote-profile-dialog');
+ dialog.setAttribute('role', 'dialog');
+ dialog.setAttribute('aria-modal', 'true');
+ dialog.setAttribute('aria-labelledby', 'mnote-profile-dialog-title');
+ var userId = escapeHtml(session.userId || 'anonymous');
+ dialog.innerHTML =
+ '' +
+ '' +
+ '' +
+ '' +
+ '
' + escapeHtml(String(session.name || session.email || session.userId || '用').slice(0, 1).toUpperCase()) + '
' +
+ '
' + escapeHtml(session.name || session.userId || '当前用户') + '' + escapeHtml(session.email || '未提供邮箱') + '
' +
+ '
' +
+ '' +
+ '- 用户名
- ' + escapeHtml(session.name || '未设置') + '
' +
+ '- 邮箱
- ' + escapeHtml(session.email || '未提供邮箱') + '
' +
+ '- 用户 ID
' + userId + ' ' +
+ '- 身份
- ' + escapeHtml(session.actorType || session.authMode || 'unknown') + '
' +
+ '
' +
+ '';
+ dialog.querySelectorAll('[data-profile-dialog-close]').forEach(function(button) {
+ button.addEventListener('click', function(event) {
+ event.preventDefault();
+ closeProfileDialog();
+ });
+ });
+ var copyButton = dialog.querySelector('[data-profile-copy-user-id]');
+ if (copyButton) {
+ copyButton.addEventListener('click', function(event) {
+ event.preventDefault();
+ void copyTreeContextValue(copyButton.getAttribute('data-copy-value') || '', 'profile-user-id');
+ });
+ }
+ document.body.appendChild(dialog);
+ var closeButton = dialog.querySelector('.mnote-profile-dialog__close');
+ if (closeButton instanceof HTMLElement) closeButton.focus();
+ }
+
+ function openAdminAccessPolicyDialog(trigger) {
+ closeAdminAccessPolicyDialog();
+ closeAccountMenu();
+ if (!(trigger instanceof HTMLElement)) return;
+ var role = trigger.getAttribute('data-access-policy-role') === 'admin' ? 'admin' : 'user';
+ var template = document.querySelector('[data-testid="mnote-admin-access-policy-template-' + role + '"]');
+ var dialog = document.createElement('div');
+ dialog.className = 'mnote-admin-policy-modal';
+ dialog.setAttribute('data-testid', 'mnote-admin-access-policy-modal');
+ dialog.setAttribute('role', 'dialog');
+ dialog.setAttribute('aria-modal', 'true');
+ dialog.setAttribute('aria-labelledby', 'mnote-admin-policy-dialog-title');
+ dialog.innerHTML =
+ '' +
+ '';
+ var content = dialog.querySelector('[data-testid="mnote-admin-access-policy-modal-content"]');
+ if (content) {
+ content.innerHTML = template ? template.innerHTML : '授权管理面板加载失败
';
+ }
+ if (typeof window.MNOTEInitAccessPolicyPanel === 'function') {
+ window.MNOTEInitAccessPolicyPanel(dialog);
+ }
+ dialog.querySelectorAll('[data-admin-access-policy-close]').forEach(function(button) {
+ button.addEventListener('click', function(event) {
+ event.preventDefault();
+ closeAdminAccessPolicyDialog();
+ });
+ });
+ document.body.appendChild(dialog);
+ document.documentElement.setAttribute('data-mnote-admin-access-policy-modal-open', 'true');
+ var closeButton = dialog.querySelector('.mnote-admin-policy-modal__close');
+ if (closeButton instanceof HTMLElement) closeButton.focus();
+ }
+
+ async function signOutAccount(trigger) {
+ setCommandPending(trigger, true);
+ try {
+ var response = await fetch('/api/auth', {
+ method: 'POST',
+ headers: { 'content-type': 'application/json' },
+ credentials: 'include',
+ body: JSON.stringify({ action: 'auth:signOut', args: {} })
+ });
+ var payload = await response.json().catch(function() { return {}; });
+ payload = payload || {};
+ if (!response.ok || payload.error) {
+ throw new Error(payload.error || '退出登录失败');
+ }
+ window.location.assign('/auth');
+ } catch (error) {
+ var menu = document.querySelector('[data-testid="mnote-account-menu"]');
+ var errorNode = menu ? menu.querySelector('[data-account-error]') : null;
+ if (errorNode instanceof HTMLElement) {
+ errorNode.textContent = error && error.message ? error.message : '退出登录失败';
+ errorNode.hidden = false;
+ }
+ setCommandPending(trigger, false);
+ }
+ }
+
+ function openAccountMenu(trigger) {
+ closeAccountMenu();
+ if (!(trigger instanceof HTMLElement)) return;
+ trigger.setAttribute('aria-expanded', 'true');
+ var menu = document.createElement('div');
+ menu.className = 'mnote-workspace-source-menu mnote-account-menu';
+ menu.setAttribute('data-testid', 'mnote-account-menu');
+ menu.setAttribute('role', 'menu');
+ menu.innerHTML =
+ '' +
+ '' +
+ '' +
+ '';
+ var sessionPromise = fetchAccountSession();
+ var profileButton = menu.querySelector('[data-testid="mnote-account-profile"]');
+ if (profileButton) {
+ profileButton.addEventListener('click', function(event) {
+ event.preventDefault();
+ sessionPromise.then(openProfileDialog);
+ });
+ }
+ var accessPolicyLink = menu.querySelector('[data-testid="mnote-account-access-policy"]');
+ sessionPromise.then(function(session) {
+ if (!(accessPolicyLink instanceof HTMLElement)) return;
+ accessPolicyLink.hidden = false;
+ accessPolicyLink.setAttribute('data-access-policy-role', sessionIsAdmin(session) ? 'admin' : 'user');
+ });
+ if (accessPolicyLink) {
+ accessPolicyLink.addEventListener('click', function(event) {
+ event.preventDefault();
+ openAdminAccessPolicyDialog(accessPolicyLink);
+ });
+ }
+ var signOutButton = menu.querySelector('[data-testid="mnote-account-sign-out"]');
+ if (signOutButton) {
+ signOutButton.addEventListener('click', function(event) {
+ event.preventDefault();
+ void signOutAccount(signOutButton);
+ });
+ }
+ var host = trigger.closest('[data-testid="wolai-sidebar-quick-actions"]') || trigger.parentElement;
+ if (host) host.appendChild(menu);
+ }
+
+
+ return {
+ activeSidebarTreeMode,
+ autoOpenRecentLocalRootOnHome,
+ closeAccountMenu,
+ closeLocalFolderDialog,
+ closeTrashModal,
+ closeWorkspaceSourceMenu,
+ copyWorkspaceSourceParams,
+ createDefaultLocalWorkspace,
+ currentRootUri,
+ currentSourceKind,
+ currentWorkspaceId,
+ currentWorkspaceSourcePayload,
+ normalizeSidebarTreeMode,
+ openAccountMenu,
+ openTrashModal,
+ openWorkspaceSourceMenu,
+ persistSidebarTreeMode,
+ readStoredSidebarTreeMode,
+ requestOpenLocalFolder,
+ resolveWorkspaceId,
+ };
+};
diff --git a/rust/crates/mnote-web/src/routes/mod.rs b/rust/crates/mnote-web/src/routes/mod.rs
index 91f9354f..fc93340b 100644
--- a/rust/crates/mnote-web/src/routes/mod.rs
+++ b/rust/crates/mnote-web/src/routes/mod.rs
@@ -126,6 +126,10 @@ pub fn build_router(state: AppState) -> Router {
"/api/mnote-browser-runtime/sidebar-shell-runtime.js",
get(web_shell::sidebar_shell_runtime_asset),
)
+ .route(
+ "/api/mnote-browser-runtime/sidebar-workspace-runtime.js",
+ get(web_shell::sidebar_workspace_runtime_asset),
+ )
.route(
"/api/mnote-browser-runtime/sidebar-tree-runtime.js",
get(web_shell::sidebar_tree_runtime_asset),
@@ -579,6 +583,7 @@ mod tests {
"/api/mnote-browser-runtime/filetree-dnd-runtime.js",
"/api/mnote-browser-runtime/filetree-keyboard-runtime.js",
"/api/mnote-browser-runtime/sidebar-shell-runtime.js",
+ "/api/mnote-browser-runtime/sidebar-workspace-runtime.js",
"/api/mnote-browser-runtime/sidebar-tree-runtime.js",
"/api/mnote-browser-runtime/tree-live-controller.js",
"/api/mnote-browser-runtime/tree-shell-runtime.js",
diff --git a/rust/crates/mnote-web/src/routes/web_shell.rs b/rust/crates/mnote-web/src/routes/web_shell.rs
index 530a716e..cb711a00 100644
--- a/rust/crates/mnote-web/src/routes/web_shell.rs
+++ b/rust/crates/mnote-web/src/routes/web_shell.rs
@@ -742,6 +742,20 @@ pub async fn sidebar_shell_runtime_asset() -> Response {
.unwrap_or_else(|_| Response::new(Body::empty()))
}
+pub async fn sidebar_workspace_runtime_asset() -> Response {
+ const JS: &str = include_str!("../../browser/sidebar-workspace-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 filetree_keyboard_runtime_asset() -> Response {
const JS: &str = include_str!("../../browser/filetree-keyboard-runtime.js");
Response::builder()
diff --git a/rust/crates/mnote-web/src/ssr/pages/layout.rs b/rust/crates/mnote-web/src/ssr/pages/layout.rs
index b0730101..375fbe50 100644
--- a/rust/crates/mnote-web/src/ssr/pages/layout.rs
+++ b/rust/crates/mnote-web/src/ssr/pages/layout.rs
@@ -201,6 +201,8 @@ mod tests {
const SIDEBAR_TREE_RUNTIME_JS: &str = include_str!("../../../browser/sidebar-tree-runtime.js");
const SIDEBAR_SHELL_RUNTIME_JS: &str =
include_str!("../../../browser/sidebar-shell-runtime.js");
+ const SIDEBAR_WORKSPACE_RUNTIME_JS: &str =
+ include_str!("../../../browser/sidebar-workspace-runtime.js");
const FILETREE_CONTEXT_MENU_RUNTIME_JS: &str =
include_str!("../../../browser/filetree-context-menu-runtime.js");
const FILETREE_DND_RUNTIME_JS: &str = include_str!("../../../browser/filetree-dnd-runtime.js");
@@ -301,18 +303,18 @@ mod tests {
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("sidebarFileTreeClipboard"));
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("function recentLocalRootsStorageKey"));
- assert!(SIDEBAR_TREE_RUNTIME_JS.contains("data-mnote-actor-id"));
- assert!(SIDEBAR_TREE_RUNTIME_JS.contains("requestOpenLocalFolder"));
- assert!(SIDEBAR_TREE_RUNTIME_JS.contains("autoOpenRecentLocalRootOnHome"));
- assert!(SIDEBAR_TREE_RUNTIME_JS.contains("sourceKind', 'local_folder"));
- assert!(SIDEBAR_TREE_RUNTIME_JS.contains("switchToCloudWorkspace"));
- assert!(SIDEBAR_TREE_RUNTIME_JS.contains("mnote-switch-cloud-workspace"));
- assert!(SIDEBAR_TREE_RUNTIME_JS.contains("mnote-recent-local-root"));
- assert!(SIDEBAR_TREE_RUNTIME_JS.contains("mnote-local-folder-authorized-roots"));
- assert!(SIDEBAR_TREE_RUNTIME_JS.contains("mnote-local-folder-authorized-root"));
- assert!(SIDEBAR_TREE_RUNTIME_JS.contains("已授权文件夹"));
- assert!(SIDEBAR_TREE_RUNTIME_JS.contains("fetch('/api/user/access-policy'"));
+ assert!(SIDEBAR_WORKSPACE_RUNTIME_JS.contains("function recentLocalRootsStorageKey"));
+ assert!(SIDEBAR_WORKSPACE_RUNTIME_JS.contains("data-mnote-actor-id"));
+ assert!(SIDEBAR_WORKSPACE_RUNTIME_JS.contains("requestOpenLocalFolder"));
+ assert!(SIDEBAR_WORKSPACE_RUNTIME_JS.contains("autoOpenRecentLocalRootOnHome"));
+ assert!(SIDEBAR_WORKSPACE_RUNTIME_JS.contains("sourceKind', 'local_folder"));
+ assert!(SIDEBAR_WORKSPACE_RUNTIME_JS.contains("switchToCloudWorkspace"));
+ assert!(SIDEBAR_WORKSPACE_RUNTIME_JS.contains("mnote-switch-cloud-workspace"));
+ assert!(SIDEBAR_WORKSPACE_RUNTIME_JS.contains("mnote-recent-local-root"));
+ assert!(SIDEBAR_WORKSPACE_RUNTIME_JS.contains("mnote-local-folder-authorized-roots"));
+ assert!(SIDEBAR_WORKSPACE_RUNTIME_JS.contains("mnote-local-folder-authorized-root"));
+ assert!(SIDEBAR_WORKSPACE_RUNTIME_JS.contains("已授权文件夹"));
+ assert!(SIDEBAR_WORKSPACE_RUNTIME_JS.contains("fetch('/api/user/access-policy'"));
}
#[test]
@@ -338,12 +340,12 @@ mod tests {
#[test]
fn page_ai_local_source_passes_file_reference_fields_to_agent_run() {
- assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function currentRootUri()"));
+ assert!(SIDEBAR_WORKSPACE_RUNTIME_JS.contains("function currentRootUri()"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("sourceKind: currentSourceKind()"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("rootUri: currentRootUri()"));
- assert!(SIDEBAR_TREE_RUNTIME_JS
+ assert!(SIDEBAR_WORKSPACE_RUNTIME_JS
.contains("if (sourceKind && !payload.sourceKind) payload.sourceKind = sourceKind"));
- assert!(SIDEBAR_TREE_RUNTIME_JS
+ assert!(SIDEBAR_WORKSPACE_RUNTIME_JS
.contains("if (rootUri && !payload.rootUri) payload.rootUri = rootUri"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("pageContext: scopedContext.pageContext"));
assert!(
@@ -449,15 +451,15 @@ mod tests {
"进入本地文件夹前不能用 document.body 推导 workspaceId;它会在无 workspaceId URL 时回退成 default"
);
assert!(
- SIDEBAR_TREE_RUNTIME_JS.contains("rememberCurrentCloudWorkspaceId()"),
+ SIDEBAR_WORKSPACE_RUNTIME_JS.contains("rememberCurrentCloudWorkspaceId()"),
"进入本地文件夹前应从 URL 或 DOM 子树读取真实 workspaceId"
);
assert!(
- SIDEBAR_TREE_RUNTIME_JS.contains("normalized.indexOf('local-ws:') === 0"),
+ SIDEBAR_WORKSPACE_RUNTIME_JS.contains("normalized.indexOf('local-ws:') === 0"),
"本地文件夹 workspaceId 不能写入 lastCloudWorkspaceId"
);
assert!(
- SIDEBAR_TREE_RUNTIME_JS.contains("stored.trim().indexOf('local-ws:') !== 0"),
+ SIDEBAR_WORKSPACE_RUNTIME_JS.contains("stored.trim().indexOf('local-ws:') !== 0"),
"切回工作区时不能复用 local-ws:* 作为旧云 workspace"
);
assert!(
@@ -471,7 +473,7 @@ mod tests {
fn sidebar_tree_runtime_polls_local_folder_without_browser_reload() {
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("startLocalFolderSidebarWatch"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("refreshLocalFolderSidebarSnapshot"));
- assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function currentWorkspaceId()"));
+ assert!(SIDEBAR_WORKSPACE_RUNTIME_JS.contains("function currentWorkspaceId()"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("var rootUri = currentRootUri();"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("/api/tree/local-folder-watch"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("/api/tree/projections/sidebar"));
@@ -791,21 +793,22 @@ mod tests {
#[test]
fn sidebar_local_folder_authorized_root_normalizes_plain_path_root_uri() {
- assert!(
- SIDEBAR_TREE_RUNTIME_JS.contains("function normalizeGrantedLocalFolderRootUri(grant)")
- );
- assert!(
- SIDEBAR_TREE_RUNTIME_JS.contains("if (rootPath) return pathToFileRootUri(rootPath);")
- );
- assert!(SIDEBAR_TREE_RUNTIME_JS.contains("if (rootUri) return pathToFileRootUri(rootUri);"));
- assert!(SIDEBAR_TREE_RUNTIME_JS
+ assert!(SIDEBAR_WORKSPACE_RUNTIME_JS
+ .contains("function normalizeGrantedLocalFolderRootUri(grant)"));
+ assert!(SIDEBAR_WORKSPACE_RUNTIME_JS
+ .contains("if (rootPath) return pathToFileRootUri(rootPath);"));
+ assert!(SIDEBAR_WORKSPACE_RUNTIME_JS
+ .contains("if (rootUri) return pathToFileRootUri(rootUri);"));
+ assert!(SIDEBAR_WORKSPACE_RUNTIME_JS
.contains("var rootUri = normalizeGrantedLocalFolderRootUri(grant);"));
- assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function isDefaultWorkspaceAutoGrant(grant)"));
- assert!(SIDEBAR_TREE_RUNTIME_JS.contains("if (isDefaultWorkspaceAutoGrant(grant)) return;"));
- assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function recentLocalRootLabel(rootUri)"));
assert!(
- SIDEBAR_TREE_RUNTIME_JS.contains("button.textContent = recentLocalRootLabel(rootUri);")
+ SIDEBAR_WORKSPACE_RUNTIME_JS.contains("function isDefaultWorkspaceAutoGrant(grant)")
);
+ assert!(SIDEBAR_WORKSPACE_RUNTIME_JS
+ .contains("if (isDefaultWorkspaceAutoGrant(grant)) return;"));
+ assert!(SIDEBAR_WORKSPACE_RUNTIME_JS.contains("function recentLocalRootLabel(rootUri)"));
+ assert!(SIDEBAR_WORKSPACE_RUNTIME_JS
+ .contains("button.textContent = recentLocalRootLabel(rootUri);"));
assert!(!SIDEBAR_TREE_RUNTIME_JS
.contains("button.textContent = rootUri.replace(/^file:\\/\\//, '')"));
}