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 'local_folder'; } 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', 'fileTreeScope', '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 = [ '
', '

本地文件夹垃圾箱

', '

本地删除项保存在当前目录的 .mnote/trash.mnote/trash-index.json 中。

', '
当前弹窗已保持在本地文件夹上下文:' + 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, }; };