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:
@@ -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, '&')
|
||||
@@ -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"));
|
||||
|
||||
Reference in New Issue
Block a user