chore: align local-first control plane and editor fixes

- wire SQLite control-plane access/session paths into Rust web local-folder routes

- preserve local Markdown attachment semantics across upload, reload, and secondary-pane resource tabs

- refresh design governance docs, Reasonix task templates, and bug records

- retire root .mcp.json local MCP config
This commit is contained in:
lix-2026
2026-05-23 23:38:42 +08:00
parent 42fb58310c
commit 5f97800489
110 changed files with 5344 additions and 889 deletions
+73 -5
View File
@@ -111,6 +111,7 @@ pub(crate) const ADMIN_POLICY_SCRIPT: &str = r#"
var deleteShareGrantResult = root.querySelector('[data-testid="mnote-admin-delete-share-grant-result"]');
var refreshShareGrantsButton = root.querySelector('[data-admin-action="refresh-share-grants"]');
var shareGrantsList = root.querySelector('[data-testid="mnote-admin-share-grants-list"]');
var currentActorId = document.body && document.body.getAttribute ? String(document.body.getAttribute('data-mnote-actor-id') || '').trim() : '';
var pageConfig = (function () {
var node = root.querySelector('#__MNOTE_ACCESS_POLICY_PAGE__');
try { return JSON.parse(node ? node.textContent || '{}' : '{}'); } catch (_) { return {}; }
@@ -147,6 +148,48 @@ pub(crate) const ADMIN_POLICY_SCRIPT: &str = r#"
return suffix ? base + suffix : base;
}
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 localFolderOpenHref(grant) {
var rootUri = String(grant.rootUri || '').trim();
if (rootUri && !/^file:\/\//i.test(rootUri)) rootUri = '';
if (!rootUri) {
var rootPath = String(grant.rootPath || '').trim();
if (rootPath) rootUri = pathToFileRootUri(rootPath);
}
if (!rootUri) {
rootUri = pathToFileRootUri(String(grant.rootUri || '').trim());
}
if (!rootUri) return '';
var url = new URL('/', window.location.origin);
url.searchParams.set('treeView', 'filetree');
url.searchParams.set('sourceKind', 'local_folder');
url.searchParams.set('rootUri', rootUri);
return url.pathname + url.search;
}
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 renderShareGrants(payload) {
if (!shareGrantsList) return;
var grants = readDirectoryGrants(payload);
@@ -156,12 +199,20 @@ pub(crate) const ADMIN_POLICY_SCRIPT: &str = r#"
}
shareGrantsList.innerHTML = grants.map(function(grant) {
var active = grant.active === false || grant.status === 'revoked' ? '已撤销' : '有效';
var revokeButton = grant.active === false ? '' :
var openHref = localFolderOpenHref(grant);
var rootLabel = escapeHtml(grant.rootPath || grant.rootUri || '');
var createdBy = String(grant.createdBy || grant.ownerUserId || '').trim();
var systemOwned = isDefaultWorkspaceAutoGrant(grant);
var canRevoke = !systemOwned && (isAdmin || (createdBy && currentActorId && createdBy === currentActorId));
var rootNode = openHref
? '<a class="mnote-admin-policy-root-link" data-testid="mnote-admin-open-granted-root" href="' + escapeHtml(openHref) + '">' + rootLabel + '</a>'
: '<strong>' + rootLabel + '</strong>';
var revokeButton = grant.active === false || !canRevoke ? '' :
'<button type="button" data-admin-action="revoke-access-grant" data-grant-id="' + escapeHtml(grant.id || '') + '">撤销</button>';
return '<article class="mnote-admin-policy-row">' +
'<div><strong>' + escapeHtml(grant.rootPath || grant.rootUri || '') + '</strong><span>授权用户 ' + escapeHtml(grant.targetUserId || grant.userId || '') + '</span></div>' +
'<div>' + rootNode + '<span>授权用户 ' + escapeHtml(grant.targetUserId || grant.userId || '') + '</span></div>' +
'<div>' + renderBadge(grant.permission) + renderBadge(active) + '</div>' +
'<div><span>创建者 ' + escapeHtml(grant.createdBy || grant.ownerUserId || '') + '</span><span>授权 ID ' + escapeHtml(grant.id || '') + '</span>' + revokeButton + '</div>' +
'<div><span>创建者 ' + escapeHtml(createdBy) + '</span><span>授权 ID ' + escapeHtml(grant.id || '') + '</span>' + revokeButton + '</div>' +
'</article>';
}).join('');
}
@@ -203,7 +254,8 @@ pub(crate) const ADMIN_POLICY_SCRIPT: &str = r#"
root.querySelector('[data-admin-form="create-share-grant"]').addEventListener('submit', function (event) {
event.preventDefault();
var values = shareGrantFormValues(event.currentTarget);
var form = event.currentTarget;
var values = shareGrantFormValues(form);
setText(createShareGrantResult, '正在创建...');
requestJson(accessPolicyUrl('/grants'), {
method: 'POST',
@@ -211,7 +263,7 @@ pub(crate) const ADMIN_POLICY_SCRIPT: &str = r#"
}).then(function (payload) {
setText(createShareGrantResult, payload);
setText(shareGrantsMessage, '文件夹授权已创建');
event.currentTarget.reset();
form.reset();
return refreshShareGrants();
}).catch(function (error) {
setText(createShareGrantResult, { ok: false, error: error.message || '创建失败' });
@@ -292,4 +344,20 @@ mod tests {
assert!(!html.contains("capabilities"));
assert!(!html.contains("mnote-admin-validate-root-submit"));
}
#[test]
fn admin_policy_script_keeps_form_reference_for_async_reset() {
assert!(ADMIN_POLICY_SCRIPT.contains("var form = event.currentTarget;"));
assert!(ADMIN_POLICY_SCRIPT.contains("form.reset();"));
assert!(!ADMIN_POLICY_SCRIPT.contains("event.currentTarget.reset();"));
}
#[test]
fn admin_policy_script_links_grants_to_local_folder_entry() {
assert!(ADMIN_POLICY_SCRIPT.contains("function localFolderOpenHref(grant)"));
assert!(ADMIN_POLICY_SCRIPT.contains("sourceKind', 'local_folder'"));
assert!(ADMIN_POLICY_SCRIPT.contains("data-testid=\"mnote-admin-open-granted-root\""));
assert!(ADMIN_POLICY_SCRIPT.contains("function isDefaultWorkspaceAutoGrant(grant)"));
assert!(ADMIN_POLICY_SCRIPT.contains("var canRevoke = !systemOwned &&"));
}
}
+38 -16
View File
@@ -94,17 +94,6 @@ fn DocumentPane(model: DocumentPaneViewModel) -> impl IntoView {
rows="1"
>{model.title.clone()}</textarea>
</h1>
<Show when={move || model.pane_role == "secondary"}>
<button
type="button"
class="document-pane-close"
data-mnote-pane-close="secondary"
aria-label="关闭右侧文档"
title="关闭右侧文档"
>
<span class="material-symbols-outlined" data-icon="close" aria-hidden="true"></span>
</button>
</Show>
</div>
<div class="document-shell-meta" aria-label="页面元信息">
<span data-page-title-current="true">{model.title.clone()}</span>
@@ -297,13 +286,14 @@ pub fn DocumentPage(
data-testid="mnote-document-workspace"
data-has-secondary-pane={secondary_visible.to_string()}
>
<section class="document-main-editor-group" data-testid="mnote-main-editor-tab-host">
<div class="mnote-main-tab-strip" data-mnote-main-tab-strip="true" role="tablist" aria-label="主编辑区标签页">
<section class="document-main-editor-group" data-testid="mnote-main-editor-tab-host" data-pane-role="primary">
<div class="mnote-main-tab-strip" data-mnote-main-tab-strip="true" data-pane-role="primary" role="tablist" aria-label="主编辑区标签页">
<button
type="button"
class="mnote-main-tab is-active"
data-mnote-main-tab="page"
data-mnote-tab-kind="page"
data-pane-role="primary"
role="tab"
aria-selected="true"
tabindex="0"
@@ -313,16 +303,17 @@ pub fn DocumentPage(
</button>
</div>
<div class="mnote-main-tab-panels">
<div data-mnote-page-tab-panel="true">
<div data-mnote-page-tab-panel="true" data-pane-role="primary">
<DocumentPane model={primary_model} />
</div>
<section
class="mnote-resource-tab-host"
data-mnote-resource-tab-host="true"
data-pane-role="primary"
data-testid="mnote-resource-tab-host"
hidden=true
>
<div class="mnote-resource-tab-panel-root" data-mnote-resource-tab-panel-root="true"></div>
<div class="mnote-resource-tab-panel-root" data-mnote-resource-tab-panel-root="true" data-pane-role="primary"></div>
</section>
</div>
</section>
@@ -333,7 +324,38 @@ pub fn DocumentPage(
aria-hidden="true"
hidden={!secondary_visible}
></div>
<DocumentPane model={secondary_model} />
<section class="document-main-editor-group" data-testid="mnote-secondary-editor-tab-host" data-pane-role="secondary" hidden={!secondary_visible}>
<div class="mnote-main-tab-strip" data-mnote-main-tab-strip="true" data-pane-role="secondary" role="tablist" aria-label="右侧编辑区标签页">
<button
type="button"
class="mnote-main-tab is-active"
data-mnote-main-tab="page"
data-mnote-tab-kind="page"
data-pane-role="secondary"
role="tab"
aria-selected="true"
tabindex="0"
>
<span class="mnote-main-tab-badge" aria-hidden="true"></span>
<span class="mnote-main-tab-title">{secondary_model.title.clone()}</span>
<span class="mnote-main-tab-close" role="button" data-mnote-pane-close="secondary" aria-label="关闭右侧文档" title="关闭右侧文档">{"×"}</span>
</button>
</div>
<div class="mnote-main-tab-panels">
<div data-mnote-page-tab-panel="true" data-pane-role="secondary">
<DocumentPane model={secondary_model} />
</div>
<section
class="mnote-resource-tab-host"
data-mnote-resource-tab-host="true"
data-pane-role="secondary"
data-testid="mnote-secondary-resource-tab-host"
hidden=true
>
<div class="mnote-resource-tab-panel-root" data-mnote-resource-tab-panel-root="true" data-pane-role="secondary"></div>
</section>
</div>
</section>
</div>
<div class="sr-only" data-mnote-workspace-label>{workspace_label}</div>
</PageLayout>
+574 -61
View File
@@ -97,6 +97,57 @@ const SIDEBAR_TREE_JS: &str = r##"
}
}
function installWorkspaceSidebarResizer() {
var shell = document.querySelector('.mnote-shell, .wolai-workspace-shell');
var sidebar = document.querySelector('[data-testid="wolai-sidebar"]');
var resizer = document.querySelector('[data-mnote-sidebar-resizer="true"]');
if (!(shell instanceof HTMLElement) || !(sidebar instanceof HTMLElement) || !(resizer instanceof HTMLElement)) return;
if (resizer.getAttribute('data-mnote-sidebar-resizer-bound') === 'true') return;
resizer.setAttribute('data-mnote-sidebar-resizer-bound', 'true');
var storageKey = 'mnote.workspace.sidebarWidth.v1';
var minWidth = 220;
var maxWidth = 520;
function clampWidth(value) {
var width = Number(value);
if (!Number.isFinite(width)) return 248;
return Math.max(minWidth, Math.min(maxWidth, width));
}
function applyWidth(value) {
var width = clampWidth(value);
shell.style.setProperty('--mnote-sidebar-width', width + 'px');
document.documentElement.setAttribute('data-mnote-sidebar-width', String(width));
return width;
}
try {
var stored = Number(window.localStorage.getItem(storageKey) || '');
if (stored) applyWidth(stored);
} catch (_) {}
resizer.addEventListener('pointerdown', function(event) {
if (event.button !== 0) return;
event.preventDefault();
var startX = event.clientX;
var startWidth = sidebar.getBoundingClientRect().width || clampWidth(0);
resizer.setPointerCapture(event.pointerId);
document.documentElement.setAttribute('data-mnote-sidebar-resizing', 'true');
function onMove(moveEvent) {
applyWidth(startWidth + moveEvent.clientX - startX);
}
function onEnd(endEvent) {
resizer.removeEventListener('pointermove', onMove);
resizer.removeEventListener('pointerup', onEnd);
resizer.removeEventListener('pointercancel', onEnd);
try { resizer.releasePointerCapture(endEvent.pointerId); } catch (_) {}
document.documentElement.removeAttribute('data-mnote-sidebar-resizing');
try {
window.localStorage.setItem(storageKey, String(Math.round(sidebar.getBoundingClientRect().width)));
} catch (_) {}
}
resizer.addEventListener('pointermove', onMove);
resizer.addEventListener('pointerup', onEnd);
resizer.addEventListener('pointercancel', onEnd);
});
}
function escapeHtml(value) {
return String(value == null ? '' : value)
.replace(/&/g, '&amp;')
@@ -124,9 +175,62 @@ const SIDEBAR_TREE_JS: &str = r##"
function currentDocumentId() {
var match = window.location.pathname.match(/^\/documents\/([^\/]+)/);
if (!match) match = window.location.pathname.match(/^\/mindmap\/([^\/]+)\/([^\/]+)/);
return match ? decodeURIComponent(match[1]) : '';
if (match) return decodeURIComponent(match[1]);
var params = new URLSearchParams(window.location.search);
var fromQuery = (params.get('documentId') || params.get('pageId') || '').trim();
if (fromQuery) return fromQuery;
var activePane = document.querySelector('.document-pane[data-pane-role="primary"][data-pane-document-id], .document-pane[data-pane-visible="true"][data-pane-document-id]');
if (activePane instanceof HTMLElement) {
var paneDocumentId = (activePane.getAttribute('data-pane-document-id') || '').trim();
if (paneDocumentId) return paneDocumentId;
}
var shell = document.querySelector('.document-shell[data-document-id]');
if (shell instanceof HTMLElement) return (shell.getAttribute('data-document-id') || '').trim();
return '';
}
function localFolderSelfChangeSuppressions() {
window.__mnoteLocalFolderSelfChangeSuppressions = window.__mnoteLocalFolderSelfChangeSuppressions || new Map();
return window.__mnoteLocalFolderSelfChangeSuppressions;
}
function persistLocalFolderSelfChangeSuppression(documentId, expiresAt) {
var doc = String(documentId || '').trim();
if (!doc) return;
localFolderSelfChangeSuppressions().set(doc, expiresAt);
try {
var key = 'mnote.localFolder.selfChangeSuppressions.v1';
var existing = JSON.parse(window.sessionStorage.getItem(key) || '{}');
existing[doc] = expiresAt;
window.sessionStorage.setItem(key, JSON.stringify(existing));
} catch (_) {}
}
var EDITOR_UPLOAD_ROOT_SELECTOR = '[data-editor-host-kind="leptos_tiptap_island"], [data-editor-host-kind="leptos_tiptap_resource"]';
function editorUploadRootFromElement(target) {
if (!(target instanceof Element)) return null;
var root = target.closest(EDITOR_UPLOAD_ROOT_SELECTOR);
return root instanceof HTMLElement ? root : null;
}
function rememberEditorUploadRootFromTarget(target) {
var root = editorUploadRootFromElement(target);
if (root instanceof HTMLElement) {
window.__mnoteLastEditorUploadRoot = root;
return root;
}
return null;
}
document.addEventListener('pointerdown', function(event) {
rememberEditorUploadRootFromTarget(event.target);
}, true);
document.addEventListener('focusin', function(event) {
rememberEditorUploadRootFromTarget(event.target);
}, true);
function currentFileTreeActiveRowId() {
var explicitRowId = new URL(window.location.href).searchParams.get('restoreFocusRowId') || '';
if (explicitRowId) return explicitRowId;
@@ -779,6 +883,34 @@ const SIDEBAR_TREE_JS: &str = r##"
}
}
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();
@@ -1066,6 +1198,49 @@ const SIDEBAR_TREE_JS: &str = r##"
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';
@@ -1077,7 +1252,7 @@ const SIDEBAR_TREE_JS: &str = r##"
recent.slice(0, 5).forEach(function(rootUri) {
var button = document.createElement('button');
button.type = 'button';
button.textContent = rootUri.replace(/^file:\/\//, '');
button.textContent = recentLocalRootLabel(rootUri);
button.addEventListener('click', function(event) {
event.preventDefault();
closeLocalFolderDialog();
@@ -1569,8 +1744,7 @@ const SIDEBAR_TREE_JS: &str = r##"
var nextWorkspaceId = result.workspaceId || workspaceId;
var nextDocumentId = commandDocumentId(result, '');
if (nextDocumentId) {
window.__mnoteLocalFolderSelfChangeSuppressions = window.__mnoteLocalFolderSelfChangeSuppressions || new Map();
window.__mnoteLocalFolderSelfChangeSuppressions.set(nextDocumentId, Date.now() + 5000);
persistLocalFolderSelfChangeSuppression(nextDocumentId, Date.now() + 5000);
}
if (nextDocumentId && currentSourceKind() === 'local_folder') {
await refreshLocalFolderSidebarSnapshot();
@@ -2055,7 +2229,7 @@ const SIDEBAR_TREE_JS: &str = r##"
? '<ul class="tree-children' + (expanded ? '' : ' tree-children--collapsed') + '">' + renderPageRows(nodeId, grouped, activeId, depth + 1) + '</ul>'
: '';
var currentAttr = nodeId === activeId ? ' aria-current="page" aria-selected="true"' : ' aria-selected="false"';
return '<li class="tree-node" data-node-id="' + escapeHtml(nodeId) + '"><div class="tree-row" role="treeitem" aria-level="' + (depth + 1) + '" aria-expanded="' + String(expandable && expanded) + '" data-rust-rendered-row="page" data-testid="wolai-sidebar-row" data-tree-testid="tree-node-open" data-node-id="' + escapeHtml(nodeId) + '"' + (parent ? ' data-parent-id="' + escapeHtml(parent) + '"' : '') + ' data-depth="' + depth + '" data-shell-mode="page" data-active="' + String(nodeId === activeId) + '"' + currentAttr + ' data-focused="false" data-page-openable="' + String(openable) + '" data-draggable="true" draggable="true" tabindex="-1">' + toggle + '<button type="button" class="tree-link" data-testid="tree-node-open" data-rust-action="open" data-node-id="' + escapeHtml(nodeId) + '" data-page-openable="' + String(openable) + '"><span class="tree-link-title">' + escapeHtml(title) + '</span></button><div class="tree-actions"><button type="button" class="tree-action" data-testid="tree-action-menu" data-rust-action="menu" data-node-id="' + escapeHtml(nodeId) + '" aria-label="更多操作"></button><button type="button" class="tree-action" data-testid="tree-action-create" data-rust-action="create" data-node-id="' + escapeHtml(nodeId) + '" aria-label="新建子页面">+</button></div></div>' + childHtml + '</li>';
return '<li class="tree-node" data-node-id="' + escapeHtml(nodeId) + '"><div class="tree-row" role="treeitem" aria-level="' + (depth + 1) + '" aria-expanded="' + String(expandable && expanded) + '" data-rust-rendered-row="page" data-testid="wolai-sidebar-row" data-tree-testid="tree-node-open" data-node-id="' + escapeHtml(nodeId) + '"' + (parent ? ' data-parent-id="' + escapeHtml(parent) + '"' : '') + ' data-depth="' + depth + '" data-shell-mode="page" data-active="' + String(nodeId === activeId) + '"' + currentAttr + ' data-focused="false" data-page-openable="' + String(openable) + '" data-draggable="true" draggable="true" tabindex="-1">' + toggle + '<button type="button" class="tree-link" data-testid="tree-node-open" data-rust-action="open" data-node-id="' + escapeHtml(nodeId) + '" data-page-openable="' + String(openable) + '" title="' + escapeHtml(title) + '"><span class="tree-link-title" title="' + escapeHtml(title) + '">' + escapeHtml(title) + '</span></button><div class="tree-actions"><button type="button" class="tree-action" data-testid="tree-action-menu" data-rust-action="menu" data-node-id="' + escapeHtml(nodeId) + '" aria-label="更多操作"></button><button type="button" class="tree-action" data-testid="tree-action-create" data-rust-action="create" data-node-id="' + escapeHtml(nodeId) + '" aria-label="新建子页面">+</button></div></div>' + childHtml + '</li>';
}).join('');
}
@@ -2104,6 +2278,20 @@ const SIDEBAR_TREE_JS: &str = r##"
return { objectKind: objectKind, documentId: documentId, blockId: null, assetId: assetId };
}
function fileOwnerDocumentId(item, fallbackDocumentId, objectIdentity) {
var identity = objectIdentity && typeof objectIdentity === 'object' ? objectIdentity : fileObjectIdentity(item);
var owner = String(identity && identity.documentId || '').trim();
if (owner) return owner;
var assetId = fileAssetId(item);
var localPath = localFilePathFromAssetId(assetId);
if (localPath && localPath.indexOf('/') > 0) {
var parts = localPath.split('/');
var bundleName = parts[0] || '';
if (bundleName) return 'local-md:' + bundleName + '~2F' + bundleName + '.md';
}
return String(fallbackDocumentId || '').trim();
}
function objectIdentityAttr(identity) {
try {
return JSON.stringify(identity || {});
@@ -2148,6 +2336,7 @@ const SIDEBAR_TREE_JS: &str = r##"
var assetId = fileAssetId(item);
var relativePath = fileWorkspaceRelativePath(item);
var objectIdentity = fileObjectIdentity(item);
var ownerDocumentId = fileOwnerDocumentId(item, documentId, objectIdentity);
var iconKind = iconKindOf(item);
var title = isFileTreeProjectionPageRow(rowKind, assetId)
? fileTreePageTitle(rawTitle)
@@ -2166,7 +2355,7 @@ const SIDEBAR_TREE_JS: &str = r##"
var childHtml = expandable
? '<ul class="tree-children' + (expanded ? '' : ' tree-children--collapsed') + '">' + renderFileRows(nodeId, grouped, activeId, activeRowId) + '</ul>'
: '';
return '<li class="tree-node" data-node-id="' + escapeHtml(nodeId) + '"><div class="tree-row" role="treeitem" aria-level="' + (depth + 1) + '" aria-expanded="' + String(expandable && expanded) + '" data-rust-rendered-row="filetree" data-testid="' + testId + '" data-row-id="' + escapeHtml(rowId) + '" data-row-kind="' + escapeHtml(rowKind) + '" data-node-id="' + escapeHtml(nodeId) + '"' + (parent ? ' data-parent-id="' + escapeHtml(parent) + '"' : '') + ' data-document-id="' + escapeHtml(documentId) + '" data-doc-id="' + escapeHtml(documentId) + '" data-asset-id="' + escapeHtml(assetId) + '" data-local-relative-path="' + escapeHtml(relativePath) + '" data-object-identity="' + escapeHtml(objectIdentityAttr(objectIdentity)) + '" data-object-kind="' + escapeHtml(String(objectIdentity.objectKind || '')) + '" data-capabilities="' + escapeHtml(fileCapabilitiesAttr(item)) + '" data-shell-mode="filetree" data-selected="' + String(selected) + '" data-active="false" draggable="true">' + toggle + '<span class="tree-kind-badge" data-kind="' + escapeHtml(iconKind) + '" aria-hidden="true"></span><button type="button" class="tree-link" data-rust-action="open" data-row-id="' + escapeHtml(rowId) + '" data-document-id="' + escapeHtml(documentId) + '" data-asset-id="' + escapeHtml(assetId) + '" data-local-relative-path="' + escapeHtml(relativePath) + '"><span class="tree-link-title">' + escapeHtml(title) + '</span></button><div class="tree-actions">' + createAction + '<button type="button" class="tree-action" data-testid="filetree-action-menu" data-rust-action="menu" data-row-id="' + escapeHtml(rowId) + '" aria-label="更多操作"></button></div></div>' + childHtml + '</li>';
return '<li class="tree-node" data-node-id="' + escapeHtml(nodeId) + '"><div class="tree-row" role="treeitem" aria-level="' + (depth + 1) + '" aria-expanded="' + String(expandable && expanded) + '" data-rust-rendered-row="filetree" data-testid="' + testId + '" data-row-id="' + escapeHtml(rowId) + '" data-row-kind="' + escapeHtml(rowKind) + '" data-node-id="' + escapeHtml(nodeId) + '"' + (parent ? ' data-parent-id="' + escapeHtml(parent) + '"' : '') + ' data-document-id="' + escapeHtml(documentId) + '" data-doc-id="' + escapeHtml(documentId) + '" data-owner-document-id="' + escapeHtml(ownerDocumentId) + '" data-asset-id="' + escapeHtml(assetId) + '" data-local-relative-path="' + escapeHtml(relativePath) + '" data-object-identity="' + escapeHtml(objectIdentityAttr(objectIdentity)) + '" data-object-kind="' + escapeHtml(String(objectIdentity.objectKind || '')) + '" data-capabilities="' + escapeHtml(fileCapabilitiesAttr(item)) + '" data-shell-mode="filetree" data-selected="' + String(selected) + '" data-active="false" draggable="true">' + toggle + '<span class="tree-kind-badge" data-kind="' + escapeHtml(iconKind) + '" aria-hidden="true"></span><button type="button" class="tree-link" data-rust-action="open" data-row-id="' + escapeHtml(rowId) + '" data-document-id="' + escapeHtml(documentId) + '" data-owner-document-id="' + escapeHtml(ownerDocumentId) + '" data-asset-id="' + escapeHtml(assetId) + '" data-local-relative-path="' + escapeHtml(relativePath) + '" title="' + escapeHtml(title) + '"><span class="tree-link-title" title="' + escapeHtml(title) + '">' + escapeHtml(title) + '</span></button><div class="tree-actions">' + createAction + '<button type="button" class="tree-action" data-testid="filetree-action-menu" data-rust-action="menu" data-row-id="' + escapeHtml(rowId) + '" aria-label="更多操作"></button></div></div>' + childHtml + '</li>';
}).join('');
}
@@ -2401,7 +2590,8 @@ const SIDEBAR_TREE_JS: &str = r##"
documentId: detail.documentId || currentDocumentId() || '',
workspaceId: detail.workspaceId || resolveWorkspaceId(document.body) || '',
href: buildLocalFileOpenUrl(localFilePath, false),
officeUrl: localOfficeUrl
officeUrl: localOfficeUrl,
paneRole: detail.paneRole || 'primary'
});
if (!opened) window.open(localOfficeUrl, '_blank', 'noopener,noreferrer');
return true;
@@ -2498,7 +2688,8 @@ const SIDEBAR_TREE_JS: &str = r##"
href: String(input && input.href || buildLocalFileOpenUrl(relativePath, false) || '').trim(),
officeUrl: String(input && input.officeUrl || '').trim(),
documentId: String(input && input.documentId || currentDocumentId() || '').trim(),
workspaceId: String(input && input.workspaceId || resolveWorkspaceId(document.body) || '').trim()
workspaceId: String(input && input.workspaceId || resolveWorkspaceId(document.body) || '').trim(),
paneRole: String(input && input.paneRole || 'primary').trim() || 'primary'
});
}
@@ -2536,11 +2727,23 @@ const SIDEBAR_TREE_JS: &str = r##"
var forceEditMode = openTarget === 'edit-mode';
if (openTarget === 'side') {
if (typeof window.__mnoteDocumentPaneRuntime?.openResourceAsSideTarget === 'function') {
var sideLocalFilePath = localFilePathFromAssetId(assetId);
var sideRootUri = String(detail.rootUri || currentRootUri() || '').trim();
var sideFileName = String(detail.title || detail.fileName || (sideLocalFilePath ? sideLocalFilePath.split('/').pop() : '') || assetId || '').trim();
var sideKind = String(detail.iconKind || detail.assetType || fileTreeIconKindForFileName(sideFileName) || 'file').trim();
var sideHref = sideLocalFilePath ? buildLocalFileOpenUrl(sideLocalFilePath, false) : '';
var sideOfficeUrl = sideLocalFilePath ? buildLocalOnlyOfficeOpenUrl(sideLocalFilePath, sideFileName, String(detail.documentId || currentDocumentId() || '').trim(), assetId, 'view') : '';
if (sideOfficeUrl) sideKind = 'office';
void window.__mnoteDocumentPaneRuntime.openResourceAsSideTarget({
objectIdentity: String(detail.objectIdentity || detail.assetId || ''),
objectIdentity: sideLocalFilePath && sideRootUri ? 'resource:file:' + sideRootUri + ':' + sideLocalFilePath : String(detail.objectIdentity || detail.assetId || ''),
assetId: assetId,
title: String(detail.title || detail.fileName || assetId || ''),
kind: String(detail.iconKind || detail.assetType || 'file'),
title: sideFileName,
fileName: sideFileName,
kind: sideKind,
path: sideLocalFilePath,
rootUri: sideRootUri,
href: sideHref,
officeUrl: sideOfficeUrl,
documentId: String(detail.documentId || currentDocumentId() || ''),
workspaceId: String(detail.workspaceId || resolveWorkspaceId(document.body) || '')
});
@@ -2571,7 +2774,8 @@ const SIDEBAR_TREE_JS: &str = r##"
navigateToMindmapObject(String(detail && detail.documentId || currentDocumentId() || '').trim(), assetId, String(detail.workspaceId || '').trim());
return;
}
var localOfficeUrl = buildLocalOnlyOfficeOpenUrl(localFilePath, localFileName, String(detail && detail.documentId || currentDocumentId() || '').trim(), assetId, forceEditMode ? 'edit' : 'view');
var requestedOfficeMode = forceNewWindow || forceEditMode ? 'edit' : 'view';
var localOfficeUrl = buildLocalOnlyOfficeOpenUrl(localFilePath, localFileName, String(detail && detail.documentId || currentDocumentId() || '').trim(), assetId, requestedOfficeMode);
if (localOfficeUrl) {
if (!forceNewWindow && await openLocalResourceInActiveTab({
path: localFilePath,
@@ -2587,7 +2791,8 @@ const SIDEBAR_TREE_JS: &str = r##"
}
var localFileUrl = buildLocalFileOpenUrl(localFilePath, false);
if (localFileUrl) {
if (!forceNewWindow && await openLocalResourceInActiveTab({
var shouldOpenInNewWindow = forceNewWindow || shouldOpenLocalResourceInNewWindow(localFileName);
if (!shouldOpenInNewWindow && await openLocalResourceInActiveTab({
path: localFilePath,
title: localFileName,
kind: fileTreeIconKindForFileName(localFileName),
@@ -2932,6 +3137,11 @@ const SIDEBAR_TREE_JS: &str = r##"
return '';
}
function shouldOpenLocalResourceInNewWindow(fileName) {
var ext = attachmentExtensionFromFileName(fileName);
return ext === 'pdf';
}
function isLocalUploadedAsset(asset) {
var id = String(asset && asset.id || '').trim();
return String(asset && asset.sourceKind || '').trim() === 'local_folder' || id.indexOf('local:asset:') === 0;
@@ -3492,10 +3702,99 @@ const SIDEBAR_TREE_JS: &str = r##"
return true;
}
async function insertUploadedAssetIntoEditor(asset) {
var editorRoot = document.querySelector('.editor-surface .ProseMirror');
function resolveEditorUploadContext(detail) {
var root = null;
var selector = detail && detail.editorRootSelector ? String(detail.editorRootSelector) : '';
if (selector) {
try {
var selected = document.querySelector(selector);
if (selected instanceof HTMLElement) root = selected;
} catch (_) {}
}
if (!root && window.__mnoteIntendedSlashRoot instanceof HTMLElement && window.__mnoteIntendedSlashRoot.isConnected) {
root = window.__mnoteIntendedSlashRoot;
}
if (!root && window.__mnoteLastEditorUploadRoot instanceof HTMLElement && window.__mnoteLastEditorUploadRoot.isConnected) {
root = window.__mnoteLastEditorUploadRoot;
}
if (!root && document.activeElement instanceof Element) {
root = editorUploadRootFromElement(document.activeElement);
}
if (!root) {
var focused = document.querySelector(EDITOR_UPLOAD_ROOT_SELECTOR + ' .ProseMirror:focus-within');
root = editorUploadRootFromElement(focused);
}
if (!root) {
root = document.querySelector('[data-editor-host-kind="leptos_tiptap_island"][data-pane-role="primary"]');
}
var pane = root instanceof Element ? root.closest('.document-pane[data-pane-role]') : null;
var shell = root instanceof Element ? root.closest('.document-shell[data-document-id]') : null;
return {
root: root instanceof HTMLElement ? root : null,
documentId: String(
detail && detail.documentId
|| (root instanceof HTMLElement && root.getAttribute('data-document-id'))
|| (pane instanceof HTMLElement && pane.getAttribute('data-pane-document-id'))
|| (shell instanceof HTMLElement && shell.getAttribute('data-document-id'))
|| currentDocumentId()
|| ''
).trim(),
workspaceId: String(
detail && detail.workspaceId
|| (root instanceof HTMLElement && root.getAttribute('data-workspace-id'))
|| (pane instanceof HTMLElement && pane.getAttribute('data-pane-workspace-id'))
|| (shell instanceof HTMLElement && shell.getAttribute('data-workspace-id'))
|| resolveWorkspaceId(document.body)
|| ''
).trim()
};
}
function editorRootFromUploadOptions(options) {
if (options && options.editorRoot instanceof HTMLElement) return options.editorRoot;
if (window.__mnoteIntendedSlashRoot instanceof HTMLElement && window.__mnoteIntendedSlashRoot.isConnected) {
return window.__mnoteIntendedSlashRoot;
}
if (window.__mnoteLastEditorUploadRoot instanceof HTMLElement && window.__mnoteLastEditorUploadRoot.isConnected) {
return window.__mnoteLastEditorUploadRoot;
}
var focused = document.querySelector(EDITOR_UPLOAD_ROOT_SELECTOR + ' .ProseMirror:focus-within');
return editorUploadRootFromElement(focused);
}
async function fetchWithTimeout(input, init, timeoutMs, label) {
var controller = typeof AbortController === 'function' ? new AbortController() : null;
var timer = 0;
try {
if (controller) {
timer = window.setTimeout(function() {
controller.abort();
}, Math.max(1000, Number(timeoutMs) || 15000));
}
var nextInit = Object.assign({}, init || {});
if (controller) nextInit.signal = controller.signal;
return await fetch(input, nextInit);
} catch (error) {
if (error && error.name === 'AbortError') {
throw new Error((label || '') + '');
}
throw error;
} finally {
if (timer) window.clearTimeout(timer);
}
}
async function insertUploadedAssetIntoEditor(asset, targetRoot) {
var editorRoot = targetRoot instanceof HTMLElement
? targetRoot.querySelector('.editor-surface .ProseMirror')
: document.querySelector('.editor-surface .ProseMirror');
if (targetRoot instanceof HTMLElement) window.__mnoteLastEditorUploadRoot = targetRoot;
var editor = editorRoot && editorRoot.editor;
if (!editor || !editor.chain) return false;
if (!editor || !editor.chain) {
document.documentElement.setAttribute('data-mnote-last-upload-inserted', 'false');
document.documentElement.setAttribute('data-mnote-last-upload-insert-error', 'editor_unavailable');
return false;
}
var title = uploadedAssetTitle(asset);
var url = localAssetOpenUrl(asset, false) || uploadedAssetUrl(asset);
var type = uploadedAssetType(asset);
@@ -3525,28 +3824,42 @@ const SIDEBAR_TREE_JS: &str = r##"
mode: 'view'
}))
: href;
var inserted = editor.chain().focus().insertContent({
type: 'paragraph',
content: [{
type: 'text',
text: title,
marks: [{
type: 'link',
attrs: {
href: storedHref,
target: '_blank',
rel: 'noopener noreferrer nofollow',
class: uploadedAttachmentClass(asset)
}
var inserted = editor.chain().focus().insertContent([
{
type: 'paragraph',
content: [{
type: 'text',
text: title,
marks: [{
type: 'link',
attrs: {
href: storedHref,
target: '_blank',
rel: 'noopener noreferrer nofollow',
class: uploadedAttachmentClass(asset)
}
}]
}]
}]
}).run() === true;
},
{ type: 'paragraph' }
]).focus('end').run() === true;
if (inserted) {
document.documentElement.setAttribute('data-mnote-last-upload-inserted', 'true');
document.documentElement.setAttribute('data-mnote-last-upload-asset-id', assetId || '');
document.documentElement.removeAttribute('data-mnote-last-upload-insert-error');
} else {
document.documentElement.setAttribute('data-mnote-last-upload-inserted', 'false');
document.documentElement.setAttribute('data-mnote-last-upload-insert-error', 'insert_content_failed');
}
window.setTimeout(function() {
if (targetRoot instanceof HTMLElement) window.__mnoteLastEditorUploadRoot = targetRoot;
enhanceEditorAttachmentLinks();
var selector = assetId
? '.editor-surface .ProseMirror a[href*="' + cssEscape(assetId) + '"]'
: '.editor-surface .ProseMirror a';
var link = document.querySelector(selector);
var link = targetRoot instanceof HTMLElement
? targetRoot.querySelector(selector)
: document.querySelector(selector);
if (link instanceof HTMLElement) {
link.setAttribute('data-mnote-attachment-link', 'true');
if (assetId) link.setAttribute('data-asset-id', assetId);
@@ -3563,7 +3876,7 @@ const SIDEBAR_TREE_JS: &str = r##"
async function uploadFileToMediaAsset(file, plan, options) {
if (currentSourceKind() === 'local_folder') {
var rootUri = (new URLSearchParams(window.location.search).get('rootUri') || '').trim();
var rootUri = currentRootUri();
var documentId = String(plan && plan.targetDocumentId || currentDocumentId() || '').trim();
var hasFolderTarget = plan && Object.prototype.hasOwnProperty.call(plan, 'targetRelativePath');
if (!rootUri || (!documentId && !hasFolderTarget)) {
@@ -3575,17 +3888,17 @@ const SIDEBAR_TREE_JS: &str = r##"
if (documentId) localForm.append('documentId', documentId);
if (hasFolderTarget) localForm.append('targetRelativePath', String(plan.targetRelativePath || ''));
localForm.append('kind', file && String(file.type || '').indexOf('image/') === 0 ? 'image' : 'attachment');
var localResponse = await fetch('/api/local-folder/assets/upload', {
var localResponse = await fetchWithTimeout('/api/local-folder/assets/upload', {
method: 'POST',
credentials: 'include',
body: localForm
});
}, 15000, '');
var localPayload = await localResponse.json().catch(function() { return null; });
if (!localResponse.ok || !localPayload || !localPayload.asset) {
throw new Error(localPayload && localPayload.error ? localPayload.error : '');
}
if (options && options.insertIntoEditor) {
await insertUploadedAssetIntoEditor(localPayload.asset);
await insertUploadedAssetIntoEditor(localPayload.asset, editorRootFromUploadOptions(options));
}
void refreshLocalFolderSidebarSnapshot();
window.dispatchEvent(new CustomEvent('wolai:local-assets-changed', {
@@ -3598,18 +3911,18 @@ const SIDEBAR_TREE_JS: &str = r##"
form.append('workspaceId', plan.workspaceId);
form.append('documentId', plan.targetDocumentId);
if (plan.targetMindmapId) form.append('mindmapId', plan.targetMindmapId);
var response = await fetch('/api/media/upload', {
var response = await fetchWithTimeout('/api/media/upload', {
method: 'POST',
credentials: 'include',
body: form
});
}, 15000, '');
var payload = await response.json().catch(function() { return null; });
if (!response.ok || !payload || !payload.asset) {
throw new Error(payload && payload.error ? payload.error : '');
}
appendUploadedAssetRow(payload.asset, plan.targetDocumentId);
if (options && options.insertIntoEditor) {
await insertUploadedAssetIntoEditor(payload.asset);
await insertUploadedAssetIntoEditor(payload.asset, editorRootFromUploadOptions(options));
}
window.dispatchEvent(new CustomEvent('wolai:assets-changed', {
detail: { docId: plan.targetDocumentId, asset: payload.asset, assetIds: [payload.asset.id] }
@@ -3642,6 +3955,7 @@ const SIDEBAR_TREE_JS: &str = r##"
}
function openEditorUploadFilePicker(detail) {
var uploadContext = resolveEditorUploadContext(detail || {});
var input = document.createElement('input');
input.type = 'file';
input.multiple = detail && detail.multiple !== false;
@@ -3654,11 +3968,12 @@ const SIDEBAR_TREE_JS: &str = r##"
var files = Array.from(input.files || []);
input.remove();
void uploadFilesWithResolvedTarget(files, {
workspaceId: resolveWorkspaceId(document.body),
documentId: currentDocumentId(),
workspaceId: uploadContext.workspaceId || resolveWorkspaceId(document.body),
documentId: uploadContext.documentId || currentDocumentId(),
targetRowId: null
}, {
insertIntoEditor: detail && detail.insertIntoEditor !== false
insertIntoEditor: detail && detail.insertIntoEditor !== false,
editorRoot: uploadContext.root
});
}, { once: true });
input.click();
@@ -3695,7 +4010,8 @@ const SIDEBAR_TREE_JS: &str = r##"
documentId: currentDocumentId(),
targetRowId: null
}, {
insertIntoEditor: true
insertIntoEditor: true,
editorRoot: editorUploadRootFromElement(editorTarget)
});
}, true);
@@ -8361,14 +8677,47 @@ const SIDEBAR_TREE_JS: &str = r##"
return Boolean(inferOnlyOfficeFileType(fileName, ''));
}
function editorAttachmentPaneContext(link) {
var pane = link && typeof link.closest === 'function' ? link.closest('[data-document-pane="true"]') : null;
var roleHost = link && typeof link.closest === 'function' ? link.closest('[data-pane-role]') : null;
var paneRole = (
pane instanceof HTMLElement && pane.getAttribute('data-pane-role') === 'secondary'
) || (
roleHost instanceof HTMLElement && roleHost.getAttribute('data-pane-role') === 'secondary'
) ? 'secondary' : 'primary';
var paneDocumentId = '';
var paneWorkspaceId = '';
if (pane instanceof HTMLElement) {
paneDocumentId = (pane.getAttribute('data-pane-document-id') || '').trim();
paneWorkspaceId = (pane.getAttribute('data-pane-workspace-id') || '').trim();
var shell = pane.querySelector('.document-shell[data-document-id]');
if (!paneDocumentId && shell instanceof HTMLElement) paneDocumentId = (shell.getAttribute('data-document-id') || '').trim();
if (!paneWorkspaceId && shell instanceof HTMLElement) paneWorkspaceId = (shell.getAttribute('data-workspace-id') || '').trim();
}
if (roleHost instanceof HTMLElement) {
if (!paneDocumentId) paneDocumentId = (roleHost.getAttribute('data-document-id') || '').trim();
if (!paneWorkspaceId) paneWorkspaceId = (roleHost.getAttribute('data-workspace-id') || '').trim();
var roleHostShell = roleHost.matches('.document-shell') ? roleHost : roleHost.querySelector?.('.document-shell[data-document-id]');
if (!paneDocumentId && roleHostShell instanceof HTMLElement) paneDocumentId = (roleHostShell.getAttribute('data-document-id') || '').trim();
if (!paneWorkspaceId && roleHostShell instanceof HTMLElement) paneWorkspaceId = (roleHostShell.getAttribute('data-workspace-id') || '').trim();
}
return {
paneRole: paneRole,
documentId: paneDocumentId || currentDocumentId() || '',
workspaceId: paneWorkspaceId || resolveWorkspaceId(document.body) || ''
};
}
function detailFromEditorAttachmentLink(link) {
var rawHref = link instanceof HTMLAnchorElement ? link.href : '';
var params = attachmentQueryParams(rawHref);
var paneContext = editorAttachmentPaneContext(link);
var localFilePath = localFileOpenPathFromHref(rawHref);
var fileName = params.get('fileName') || fileNameFromPath(localFilePath) || (link ? link.textContent : '') || '';
var fileType = params.get('fileType') || inferOnlyOfficeFileType(fileName, '');
var assetId = params.get('assetId') || (link ? link.getAttribute('data-asset-id') : '') || (localFilePath ? 'local-file:' + localFilePath : '');
var fileUrl = params.get('fileUrl') || '';
var documentId = params.get('documentId') || paneContext.documentId || '';
var href = rawHref;
if (!isOnlyOfficeAttachmentHref(rawHref) && fileType) {
fileUrl = rawHref;
@@ -8377,7 +8726,7 @@ const SIDEBAR_TREE_JS: &str = r##"
fileName: fileName,
fileType: fileType,
assetId: assetId,
documentId: currentDocumentId() || '',
documentId: documentId,
userId: '',
mode: 'view'
});
@@ -8391,8 +8740,9 @@ const SIDEBAR_TREE_JS: &str = r##"
title: fileName,
fileType: fileType,
assetId: assetId,
documentId: params.get('documentId') || currentDocumentId() || '',
workspaceId: resolveWorkspaceId(document.body),
documentId: documentId,
workspaceId: paneContext.workspaceId,
paneRole: paneContext.paneRole,
fileSize: (link ? link.getAttribute('data-file-size') : '') || ''
};
}
@@ -8402,13 +8752,17 @@ const SIDEBAR_TREE_JS: &str = r##"
var href = link.getAttribute('href') || '';
var params = attachmentQueryParams(href);
var localFilePath = localFileOpenPathFromHref(href);
var paneContext = editorAttachmentPaneContext(link);
var fileName = params.get('fileName') || fileNameFromPath(localFilePath) || link.textContent || '';
var className = link.getAttribute('class') || '';
var shouldEnhance = isOnlyOfficeAttachmentHref(href)
|| className.indexOf('mnote-uploaded-attachment-row') >= 0
|| isOfficeFileName(fileName)
|| Boolean(localFilePath && (isPdfAttachmentFileName(fileName) || isCodeAttachmentFileName(fileName)));
|| Boolean(localFilePath);
if (!shouldEnhance) return;
if (localFilePath && !isOnlyOfficeAttachmentHref(href)) {
return;
}
link.setAttribute('data-mnote-attachment-link', 'true');
var assetId = params.get('assetId') || link.getAttribute('data-asset-id') || (localFilePath ? 'local-file:' + localFilePath : '');
if (assetId) link.setAttribute('data-asset-id', assetId);
@@ -8418,7 +8772,7 @@ const SIDEBAR_TREE_JS: &str = r##"
fileName: fileName || '',
fileType: params.get('fileType') || inferOnlyOfficeFileType(fileName, ''),
assetId: assetId,
documentId: params.get('documentId') || currentDocumentId() || '',
documentId: params.get('documentId') || paneContext.documentId || '',
userId: params.get('userId') || '',
mode: params.get('mode') || 'view'
}));
@@ -8435,6 +8789,10 @@ const SIDEBAR_TREE_JS: &str = r##"
document.querySelectorAll('.editor-surface .ProseMirror a[href]').forEach(enhanceEditorAttachmentLink);
void healLegacyOfficeAttachmentParagraphs();
}
window.__mnoteEnhanceEditorAttachmentLinks = function() {
observeEditorAttachmentRoots();
enhanceEditorAttachmentLinks();
};
function ensureAttachmentActions() {
var existing = document.querySelector('[data-testid="mnote-attachment-actions"]');
@@ -8486,7 +8844,8 @@ const SIDEBAR_TREE_JS: &str = r##"
assetId: detail.assetId,
documentId: detail.documentId || currentDocumentId() || '',
workspaceId: detail.workspaceId || resolveWorkspaceId(document.body) || '',
href: buildLocalFileOpenUrl(localFilePath, false)
href: buildLocalFileOpenUrl(localFilePath, false),
paneRole: detail.paneRole || 'primary'
}).then(function(opened) {
if (!opened) window.open(buildLocalFileOpenUrl(localFilePath, false) || detail.href, '_blank', 'noopener,noreferrer');
});
@@ -8510,7 +8869,8 @@ const SIDEBAR_TREE_JS: &str = r##"
kind: 'office',
officeUrl: detail.href,
documentId: detail.documentId || '',
workspaceId: detail.workspaceId || ''
workspaceId: detail.workspaceId || '',
paneRole: detail.paneRole || 'primary'
});
return;
}
@@ -8582,7 +8942,8 @@ const SIDEBAR_TREE_JS: &str = r##"
kind: 'office',
officeUrl: href,
documentId: detail.documentId || '',
workspaceId: detail.workspaceId || ''
workspaceId: detail.workspaceId || '',
paneRole: detail.paneRole || 'primary'
});
if (!didOpenEditTab && href) window.open(href, '_blank', 'noopener,noreferrer');
return didOpenEditTab;
@@ -8737,11 +9098,51 @@ const SIDEBAR_TREE_JS: &str = r##"
}
enhanceEditorAttachmentLinks();
var attachmentObserver = new MutationObserver(function() { enhanceEditorAttachmentLinks(); });
attachmentObserver.observe(document.documentElement, { childList: true, subtree: true });
var attachmentEnhanceFrame = 0;
var attachmentEditorObserver = null;
var attachmentObservedEditors = typeof WeakSet === 'function' ? new WeakSet() : null;
function scheduleEditorAttachmentEnhance() {
if (attachmentEnhanceFrame) return;
attachmentEnhanceFrame = window.requestAnimationFrame(function() {
attachmentEnhanceFrame = 0;
enhanceEditorAttachmentLinks();
observeEditorAttachmentRoots();
});
}
function addedNodeMayContainEditorAttachmentLink(node) {
return node instanceof HTMLElement && (
node.matches('.editor-surface .ProseMirror, .editor-surface .ProseMirror a[href], .editor-surface .ProseMirror p, .editor-surface .ProseMirror span, .editor-surface .ProseMirror div')
|| node.querySelector?.('.editor-surface .ProseMirror, .editor-surface .ProseMirror a[href]')
);
}
function observeEditorAttachmentRoots() {
if (!attachmentEditorObserver) return;
document.querySelectorAll('.editor-surface .ProseMirror').forEach(function(editor) {
if (!(editor instanceof HTMLElement)) return;
if (attachmentObservedEditors && attachmentObservedEditors.has(editor)) return;
if (attachmentObservedEditors) attachmentObservedEditors.add(editor);
attachmentEditorObserver.observe(editor, { childList: true, subtree: true });
});
}
attachmentEditorObserver = new MutationObserver(function(records) {
var shouldEnhance = Array.isArray(records) && records.some(function(record) {
if (!record || record.type !== 'childList') return false;
return Array.from(record.addedNodes || []).some(function(node) {
return addedNodeMayContainEditorAttachmentLink(node);
});
});
if (!shouldEnhance) return;
scheduleEditorAttachmentEnhance();
});
attachmentEditorObserver.observe(document.documentElement, { childList: true, subtree: true });
observeEditorAttachmentRoots();
window.addEventListener('mnote:editor-attachment-links-changed', function() {
window.__mnoteEnhanceEditorAttachmentLinks();
scheduleEditorAttachmentEnhance();
});
function interceptEditorAttachmentLink(event) {
var editorAttachmentLink = closestAction(event.target, '.editor-surface .ProseMirror a[data-mnote-attachment-link="true"], .editor-surface .ProseMirror a.mnote-uploaded-attachment-row');
var editorAttachmentLink = closestAction(event.target, '.editor-surface .ProseMirror a[data-mnote-attachment-link="true"], .editor-surface .ProseMirror a.mnote-uploaded-attachment-row, .editor-surface .ProseMirror a[href*="/api/local-folder/files/open"]');
if (!(editorAttachmentLink instanceof HTMLAnchorElement)) return;
event.preventDefault();
event.stopPropagation();
@@ -8750,7 +9151,7 @@ const SIDEBAR_TREE_JS: &str = r##"
}
function suppressEditorAttachmentLinkDefault(event) {
var editorAttachmentLink = closestAction(event.target, '.editor-surface .ProseMirror a[data-mnote-attachment-link="true"], .editor-surface .ProseMirror a.mnote-uploaded-attachment-row');
var editorAttachmentLink = closestAction(event.target, '.editor-surface .ProseMirror a[data-mnote-attachment-link="true"], .editor-surface .ProseMirror a.mnote-uploaded-attachment-row, .editor-surface .ProseMirror a[href*="/api/local-folder/files/open"]');
if (!(editorAttachmentLink instanceof HTMLAnchorElement)) return;
event.preventDefault();
event.stopPropagation();
@@ -8761,7 +9162,7 @@ const SIDEBAR_TREE_JS: &str = r##"
window.addEventListener('click', interceptEditorAttachmentLink, true);
document.addEventListener('mouseover', function(event) {
var link = closestAction(event.target, '.editor-surface .ProseMirror a[data-mnote-attachment-link="true"], .editor-surface .ProseMirror a.mnote-uploaded-attachment-row');
var link = closestAction(event.target, '.editor-surface .ProseMirror a[data-mnote-attachment-link="true"], .editor-surface .ProseMirror a.mnote-uploaded-attachment-row, .editor-surface .ProseMirror a[href*="/api/local-folder/files/open"]');
if (!(link instanceof HTMLAnchorElement)) return;
if (attachmentActionsHideTimer) window.clearTimeout(attachmentActionsHideTimer);
enhanceEditorAttachmentLink(link);
@@ -8769,7 +9170,7 @@ const SIDEBAR_TREE_JS: &str = r##"
});
document.addEventListener('mouseout', function(event) {
var link = closestAction(event.target, '.editor-surface .ProseMirror a[data-mnote-attachment-link="true"], .editor-surface .ProseMirror a.mnote-uploaded-attachment-row');
var link = closestAction(event.target, '.editor-surface .ProseMirror a[data-mnote-attachment-link="true"], .editor-surface .ProseMirror a.mnote-uploaded-attachment-row, .editor-surface .ProseMirror a[href*="/api/local-folder/files/open"]');
if (!(link instanceof HTMLAnchorElement)) return;
var next = event.relatedTarget;
var actions = document.querySelector('[data-testid="mnote-attachment-actions"]');
@@ -8807,7 +9208,7 @@ const SIDEBAR_TREE_JS: &str = r##"
return;
}
var editorAttachmentLink = closestAction(e.target, '.editor-surface .ProseMirror a[data-mnote-attachment-link="true"], .editor-surface .ProseMirror a.mnote-uploaded-attachment-row');
var editorAttachmentLink = closestAction(e.target, '.editor-surface .ProseMirror a[data-mnote-attachment-link="true"], .editor-surface .ProseMirror a.mnote-uploaded-attachment-row, .editor-surface .ProseMirror a[href*="/api/local-folder/files/open"]');
if (editorAttachmentLink instanceof HTMLAnchorElement) {
e.preventDefault();
openEditorAttachmentLink(editorAttachmentLink);
@@ -9170,6 +9571,7 @@ const SIDEBAR_TREE_JS: &str = r##"
var rowId = fileRow.getAttribute('data-row-id') || '';
var rowKind = fileRow.getAttribute('data-row-kind') || '';
var documentId = fileRow.getAttribute('data-document-id') || fileRow.getAttribute('data-doc-id') || '';
var ownerDocumentId = fileRow.getAttribute('data-owner-document-id') || documentId;
var assetId = fileRow.getAttribute('data-asset-id') || '';
var assetType = '';
var kindBadge = fileRow.querySelector('.tree-kind-badge');
@@ -9205,14 +9607,14 @@ const SIDEBAR_TREE_JS: &str = r##"
e.preventDefault();
selectSidebarFileTreeRow(fileRow, { ctrlKey: e.ctrlKey, metaKey: e.metaKey, shiftKey: e.shiftKey });
var objectIdentity = readFileTreeObjectIdentity(fileRow);
dispatchSidebarEvent('tree.filetree.open', { rowId: rowId, rowKind: rowKind, documentId: documentId || null, assetId: assetId || null, assetType: assetType || null, objectIdentity: objectIdentity, workspaceId: resolveWorkspaceId(fileRow) });
dispatchSidebarEvent('tree.filetree.open', { rowId: rowId, rowKind: rowKind, documentId: ownerDocumentId || null, assetId: assetId || null, assetType: assetType || null, objectIdentity: objectIdentity, workspaceId: resolveWorkspaceId(fileRow) });
if (e.shiftKey || e.ctrlKey || e.metaKey) {
return;
}
if ((rowKind === 'document' || rowKind === 'index' || rowKind === 'markdown') && documentId) {
navigateToDocument(documentId, resolveWorkspaceId(fileRow), { treeView: 'filetree' });
} else if (assetId) {
dispatchSidebarEvent('tree.asset.open', { rowId: rowId, rowKind: rowKind, documentId: documentId || null, assetId: assetId, assetType: assetType || null, objectIdentity: objectIdentity, workspaceId: resolveWorkspaceId(fileRow) });
dispatchSidebarEvent('tree.asset.open', { rowId: rowId, rowKind: rowKind, documentId: ownerDocumentId || null, assetId: assetId, assetType: assetType || null, objectIdentity: objectIdentity, workspaceId: resolveWorkspaceId(fileRow), openTarget: e.shiftKey || e.ctrlKey || e.metaKey ? 'new-window' : 'active-tab' });
}
return;
}
@@ -9264,7 +9666,7 @@ const SIDEBAR_TREE_JS: &str = r##"
});
document.addEventListener('contextmenu', function(event) {
var editorAttachmentLink = closestAction(event.target, '.editor-surface .ProseMirror a[data-mnote-attachment-link="true"], .editor-surface .ProseMirror a.mnote-uploaded-attachment-row');
var editorAttachmentLink = closestAction(event.target, '.editor-surface .ProseMirror a[data-mnote-attachment-link="true"], .editor-surface .ProseMirror a.mnote-uploaded-attachment-row, .editor-surface .ProseMirror a[href*="/api/local-folder/files/open"]');
if (editorAttachmentLink instanceof HTMLAnchorElement) {
event.preventDefault();
event.stopPropagation();
@@ -9447,6 +9849,7 @@ const SIDEBAR_TREE_JS: &str = r##"
updatePageSettingsTriggerState();
updatePageAiTriggerState();
ensureHistorySnapshotsSeeded();
installWorkspaceSidebarResizer();
}
window.__mnoteRecordPageHistorySnapshot = recordPageHistorySnapshot;
@@ -10143,6 +10546,7 @@ pub fn PageLayout(
<script inner_html={SIDEBAR_TREE_JS.to_string()}></script>
<script id="mnote-tree-live-controller" inner_html={TREE_LIVE_CONTROLLER_JS.to_string()}></script>
</aside>
<div class="mnote-sidebar-resizer" data-mnote-sidebar-resizer="true" role="separator" aria-orientation="vertical" aria-label="调整侧栏宽度"></div>
<div class="mnote-main">
<header class="wolai-topbar" data-testid="wolai-topbar">
<div class="wolai-topbar-left">
@@ -10180,6 +10584,27 @@ pub fn PageLayout(
mod tests {
use super::{SIDEBAR_TREE_JS, TREE_LIVE_CONTROLLER_JS};
fn js_function_body(source: &str, name: &str) -> String {
let marker = format!("function {name}(");
let start = source.find(&marker).expect("js function exists");
let rest = &source[start..];
let brace = rest.find('{').expect("js function body starts");
let mut depth = 0usize;
let mut end = None;
for (offset, ch) in rest[brace..].char_indices() {
if ch == '{' {
depth += 1;
} else if ch == '}' {
depth -= 1;
if depth == 0 {
end = Some(brace + offset + 1);
break;
}
}
}
rest[..end.expect("js function body ends")].to_string()
}
#[test]
fn sidebar_tree_runtime_handles_navigation_drag_and_filetree_actions() {
assert!(SIDEBAR_TREE_JS.contains("mnoteNavigationInFlight"));
@@ -10255,6 +10680,10 @@ mod tests {
assert!(SIDEBAR_TREE_JS.contains("switchToCloudWorkspace"));
assert!(SIDEBAR_TREE_JS.contains("mnote-switch-cloud-workspace"));
assert!(SIDEBAR_TREE_JS.contains("mnote-recent-local-root"));
assert!(SIDEBAR_TREE_JS.contains("mnote-local-folder-authorized-roots"));
assert!(SIDEBAR_TREE_JS.contains("mnote-local-folder-authorized-root"));
assert!(SIDEBAR_TREE_JS.contains("已授权文件夹"));
assert!(SIDEBAR_TREE_JS.contains("fetch('/api/user/access-policy'"));
}
#[test]
@@ -10412,8 +10841,23 @@ mod tests {
#[test]
fn sidebar_upload_runtime_routes_local_markdown_assets_to_local_folder() {
let current_document_function = js_function_body(SIDEBAR_TREE_JS, "currentDocumentId");
let upload_function = js_function_body(SIDEBAR_TREE_JS, "uploadFileToMediaAsset");
assert!(SIDEBAR_TREE_JS.contains("currentSourceKind() === 'local_folder'"));
assert!(SIDEBAR_TREE_JS.contains("/api/local-folder/assets/upload"));
assert!(
upload_function.contains("var rootUri = currentRootUri();"),
"本地上传必须复用 currentRootUri(),否则 / 页面由 body dataset 提供 rootUri 时 .md 上传会失败"
);
assert!(
current_document_function.contains("params.get('pageId')"),
"SQLite/local-first 根入口可能通过 pageId 或 DOM 暴露当前页面,不能只解析 /documents/:id"
);
assert!(
current_document_function.contains("data-pane-document-id")
&& current_document_function.contains("data-document-id"),
"主编辑器上传应能从当前文档 DOM 回退解析 documentId"
);
assert!(SIDEBAR_TREE_JS.contains("localForm.append('rootUri', rootUri)"));
assert!(SIDEBAR_TREE_JS.contains("localForm.append('documentId', documentId)"));
assert!(SIDEBAR_TREE_JS.contains("isLocalUploadedAsset(asset)"));
@@ -10422,6 +10866,11 @@ mod tests {
assert!(SIDEBAR_TREE_JS.contains("fileTreeIconKindForFileName(title)"));
assert!(SIDEBAR_TREE_JS.contains("refreshLocalFolderSidebarSnapshot"));
assert!(SIDEBAR_TREE_JS.contains("wolai:local-assets-changed"));
assert!(SIDEBAR_TREE_JS.contains("function resolveEditorUploadContext"));
assert!(SIDEBAR_TREE_JS.contains("__mnoteLastEditorUploadRoot"));
assert!(SIDEBAR_TREE_JS.contains("data-pane-role=\"primary\""));
assert!(SIDEBAR_TREE_JS.contains("insertUploadedAssetIntoEditor(localPayload.asset, editorRootFromUploadOptions(options))"));
assert!(SIDEBAR_TREE_JS.contains("editorRoot: uploadContext.root"));
}
#[test]
@@ -10444,6 +10893,47 @@ mod tests {
assert!(SIDEBAR_TREE_JS.contains("isLocalAsset ? onlyOfficeUrl"));
}
#[test]
fn sidebar_local_folder_authorized_root_normalizes_plain_path_root_uri() {
assert!(SIDEBAR_TREE_JS.contains("function normalizeGrantedLocalFolderRootUri(grant)"));
assert!(SIDEBAR_TREE_JS.contains("if (rootPath) return pathToFileRootUri(rootPath);"));
assert!(SIDEBAR_TREE_JS.contains("if (rootUri) return pathToFileRootUri(rootUri);"));
assert!(
SIDEBAR_TREE_JS.contains("var rootUri = normalizeGrantedLocalFolderRootUri(grant);")
);
assert!(SIDEBAR_TREE_JS.contains("function isDefaultWorkspaceAutoGrant(grant)"));
assert!(SIDEBAR_TREE_JS.contains("if (isDefaultWorkspaceAutoGrant(grant)) return;"));
assert!(SIDEBAR_TREE_JS.contains("function recentLocalRootLabel(rootUri)"));
assert!(SIDEBAR_TREE_JS.contains("button.textContent = recentLocalRootLabel(rootUri);"));
assert!(
!SIDEBAR_TREE_JS.contains("button.textContent = rootUri.replace(/^file:\\/\\//, '')")
);
}
#[test]
fn sidebar_runtime_supports_resizable_filetree_and_hover_titles() {
assert!(SIDEBAR_TREE_JS.contains("function installWorkspaceSidebarResizer()"));
assert!(SIDEBAR_TREE_JS.contains("data-mnote-sidebar-resizer"));
assert!(SIDEBAR_TREE_JS.contains("mnote.workspace.sidebarWidth.v1"));
assert!(SIDEBAR_TREE_JS.contains("shell.style.setProperty('--mnote-sidebar-width'"));
assert!(SIDEBAR_TREE_JS.contains("installWorkspaceSidebarResizer();"));
assert!(SIDEBAR_TREE_JS.contains(r#"" title="' + escapeHtml(title) + '""#));
assert!(SIDEBAR_TREE_JS
.contains(r#"class="tree-link-title" title="' + escapeHtml(title) + '""#));
}
#[test]
fn sidebar_runtime_opens_local_pdf_assets_in_browser_tab_by_default() {
assert!(SIDEBAR_TREE_JS.contains("function shouldOpenLocalResourceInNewWindow(fileName)"));
assert!(SIDEBAR_TREE_JS.contains("return ext === 'pdf';"));
assert!(SIDEBAR_TREE_JS.contains("var shouldOpenInNewWindow = forceNewWindow || shouldOpenLocalResourceInNewWindow(localFileName);"));
assert!(SIDEBAR_TREE_JS
.contains("if (!shouldOpenInNewWindow && await openLocalResourceInActiveTab({"));
assert!(SIDEBAR_TREE_JS.contains(
"openTarget: e.shiftKey || e.ctrlKey || e.metaKey ? 'new-window' : 'active-tab'"
));
}
#[test]
fn sidebar_tree_runtime_contains_dev_hot_reload_client() {
assert!(SIDEBAR_TREE_JS.contains("installMnoteDevHotReload"));
@@ -10628,6 +11118,29 @@ mod tests {
assert!(table_loop.contains("/api/tables/"));
}
#[test]
fn sidebar_filetree_asset_open_uses_owner_document_id() {
assert!(
SIDEBAR_TREE_JS.contains(
"var ownerDocumentId = fileRow.getAttribute('data-owner-document-id') || documentId;"
),
"打开资源行时必须优先使用资源 owner document,不能用当前页面 documentId"
);
assert!(
SIDEBAR_TREE_JS.contains("documentId: ownerDocumentId || null"),
"tree.asset.open detail 应携带资源 owner document"
);
assert!(
SIDEBAR_TREE_JS.contains("data-owner-document-id=\"' + escapeHtml(ownerDocumentId)"),
"SSR filetree 行应输出 data-owner-document-id"
);
assert!(
SIDEBAR_TREE_JS
.contains("return 'local-md:' + bundleName + '~2F' + bundleName + '.md';"),
"local_folder bundle 资源应从 local-file 路径推导 owner markdown document"
);
}
#[test]
fn sidebar_tree_runtime_keeps_pdf_and_code_assets_out_of_onlyoffice() {
assert!(SIDEBAR_TREE_JS.contains("function inferOnlyOfficeFileType"));
+254 -7
View File
@@ -232,7 +232,8 @@ a:hover {
}
.mnote-shell[data-mnote-sidebar-collapsed="true"] .mnote-sidebar,
.mnote-shell[data-mnote-sidebar-collapsed="true"] .wolai-sidebar {
.mnote-shell[data-mnote-sidebar-collapsed="true"] .wolai-sidebar,
.mnote-shell[data-mnote-sidebar-collapsed="true"] .mnote-sidebar-resizer {
display: none;
}
@@ -240,13 +241,38 @@ a:hover {
--wolai-bg-sidebar: #F7F7F6;
--wolai-bg-selected: #F8E6E7;
--wolai-accent-red: #E0525B;
--mnote-sidebar-width: 288px;
}
.wolai-sidebar {
width: 288px;
width: var(--mnote-sidebar-width);
background: var(--wolai-bg-sidebar);
}
.mnote-sidebar-resizer {
display: block;
width: 6px;
min-height: 100vh;
cursor: col-resize;
position: relative;
flex: 0 0 auto;
}
.mnote-sidebar-resizer::before {
content: "";
position: absolute;
left: 2px;
top: 0;
bottom: 0;
width: 2px;
background: rgba(27, 28, 28, 0.08);
}
.mnote-sidebar-resizer:hover::before,
html[data-mnote-sidebar-resizing="true"] .mnote-sidebar-resizer::before {
background: rgba(37, 99, 235, 0.42);
}
.wolai-sidebar-header {
height: 52px;
gap: 10px;
@@ -345,6 +371,33 @@ a:hover {
color: #1D4ED8;
}
.mnote-local-folder-dialog__recent {
display: grid;
gap: 6px;
min-width: 0;
}
.mnote-local-folder-dialog__recent button {
min-width: 0;
max-width: 100%;
border: 1px solid rgba(27, 28, 28, 0.12);
border-radius: 6px;
background: #fff;
padding: 7px 9px;
color: var(--wolai-text-primary);
font-size: 13px;
line-height: 1.4;
text-align: left;
cursor: pointer;
white-space: normal;
overflow-wrap: anywhere;
word-break: break-word;
}
.mnote-local-folder-dialog__recent button:hover {
background: var(--wolai-bg-hover);
}
.mnote-account-menu{inset-inline-start:auto;right:8px;top:calc(100% - 10px);width:194px}.mnote-account-menu__item{display:flex;align-items:center;gap:10px}.mnote-account-menu__logout{color:#C2410C}.mnote-account-menu__logout:hover{background:#FFF7ED}.mnote-account-menu__error{padding:6px 8px 2px;color:#B91C1C;font-size:12px}.mnote-profile-dialog{position:fixed;inset:0;z-index:1200;display:grid;place-items:center}.mnote-profile-dialog__backdrop{position:absolute;inset:0;background:rgba(15,23,42,.22)}.mnote-profile-dialog__panel{position:relative;width:min(440px,calc(100vw - 32px));max-height:calc(100vh - 48px);overflow:auto;border:1px solid var(--wolai-border);border-radius:8px;background:#fff;box-shadow:0 18px 54px rgba(15,23,42,.18);padding:20px}.mnote-profile-dialog__header,.mnote-profile-dialog__identity,.mnote-profile-dialog__list>div,.mnote-profile-dialog__list dd{display:flex;align-items:center}.mnote-profile-dialog__header{justify-content:space-between;margin-bottom:18px}.mnote-profile-dialog__eyebrow,.mnote-profile-dialog__identity span,.mnote-profile-dialog__list dt{color:var(--wolai-text-secondary);font-size:13px}.mnote-profile-dialog__header h2{font-size:20px}.mnote-profile-dialog__close,.mnote-profile-dialog__list button{border:0;border-radius:6px;background:transparent;color:var(--wolai-text-secondary);cursor:pointer}.mnote-profile-dialog__close{width:32px;height:32px}.mnote-profile-dialog__identity{gap:12px;padding:12px;border-radius:8px;background:var(--wolai-bg-sidebar)}.mnote-profile-dialog__avatar{width:36px;height:36px;display:grid;place-items:center;border-radius:6px;background:#D6545D;color:#fff;font-weight:650}.mnote-profile-dialog__list{margin-top:16px}.mnote-profile-dialog__list>div{justify-content:space-between;gap:16px;padding:11px 0;border-bottom:1px solid var(--wolai-border)}.mnote-profile-dialog__list dd{min-width:0;gap:8px;max-width:280px;font-size:13px;text-align:right;overflow-wrap:anywhere}.mnote-profile-dialog__list code{font-family:"JetBrains Mono","SFMono-Regular",Consolas,monospace;font-size:12px;white-space:normal}
.wolai-quick-actions {
@@ -792,10 +845,15 @@ a:hover {
.mnote-content {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 0;
}
.mnote-content:has(.document-workspace) {
overflow: hidden;
}
/* ===== 首页 ===== */
.mnote-home {
max-width: 720px;
@@ -1311,7 +1369,7 @@ body {
.mnote-sidebar,
.wolai-sidebar {
width: 248px;
width: var(--mnote-sidebar-width, 248px);
background: var(--atelier-sidebar);
border-right: 0;
box-shadow: none;
@@ -1798,7 +1856,8 @@ body {
font-size: 18px;
}
.document-shell .editor-surface .ProseMirror a.mnote-uploaded-attachment-row {
.document-shell .editor-surface .ProseMirror a.mnote-uploaded-attachment-row,
.document-shell .editor-surface .ProseMirror a[href*="/api/local-folder/files/open"] {
display: inline-flex !important;
align-items: center !important;
gap: 7px !important;
@@ -1812,7 +1871,8 @@ body {
text-decoration: none !important;
}
.document-shell .editor-surface .ProseMirror a.mnote-uploaded-attachment-row::before {
.document-shell .editor-surface .ProseMirror a.mnote-uploaded-attachment-row::before,
.document-shell .editor-surface .ProseMirror a[href*="/api/local-folder/files/open"]::before {
content: "" !important;
display: inline-block !important;
width: 18px !important;
@@ -1900,7 +1960,7 @@ body {
background: var(--atelier-document);
}
.mnote-admin-policy-page{width:min(1080px,calc(100vw - 64px));margin:40px auto 64px}.mnote-admin-policy-modal{position:fixed;inset:0;z-index:1200;display:grid;place-items:center}.mnote-admin-policy-modal__backdrop{position:absolute;inset:0;background:rgba(15,23,42,.22)}.mnote-admin-policy-modal__panel{position:relative;width:min(920px,calc(100vw - 32px));max-height:calc(100vh - 48px);overflow:auto;border:1px solid var(--wolai-border);border-radius:8px;background:#fff;box-shadow:0 18px 54px rgba(15,23,42,.18);padding:20px}.mnote-admin-policy-modal__close{position:absolute;right:14px;top:14px;width:32px;height:32px;border:0;border-radius:6px;background:transparent;color:var(--wolai-text-secondary);cursor:pointer}.mnote-admin-policy-dialog__content{padding-right:28px}.mnote-admin-policy-header{margin-bottom:20px}.mnote-admin-policy-eyebrow{color:var(--wolai-text-secondary);font-size:13px}.mnote-admin-policy-header h1,.mnote-admin-policy-header h2{font-size:22px;font-weight:650}.mnote-admin-policy-header p,.mnote-admin-policy-note{color:var(--wolai-text-secondary);font-size:14px}.mnote-admin-policy-summary,.mnote-admin-policy-panel,.mnote-admin-policy-form{border:1px solid var(--wolai-border);border-radius:8px;background:#fff}.mnote-admin-policy-summary{display:grid;grid-template-columns:1fr 2fr;gap:16px;padding:16px;margin-bottom:16px}.mnote-admin-policy-summary-label{display:block;color:var(--wolai-text-secondary);font-size:12px}.mnote-admin-policy-summary code,.mnote-admin-policy-json{white-space:pre-wrap;overflow-wrap:anywhere;font-family:"JetBrains Mono","SFMono-Regular",Consolas,monospace;font-size:12px}.mnote-admin-policy-panel,.mnote-admin-policy-form{padding:16px;margin-bottom:16px}.mnote-admin-policy-panel-header{display:flex;justify-content:space-between;gap:12px;margin-bottom:12px}.mnote-admin-policy-grid{display:grid;grid-template-columns:minmax(260px,.8fr) minmax(0,1.4fr);gap:16px}.mnote-admin-policy-form{display:flex;flex-direction:column;gap:12px}.mnote-admin-policy-form label{display:flex;flex-direction:column;gap:6px;color:var(--wolai-text-secondary);font-size:12px}.mnote-admin-policy-form input,.mnote-admin-policy-form select{min-height:34px;border:1px solid var(--wolai-border);border-radius:6px;padding:6px 8px;background:#fff}.mnote-admin-policy-form button,.mnote-admin-policy-panel button,.mnote-admin-policy-row button{min-height:34px;border:1px solid var(--wolai-border);border-radius:6px;padding:6px 12px;background:var(--wolai-bg-sidebar);cursor:pointer}.mnote-admin-policy-row button{margin-top:8px;color:#B91C1C}.mnote-admin-policy-json{min-height:48px;max-height:320px;overflow:auto;border-radius:6px;background:var(--wolai-bg-sidebar);padding:10px}.mnote-admin-policy-debug{margin-top:12px}.mnote-admin-policy-table{display:grid;gap:8px}.mnote-admin-policy-row{display:grid;grid-template-columns:minmax(0,1.4fr) minmax(120px,.5fr) minmax(0,.8fr);gap:12px;border:1px solid var(--wolai-border);border-radius:8px;padding:12px}.mnote-admin-policy-row strong,.mnote-admin-policy-row span{display:block;min-width:0}.mnote-admin-policy-row span{color:var(--wolai-text-secondary);font-size:12px;overflow-wrap:anywhere}.mnote-admin-policy-badge{display:inline-flex!important;margin:2px 4px 2px 0;border-radius:999px;padding:2px 8px;background:var(--wolai-bg-sidebar);font-size:12px}.mnote-admin-policy-badge[data-value="write"]{background:#DCFCE7}.mnote-admin-policy-badge[data-value="read"]{background:#DBEAFE}.mnote-admin-policy-badge[data-value="已撤销"]{background:#FEE2E2}.mnote-admin-policy-empty{border:1px dashed var(--wolai-border);border-radius:8px;padding:18px;color:var(--wolai-text-secondary);background:var(--wolai-bg-sidebar);font-size:13px}@media(max-width:960px){.mnote-admin-policy-summary,.mnote-admin-policy-grid,.mnote-admin-policy-row{grid-template-columns:1fr}.mnote-admin-policy-dialog__content{padding-right:0}}
.mnote-admin-policy-page{width:min(1080px,calc(100vw - 64px));margin:40px auto 64px}.mnote-admin-policy-modal{position:fixed;inset:0;z-index:1200;display:grid;place-items:center}.mnote-admin-policy-modal__backdrop{position:absolute;inset:0;background:rgba(15,23,42,.22)}.mnote-admin-policy-modal__panel{position:relative;width:min(920px,calc(100vw - 32px));max-height:calc(100vh - 48px);overflow:auto;border:1px solid var(--wolai-border);border-radius:8px;background:#fff;box-shadow:0 18px 54px rgba(15,23,42,.18);padding:20px}.mnote-admin-policy-modal__close{position:absolute;right:14px;top:14px;width:32px;height:32px;border:0;border-radius:6px;background:transparent;color:var(--wolai-text-secondary);cursor:pointer}.mnote-admin-policy-dialog__content{padding-right:28px}.mnote-admin-policy-header{margin-bottom:20px}.mnote-admin-policy-eyebrow{color:var(--wolai-text-secondary);font-size:13px}.mnote-admin-policy-header h1,.mnote-admin-policy-header h2{font-size:22px;font-weight:650}.mnote-admin-policy-header p,.mnote-admin-policy-note{color:var(--wolai-text-secondary);font-size:14px}.mnote-admin-policy-summary,.mnote-admin-policy-panel,.mnote-admin-policy-form{border:1px solid var(--wolai-border);border-radius:8px;background:#fff}.mnote-admin-policy-summary{display:grid;grid-template-columns:1fr 2fr;gap:16px;padding:16px;margin-bottom:16px}.mnote-admin-policy-summary-label{display:block;color:var(--wolai-text-secondary);font-size:12px}.mnote-admin-policy-summary code,.mnote-admin-policy-json{white-space:pre-wrap;overflow-wrap:anywhere;font-family:"JetBrains Mono","SFMono-Regular",Consolas,monospace;font-size:12px}.mnote-admin-policy-panel,.mnote-admin-policy-form{padding:16px;margin-bottom:16px}.mnote-admin-policy-panel-header{display:flex;justify-content:space-between;gap:12px;margin-bottom:12px}.mnote-admin-policy-grid{display:grid;grid-template-columns:minmax(260px,.8fr) minmax(0,1.4fr);gap:16px}.mnote-admin-policy-form{display:flex;flex-direction:column;gap:12px}.mnote-admin-policy-form label{display:flex;flex-direction:column;gap:6px;color:var(--wolai-text-secondary);font-size:12px}.mnote-admin-policy-form input,.mnote-admin-policy-form select{min-height:34px;border:1px solid var(--wolai-border);border-radius:6px;padding:6px 8px;background:#fff}.mnote-admin-policy-form button,.mnote-admin-policy-panel button,.mnote-admin-policy-row button{min-height:34px;border:1px solid var(--wolai-border);border-radius:6px;padding:6px 12px;background:var(--wolai-bg-sidebar);cursor:pointer}.mnote-admin-policy-row button{margin-top:8px;color:#B91C1C}.mnote-admin-policy-json{min-height:48px;max-height:320px;overflow:auto;border-radius:6px;background:var(--wolai-bg-sidebar);padding:10px}.mnote-admin-policy-debug{margin-top:12px}.mnote-admin-policy-table{display:grid;gap:8px}.mnote-admin-policy-row{display:grid;grid-template-columns:minmax(0,1.4fr) minmax(120px,.5fr) minmax(0,.8fr);gap:12px;border:1px solid var(--wolai-border);border-radius:8px;padding:12px}.mnote-admin-policy-row>div{min-width:0}.mnote-admin-policy-row strong,.mnote-admin-policy-root-link{overflow-wrap:anywhere}.mnote-admin-policy-row strong,.mnote-admin-policy-root-link,.mnote-admin-policy-row span{display:block;min-width:0}.mnote-admin-policy-root-link{color:var(--wolai-text-primary);font-weight:650;text-decoration:none}.mnote-admin-policy-root-link:hover{text-decoration:underline}.mnote-admin-policy-row span{color:var(--wolai-text-secondary);font-size:12px;overflow-wrap:anywhere}.mnote-admin-policy-badge{display:inline-flex!important;margin:2px 4px 2px 0;border-radius:999px;padding:2px 8px;background:var(--wolai-bg-sidebar);font-size:12px}.mnote-admin-policy-badge[data-value="write"]{background:#DCFCE7}.mnote-admin-policy-badge[data-value="read"]{background:#DBEAFE}.mnote-admin-policy-badge[data-value="已撤销"]{background:#FEE2E2}.mnote-admin-policy-empty{border:1px dashed var(--wolai-border);border-radius:8px;padding:18px;color:var(--wolai-text-secondary);background:var(--wolai-bg-sidebar);font-size:13px}@media(max-width:960px){.mnote-admin-policy-summary,.mnote-admin-policy-grid,.mnote-admin-policy-row{grid-template-columns:1fr}.mnote-admin-policy-dialog__content{padding-right:0}}
.mnote-trash-workbench {
width: min(860px, calc(100vw - 64px));
@@ -2217,9 +2277,12 @@ body {
--mnote-secondary-pane-resizer-width: 6px;
display: grid;
grid-template-columns: minmax(0, 1fr);
align-items: start;
align-items: stretch;
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
overflow: hidden;
}
.document-workspace[data-has-secondary-pane="true"] {
@@ -2228,6 +2291,28 @@ body {
.document-main-editor-group {
min-width: 0;
min-height: 0;
height: 100%;
display: flex;
flex-direction: column;
overflow-y: auto;
overscroll-behavior: contain;
}
.mnote-main-tab-panels {
min-width: 0;
min-height: 0;
flex: 1 1 auto;
display: flex;
flex-direction: column;
}
.mnote-main-tab-panels > [data-mnote-page-tab-panel] {
min-width: 0;
min-height: 0;
flex: 1 1 auto;
display: flex;
flex-direction: column;
}
.mnote-main-tab-strip {
@@ -2376,8 +2461,20 @@ body {
display: none !important;
}
.mnote-resource-tab-host {
flex: 1 1 auto;
min-height: 0;
}
.mnote-resource-tab-panel-root {
min-height: 0;
height: 100%;
}
.mnote-resource-tab-panel {
min-height: calc(100vh - 76px);
height: 100%;
min-width: 0;
}
.mnote-resource-tab-frame,
@@ -2439,6 +2536,39 @@ body {
color: var(--color-basic-600, #9B9A97);
}
.mnote-resource-tab-mindmap-shell {
width: 100%;
max-width: none;
height: 100%;
min-height: calc(100vh - 80px);
margin: 0;
padding: 0;
overflow: hidden;
display: flex;
flex-direction: column;
}
.mnote-resource-tab-mindmap-root {
width: 100%;
height: 100%;
min-height: calc(100vh - 80px);
overflow: hidden;
flex: 1 1 auto;
}
.document-pane[data-pane-role="secondary"] .mnote-resource-tab-mindmap-shell,
[data-testid="mnote-secondary-editor-tab-host"] .mnote-resource-tab-mindmap-shell {
width: 100%;
padding: 0;
}
.mnote-resource-tab-mindmap-shell [data-testid="mnote-mindmap-editor-root"] {
width: 100%;
height: 100%;
min-height: calc(100vh - 80px);
overflow: hidden;
}
.mnote-resource-tab-editor-root {
min-height: calc(100vh - 112px);
}
@@ -2689,6 +2819,10 @@ body {
min-height: 320px;
}
[data-editor-host-kind="leptos_tiptap_island"] {
min-height: 320px;
}
#mnote-leptos-tiptap-island-editor-root .editor-surface {
border: 0 !important;
box-shadow: none !important;
@@ -2696,6 +2830,13 @@ body {
padding: 0 !important;
}
[data-editor-host-kind="leptos_tiptap_island"] .editor-surface {
border: 0 !important;
box-shadow: none !important;
background: transparent !important;
padding: 0 !important;
}
#mnote-leptos-tiptap-island-editor-root .ProseMirror,
.ProseMirror {
color: var(--atelier-text);
@@ -2711,6 +2852,13 @@ body {
padding: 0 !important;
}
[data-editor-host-kind="leptos_tiptap_island"] .ProseMirror {
width: 100% !important;
min-height: 280px;
margin: 0 !important;
padding: 0 !important;
}
#mnote-leptos-tiptap-island-editor-root .block-handle-shell {
left: -32px !important;
z-index: 80;
@@ -2733,17 +2881,32 @@ body {
margin: 0 0 8px;
}
[data-editor-host-kind="leptos_tiptap_island"] .ProseMirror p {
margin: 0 0 8px;
}
#mnote-leptos-tiptap-island-editor-root .ProseMirror ul,
#mnote-leptos-tiptap-island-editor-root .ProseMirror ol {
margin: 4px 0 8px;
padding-left: 1.5em;
}
[data-editor-host-kind="leptos_tiptap_island"] .ProseMirror ul,
[data-editor-host-kind="leptos_tiptap_island"] .ProseMirror ol {
margin: 4px 0 8px;
padding-left: 1.5em;
}
#mnote-leptos-tiptap-island-editor-root .ProseMirror li {
margin: 2px 0;
padding-left: 2px;
}
[data-editor-host-kind="leptos_tiptap_island"] .ProseMirror li {
margin: 2px 0;
padding-left: 2px;
}
#mnote-leptos-tiptap-island-editor-root .ProseMirror blockquote {
margin: 8px 0;
padding: 2px 0 2px 14px;
@@ -2751,6 +2914,13 @@ body {
color: #5F5B56;
}
[data-editor-host-kind="leptos_tiptap_island"] .ProseMirror blockquote {
margin: 8px 0;
padding: 2px 0 2px 14px;
border-left: 3px solid #D9D6D0;
color: #5F5B56;
}
#mnote-leptos-tiptap-island-editor-root .ProseMirror input[type="checkbox"] {
width: 16px;
height: 16px;
@@ -2759,6 +2929,14 @@ body {
accent-color: #2EA44F;
}
[data-editor-host-kind="leptos_tiptap_island"] .ProseMirror input[type="checkbox"] {
width: 16px;
height: 16px;
margin: 0 8px 0 0;
vertical-align: -3px;
accent-color: #2EA44F;
}
#mnote-leptos-tiptap-island-editor-root .ProseMirror h1,
#mnote-leptos-tiptap-island-editor-root .ProseMirror h2,
#mnote-leptos-tiptap-island-editor-root .ProseMirror h3 {
@@ -2766,6 +2944,13 @@ body {
letter-spacing: 0;
}
[data-editor-host-kind="leptos_tiptap_island"] .ProseMirror h1,
[data-editor-host-kind="leptos_tiptap_island"] .ProseMirror h2,
[data-editor-host-kind="leptos_tiptap_island"] .ProseMirror h3 {
line-height: 1.2;
letter-spacing: 0;
}
.mnote-workspace-active-state {
padding-top: 10px;
}
@@ -2846,6 +3031,13 @@ body {
line-height: 22px;
}
.document-shell[data-page-small-text="true"] [data-editor-host-kind="leptos_tiptap_island"] .ProseMirror,
.document-shell[data-page-small-text="true"] [data-editor-host-kind="leptos_tiptap_island"] .ProseMirror p,
.document-shell[data-page-small-text="true"] [data-editor-host-kind="leptos_tiptap_island"] .ProseMirror li {
font-size: 15px;
line-height: 22px;
}
.document-shell[data-layout-density="compact"] #mnote-leptos-tiptap-island-editor-root .ProseMirror p,
.document-shell[data-layout-density="compact"] #mnote-leptos-tiptap-island-editor-root .ProseMirror ul,
.document-shell[data-layout-density="compact"] #mnote-leptos-tiptap-island-editor-root .ProseMirror ol,
@@ -2853,6 +3045,13 @@ body {
margin-bottom: 4px;
}
.document-shell[data-layout-density="compact"] [data-editor-host-kind="leptos_tiptap_island"] .ProseMirror p,
.document-shell[data-layout-density="compact"] [data-editor-host-kind="leptos_tiptap_island"] .ProseMirror ul,
.document-shell[data-layout-density="compact"] [data-editor-host-kind="leptos_tiptap_island"] .ProseMirror ol,
.document-shell[data-layout-density="compact"] [data-editor-host-kind="leptos_tiptap_island"] .ProseMirror blockquote {
margin-bottom: 4px;
}
.document-shell[data-layout-density="spacious"] #mnote-leptos-tiptap-island-editor-root .ProseMirror p,
.document-shell[data-layout-density="spacious"] #mnote-leptos-tiptap-island-editor-root .ProseMirror ul,
.document-shell[data-layout-density="spacious"] #mnote-leptos-tiptap-island-editor-root .ProseMirror ol,
@@ -2860,20 +3059,39 @@ body {
margin-bottom: 14px;
}
.document-shell[data-layout-density="spacious"] [data-editor-host-kind="leptos_tiptap_island"] .ProseMirror p,
.document-shell[data-layout-density="spacious"] [data-editor-host-kind="leptos_tiptap_island"] .ProseMirror ul,
.document-shell[data-layout-density="spacious"] [data-editor-host-kind="leptos_tiptap_island"] .ProseMirror ol,
.document-shell[data-layout-density="spacious"] [data-editor-host-kind="leptos_tiptap_island"] .ProseMirror blockquote {
margin-bottom: 14px;
}
.document-shell[data-page-font="song"] .document-title-input,
.document-shell[data-page-font="song"] #mnote-leptos-tiptap-island-editor-root .ProseMirror {
font-family: "Noto Serif SC", "Songti SC", serif;
}
.document-shell[data-page-font="song"] [data-editor-host-kind="leptos_tiptap_island"] .ProseMirror {
font-family: "Noto Serif SC", "Songti SC", serif;
}
.document-shell[data-page-font="kai"] .document-title-input,
.document-shell[data-page-font="kai"] #mnote-leptos-tiptap-island-editor-root .ProseMirror {
font-family: "STKaiti", "KaiTi", serif;
}
.document-shell[data-page-font="kai"] [data-editor-host-kind="leptos_tiptap_island"] .ProseMirror {
font-family: "STKaiti", "KaiTi", serif;
}
.document-shell[data-page-show-heading-numbers="true"] #mnote-leptos-tiptap-island-editor-root .ProseMirror {
counter-reset: mnote-heading;
}
.document-shell[data-page-show-heading-numbers="true"] [data-editor-host-kind="leptos_tiptap_island"] .ProseMirror {
counter-reset: mnote-heading;
}
.document-shell[data-page-show-heading-numbers="true"] #mnote-leptos-tiptap-island-editor-root .ProseMirror h1::before,
.document-shell[data-page-show-heading-numbers="true"] #mnote-leptos-tiptap-island-editor-root .ProseMirror h2::before,
.document-shell[data-page-show-heading-numbers="true"] #mnote-leptos-tiptap-island-editor-root .ProseMirror h3::before {
@@ -2883,6 +3101,15 @@ body {
font-weight: 500;
}
.document-shell[data-page-show-heading-numbers="true"] [data-editor-host-kind="leptos_tiptap_island"] .ProseMirror h1::before,
.document-shell[data-page-show-heading-numbers="true"] [data-editor-host-kind="leptos_tiptap_island"] .ProseMirror h2::before,
.document-shell[data-page-show-heading-numbers="true"] [data-editor-host-kind="leptos_tiptap_island"] .ProseMirror h3::before {
counter-increment: mnote-heading;
content: counter(mnote-heading) ". ";
color: #8B8782;
font-weight: 500;
}
.wolai-page-settings-popover {
position: fixed;
top: 52px;
@@ -4025,6 +4252,10 @@ button.wolai-page-ai-message-text {
width: 220px;
}
.mnote-sidebar-resizer {
display: none;
}
.wolai-topbar {
padding: 0 12px;
}
@@ -4091,6 +4322,10 @@ button.wolai-page-ai-message-text {
box-shadow: none;
}
.mnote-sidebar-resizer {
display: none;
}
.document-shell {
padding: 36px 20px 112px;
}
@@ -4141,6 +4376,9 @@ mod tests {
assert!(MNOTE_CSS.contains("#mnote-editor-island"));
assert!(MNOTE_CSS.contains("#mnote-search-island"));
assert!(MNOTE_CSS.contains("#mnote-mindmap-island"));
assert!(MNOTE_CSS.contains("--mnote-sidebar-width"));
assert!(MNOTE_CSS.contains(".mnote-sidebar-resizer"));
assert!(MNOTE_CSS.contains(".mnote-local-folder-dialog__recent button"));
}
#[test]
@@ -4176,6 +4414,15 @@ mod tests {
assert!(MNOTE_CSS.contains("::-webkit-scrollbar-thumb"));
}
#[test]
fn admin_policy_rows_wrap_long_grant_values() {
assert!(MNOTE_CSS.contains(
".mnote-admin-policy-row strong,.mnote-admin-policy-root-link{overflow-wrap:anywhere"
));
assert!(MNOTE_CSS.contains(".mnote-admin-policy-row>div{min-width:0"));
assert!(MNOTE_CSS.contains(".mnote-admin-policy-root-link:hover"));
}
#[test]
fn mnote_css_contains_prosemirror_styles() {
assert!(MNOTE_CSS.contains(".ProseMirror"));