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