收口本地工作区清理与资源投影

清理历史 Electron、Graphify、沙箱和截图等仓库跟踪残留,补充 CodeGraph 与 Convex active deploy source 协作说明。

新增 tree-first 下一阶段设计稿和 2026-05-20 清理总结,记录本地工作区、路径身份和 Zed/Lapce/VSCode 参考收口方向。

扩展 Rust Web 本地文件夹、DocumentBuffer、mindmap 资源、tree runtime 和页面聚合链路,并补充 task455 local-folder mindmap clean smoke。

验证:git diff --check 通过;pnpm store status --store-dir .pnpm-store 通过;npm ls --depth=0 --json 通过;find -L node_modules 未发现断链。cargo test -p mnote-web 当前 418 passed / 35 failed。
This commit is contained in:
lix-2026
2026-05-20 10:43:38 +08:00
parent 90818cdf75
commit b4c8bcb647
73 changed files with 8073 additions and 8240 deletions
+349 -60
View File
@@ -625,10 +625,16 @@ const SIDEBAR_TREE_JS: &str = r##"
return (new URLSearchParams(window.location.search).get('workspaceId') || 'default').trim() || 'default';
}
function currentWorkspaceId() {
return resolveWorkspaceId(document.body);
}
function currentSourceKind() {
var params = new URLSearchParams(window.location.search);
var fromUrl = (params.get('sourceKind') || '').trim();
if (fromUrl) return fromUrl;
var fromBody = document.body instanceof HTMLElement ? (document.body.getAttribute('data-mnote-source-kind') || '').trim() : '';
if (fromBody) return fromBody;
if (
window.location.pathname === '/' &&
!(params.get('workspaceId') || '').trim() &&
@@ -641,7 +647,10 @@ const SIDEBAR_TREE_JS: &str = r##"
}
function currentRootUri() {
return (new URLSearchParams(window.location.search).get('rootUri') || '').trim();
var params = new URLSearchParams(window.location.search);
var fromUrl = (params.get('rootUri') || '').trim();
if (fromUrl) return fromUrl;
return document.body instanceof HTMLElement ? (document.body.getAttribute('data-mnote-root-uri') || '').trim() : '';
}
function rememberCloudWorkspaceId(workspaceId) {
@@ -691,6 +700,10 @@ const SIDEBAR_TREE_JS: &str = r##"
var value = (params.get(name) || '').trim();
if (value) targetUrl.searchParams.set(name, value);
});
var sourceKind = currentSourceKind();
var rootUri = currentRootUri();
if (sourceKind && !targetUrl.searchParams.get('sourceKind')) targetUrl.searchParams.set('sourceKind', sourceKind);
if (rootUri && !targetUrl.searchParams.get('rootUri')) targetUrl.searchParams.set('rootUri', rootUri);
}
function currentWorkspaceSourcePayload() {
@@ -700,6 +713,10 @@ const SIDEBAR_TREE_JS: &str = r##"
var value = (params.get(name) || '').trim();
if (value) payload[name] = value;
});
var sourceKind = currentSourceKind();
var rootUri = currentRootUri();
if (sourceKind && !payload.sourceKind) payload.sourceKind = sourceKind;
if (rootUri && !payload.rootUri) payload.rootUri = rootUri;
return payload;
}
@@ -838,13 +855,19 @@ const SIDEBAR_TREE_JS: &str = r##"
document.documentElement.setAttribute('data-mnote-trash-modal-open', 'true');
var content = overlay.querySelector('[data-testid="mnote-trash-modal-content"]');
var sourceKind = currentSourceKind();
if (sourceKind === 'local_folder') {
renderLocalFolderTrashPlaceholder(content, new URLSearchParams(window.location.search).get('rootUri') || '');
return;
}
var workspaceId = resolveWorkspaceId(trigger || document.body);
var url = new URL('/trash', window.location.origin);
if (workspaceId) url.searchParams.set('workspaceId', workspaceId);
if (sourceKind === 'local_folder') {
var rootUri = currentRootUri();
if (!rootUri) {
renderLocalFolderTrashPlaceholder(content, '');
return;
}
url.searchParams.set('sourceKind', 'local_folder');
url.searchParams.set('rootUri', rootUri);
} else if (workspaceId) {
url.searchParams.set('workspaceId', workspaceId);
}
fetch(url.toString(), { headers: { 'x-mnote-trash-modal': '1' } }).then(function(response) {
return response.text().then(function(html) {
if (!response.ok) throw new Error('trash_modal_load_failed_' + response.status);
@@ -1294,7 +1317,7 @@ const SIDEBAR_TREE_JS: &str = r##"
title: ''
});
var nextWorkspaceId = result.workspaceId || workspaceId;
navigateToDocument(result.documentId, nextWorkspaceId, { treeView: activeSidebarTreeMode() });
navigateToDocument(commandDocumentId(result, ''), nextWorkspaceId, { treeView: activeSidebarTreeMode() });
}
function applySidebarTreeTab(mode, shell) {
@@ -1450,6 +1473,7 @@ const SIDEBAR_TREE_JS: &str = r##"
function commandDocumentId(result, fallback) {
var value = result && (
(result.execution && (result.execution.documentId || result.execution.id || result.execution.nodeId)) ||
result.documentId ||
result.id ||
result.nodeId ||
@@ -1462,6 +1486,7 @@ const SIDEBAR_TREE_JS: &str = r##"
function commandDocumentTitle(result, fallback) {
var value = result && (
(result.execution && result.execution.title) ||
result.title ||
(result.document && result.document.title) ||
(result.node && result.node.title) ||
@@ -1581,6 +1606,17 @@ const SIDEBAR_TREE_JS: &str = r##"
return pageChanged || fileChanged;
}
function localCommandNeedsProjectionRefresh(action, result) {
if (currentSourceKind() !== 'local_folder') return false;
if (action === 'create' || action === 'rename' || action === 'move' || action === 'delete' || action === 'archive' || action === 'trash' || action === 'purge') {
return true;
}
if (result && (result.previousDocumentId || result.previousRelativePath || result.relativePath || result.trashPath)) {
return true;
}
return false;
}
function sortOrderFromDelta(data) {
var raw = data && (data.sortOrder ?? data.sort_order);
var value = Number(raw);
@@ -1798,9 +1834,8 @@ const SIDEBAR_TREE_JS: &str = r##"
function normalizeMindmapFileTreeTitle(rawTitle, assetId, iconKind, objectIdentity) {
var title = String(rawTitle || '').trim();
var isMindmap = iconKind === 'mindmap' || String(objectIdentity && objectIdentity.objectKind || '') === 'mindmap';
var generated = /^mindmap[-_]/i.test(title);
if (!isMindmap || (!generated && title.length <= 24)) return title || '';
return shortMindmapFileName(assetId || title);
if (!isMindmap) return title || '';
return title || shortMindmapFileName(assetId);
}
function renderFileRows(parentId, grouped, activeId, activeRowId) {
@@ -1865,9 +1900,8 @@ const SIDEBAR_TREE_JS: &str = r##"
}
async function refreshLocalFolderSidebarSnapshot() {
var params = new URLSearchParams(window.location.search);
var workspaceId = currentWorkspaceId();
var rootUri = (params.get('rootUri') || '').trim();
var rootUri = currentRootUri();
var currentId = currentDocumentId();
if (!workspaceId || !rootUri) return false;
@@ -1900,9 +1934,8 @@ const SIDEBAR_TREE_JS: &str = r##"
}
function startLocalFolderSidebarWatch() {
var params = new URLSearchParams(window.location.search);
if ((params.get('sourceKind') || '').trim() !== 'local_folder') return;
var rootUri = (params.get('rootUri') || '').trim();
if (currentSourceKind() !== 'local_folder') return;
var rootUri = currentRootUri();
if (!rootUri) return;
var revision = '';
var refreshTimer = 0;
@@ -2029,11 +2062,29 @@ const SIDEBAR_TREE_JS: &str = r##"
return '/onlyoffice?' + params.toString();
}
function buildLocalOnlyOfficeOpenUrl(relativePath, fileName, documentId, assetId) {
var fileType = inferOnlyOfficeFileType(fileName || relativePath || '', '');
if (!fileType) return '';
var fileUrl = buildLocalFileOpenUrl(relativePath, false);
if (!fileUrl) return '';
return buildOnlyOfficeOpenUrl({
fileUrl: fileUrl,
fileName: fileName || relativePath || '',
fileType: fileType,
assetId: assetId || ('local-file:' + relativePath),
documentId: documentId || currentDocumentId() || '',
userId: '',
mode: 'edit'
});
}
function buildMindmapOpenPath(documentId, assetId) {
var doc = String(documentId || '').trim();
var map = String(assetId || '').trim();
if (!doc || !map) return '';
return '/mindmap/' + encodeURIComponent(doc) + '/' + encodeURIComponent(map);
var targetUrl = new URL('/mindmap/' + encodeURIComponent(doc) + '/' + encodeURIComponent(map), window.location.origin);
copyWorkspaceSourceParams(targetUrl);
return targetUrl.pathname + targetUrl.search;
}
function navigateToMindmapObject(documentId, assetId, workspaceId) {
@@ -2077,7 +2128,11 @@ const SIDEBAR_TREE_JS: &str = r##"
var assetId = String(detail && detail.assetId || '').trim();
var assetType = String(detail && detail.assetType || '').trim();
if (assetType === 'mindmap') return true;
return assetId.indexOf('mindmap_') === 0 || assetId.indexOf('mindmap-') === 0;
var fileName = assetId.indexOf('/') >= 0 ? assetId.split('/').pop() : assetId;
return assetId.indexOf('mindmap_') === 0
|| assetId.indexOf('mindmap-') === 0
|| /\.mindmap\.json$/i.test(fileName)
|| (/^/i.test(fileName) && /\.json$/i.test(fileName));
}
function localFilePathFromAssetId(assetId) {
@@ -2126,6 +2181,18 @@ const SIDEBAR_TREE_JS: &str = r##"
if (!assetId) return;
var localFilePath = localFilePathFromAssetId(assetId);
if (localFilePath) {
var localFileName = localFilePath.split('/').pop() || localFilePath;
if (isMindmapAssetDetail(detail) || String(detail && detail.assetType || '').trim() === 'mindmap') {
document.documentElement.setAttribute('data-mnote-last-mindmap-asset-open-mode', 'local-mindmap-object-shell');
document.documentElement.setAttribute('data-mnote-last-mindmap-asset-id', assetId);
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);
if (localOfficeUrl) {
window.open(localOfficeUrl, '_blank', 'noopener,noreferrer');
return;
}
var localFileUrl = buildLocalFileOpenUrl(localFilePath, false);
if (localFileUrl) window.open(localFileUrl, '_blank', 'noopener,noreferrer');
return;
@@ -2267,6 +2334,18 @@ const SIDEBAR_TREE_JS: &str = r##"
return String(asset && (asset.sourcePath || asset.file_url || asset.signedUrl || asset.signed_url || asset.thumbnail_url) || '').trim();
}
function localAssetOpenUrl(asset, download) {
if (!isLocalUploadedAsset(asset)) return '';
var rootUri = String(asset && (asset.rootUri || asset.root_uri) || '').trim() || currentRootUri();
var rootRelativePath = String(asset && (asset.rootRelativePath || asset.root_relative_path) || '').trim();
if (!rootUri || !rootRelativePath) return '';
var url = new URL('/api/local-folder/files/open', window.location.origin);
url.searchParams.set('rootUri', rootUri);
url.searchParams.set('path', rootRelativePath);
if (download) url.searchParams.set('download', 'true');
return url.toString();
}
function uploadedAssetType(asset) {
return String(asset && (asset.asset_type || asset.assetType || asset.mime_type || '') || '').trim();
}
@@ -2360,11 +2439,23 @@ const SIDEBAR_TREE_JS: &str = r##"
}
function buildOnlyOfficeAssetOpenUrl(asset, userId) {
if (isLocalUploadedAsset(asset)) return '';
var title = uploadedAssetTitle(asset);
var fileType = inferOnlyOfficeFileType(title, asset && asset.mime_type);
if (!fileType) return '';
var assetId = String(asset && asset.id || '').trim();
if (isLocalUploadedAsset(asset)) {
var localUrl = localAssetOpenUrl(asset, false);
if (!localUrl) return '';
return buildOnlyOfficeOpenUrl({
fileUrl: localUrl,
fileName: title,
fileType: fileType,
assetId: assetId,
documentId: String(asset && (asset.document_id || asset.documentId) || currentDocumentId() || '').trim(),
userId: userId || '',
mode: 'edit'
});
}
return buildOnlyOfficeOpenUrl({
fileUrl: assetId ? '' : uploadedAssetUrl(asset),
fileName: title,
@@ -2402,14 +2493,17 @@ const SIDEBAR_TREE_JS: &str = r##"
legacyOfficeAttachmentIndex = Object.create(null);
return legacyOfficeAttachmentIndex;
}
legacyOfficeAttachmentIndexPending = fetch(
'/api/tree/projections/file?documentId=' + encodeURIComponent(documentId) + '&workspaceId=' + encodeURIComponent(workspaceId),
{
method: 'GET',
credentials: 'include',
cache: 'no-store'
}
).then(function(response) {
var projectionUrl = new URL('/api/tree/projections/file', window.location.origin);
projectionUrl.searchParams.set('documentId', documentId);
projectionUrl.searchParams.set('workspaceId', workspaceId);
var sourcePayload = currentWorkspaceSourcePayload();
if (sourcePayload.sourceKind) projectionUrl.searchParams.set('sourceKind', sourcePayload.sourceKind);
if (sourcePayload.rootUri) projectionUrl.searchParams.set('rootUri', sourcePayload.rootUri);
legacyOfficeAttachmentIndexPending = fetch(projectionUrl.toString(), {
method: 'GET',
credentials: 'include',
cache: 'no-store'
}).then(function(response) {
return response.json().catch(function() { return null; }).then(function(payload) {
var items = payload && payload.ok && payload.result && Array.isArray(payload.result.items)
? payload.result.items
@@ -2581,11 +2675,22 @@ const SIDEBAR_TREE_JS: &str = r##"
var container = children || root;
var li = document.createElement('li');
li.className = 'tree-node';
var objectKind = String(asset && (asset.objectKind || asset.resourceKind || '') || '').trim();
if (!objectKind && (String(asset && (asset.asset_type || asset.assetType) || '').trim() === 'mindmap' || /\.mindmap\.json$/i.test(assetId))) {
objectKind = 'mindmap';
}
var objectIdentity = {
objectKind: objectKind || 'attachment',
documentId: targetDocumentId || null,
blockId: null,
assetId: assetId
};
li.setAttribute('data-node-id', 'asset:' + assetId);
var title = uploadedAssetTitle(asset);
var iconKind = uploadedAssetType(asset) || 'file';
if (objectKind === 'mindmap') iconKind = 'mindmap';
li.innerHTML =
'<div class="tree-row" role="treeitem" aria-level="' + (parentRow ? '2' : '1') + '" aria-expanded="false" data-rust-rendered-row="filetree" data-testid="filetree-asset-row" data-row-id="' + escapeHtml('asset:' + assetId) + '" data-row-kind="asset" data-node-id="' + escapeHtml('asset:' + assetId) + '" data-document-id="' + escapeHtml(targetDocumentId) + '" data-doc-id="' + escapeHtml(targetDocumentId) + '" data-asset-id="' + escapeHtml(assetId) + '" data-shell-mode="filetree" data-selected="false" data-active="false" draggable="true">' +
'<div class="tree-row" role="treeitem" aria-level="' + (parentRow ? '2' : '1') + '" aria-expanded="false" data-rust-rendered-row="filetree" data-testid="filetree-asset-row" data-row-id="' + escapeHtml('asset:' + assetId) + '" data-row-kind="asset" data-node-id="' + escapeHtml('asset:' + assetId) + '" data-document-id="' + escapeHtml(targetDocumentId) + '" data-doc-id="' + escapeHtml(targetDocumentId) + '" data-asset-id="' + escapeHtml(assetId) + '" data-object-identity="' + escapeHtml(objectIdentityAttr(objectIdentity)) + '" data-object-kind="' + escapeHtml(objectKind || 'attachment') + '" data-shell-mode="filetree" data-selected="false" data-active="false" draggable="true">' +
'<span class="tree-spacer" aria-hidden="true"></span><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('asset:' + assetId) + '" data-document-id="' + escapeHtml(targetDocumentId) + '" data-asset-id="' + escapeHtml(assetId) + '"><span class="tree-link-title">' + escapeHtml(title) + '</span></button>' +
'<div class="tree-actions"><button type="button" class="tree-action" data-testid="filetree-action-menu" data-rust-action="menu" data-row-id="' + escapeHtml('asset:' + assetId) + '" aria-label="更多操作"></button></div></div>';
@@ -2624,25 +2729,35 @@ const SIDEBAR_TREE_JS: &str = r##"
}
}
function mindmapAssetFromTarget(documentId, mindmapId) {
function mindmapAssetFromTarget(documentId, mindmapId, writeResult) {
var docId = String(documentId || currentDocumentId() || '').trim();
var assetId = String(mindmapId || '').trim();
if (!docId || !assetId) return null;
var fileName = shortMindmapFileName(assetId);
var result = writeResult && typeof writeResult === 'object' ? writeResult : {};
var resultAssetId = String(result.assetId || result.id || '').trim();
var relativePath = String(result.relativePath || result.rootRelativePath || '').trim();
var fileName = String(result.fileName || '').trim() || shortMindmapFileName(assetId);
return {
id: assetId,
id: resultAssetId || (relativePath ? 'local-file:' + relativePath : assetId),
document_id: docId,
asset_type: 'mindmap',
file_name: fileName,
file_url: '/documents/' + encodeURIComponent(docId) + '/' + encodeURIComponent(fileName)
file_url: relativePath || ('/documents/' + encodeURIComponent(docId) + '/' + encodeURIComponent(fileName)),
sourcePath: relativePath,
rootRelativePath: relativePath,
sourceKind: currentSourceKind(),
rootUri: currentRootUri(),
objectKind: 'mindmap'
};
}
function shortMindmapFileName(mindmapId) {
var raw = String(mindmapId || '').trim();
var pathName = raw.indexOf('/') >= 0 ? raw.split('/').pop() : raw;
if (/\.json$/i.test(pathName)) return pathName;
var digits = raw.match(/(\d{4,})$/);
var suffix = digits ? digits[1].slice(-4) : raw.replace(/^mindmap[_-]?/i, '').slice(-6);
return suffix ? '-' + suffix + '.json' : '.json';
var suffix = digits ? digits[1].slice(-6) : '';
return suffix ? '' + suffix + '.json' : '.json';
}
function parseMindmapApiTarget(input) {
@@ -2665,6 +2780,27 @@ const SIDEBAR_TREE_JS: &str = r##"
}
}
function withLocalMindmapSourceParams(input) {
var target = parseMindmapApiTarget(input);
if (!target || currentSourceKind() !== 'local_folder') return input;
var rootUri = currentRootUri();
if (!rootUri) return input;
var rawUrl = typeof input === 'string'
? input
: input && typeof input.url === 'string'
? input.url
: '';
if (!rawUrl) return input;
var url = new URL(rawUrl, window.location.origin);
url.searchParams.set('sourceKind', 'local_folder');
url.searchParams.set('rootUri', rootUri);
if (typeof input === 'string') return url.pathname + url.search;
if (typeof Request !== 'undefined' && input instanceof Request) {
return new Request(url.toString(), input);
}
return url.toString();
}
function requestMethod(input, init) {
return String(
init && init.method
@@ -2686,13 +2822,13 @@ const SIDEBAR_TREE_JS: &str = r##"
}
}
function applyMindmapApiMutationToFileTree(target) {
function applyMindmapApiMutationToFileTree(target, payload) {
if (!target || !target.documentId || !target.mindmapId) return;
var asset = mindmapAssetFromTarget(target.documentId, target.mindmapId);
var asset = mindmapAssetFromTarget(target.documentId, target.mindmapId, payload && payload.writeResult);
if (!asset) return;
appendUploadedAssetRow(asset, target.documentId);
document.documentElement.setAttribute('data-mnote-assets-local-applied', 'true');
document.documentElement.setAttribute('data-mnote-last-mindmap-asset-id', target.mindmapId);
document.documentElement.setAttribute('data-mnote-last-mindmap-asset-id', asset.id || target.mindmapId);
}
function installMindmapAssetFetchObserver() {
@@ -2701,26 +2837,72 @@ const SIDEBAR_TREE_JS: &str = r##"
window.__mnoteMindmapAssetFetchObserverInstalled = true;
var originalFetch = window.fetch.bind(window);
window.fetch = function(input, init) {
var target = parseMindmapApiTarget(input);
var nextInput = withLocalMindmapSourceParams(input);
var target = parseMindmapApiTarget(nextInput);
var method = requestMethod(input, init);
var createOnly = requestBodyHasMindmapCreateOnly(init || {});
return originalFetch(input, init).then(function(response) {
return originalFetch(nextInput, init).then(function(response) {
if (target && method === 'POST' && response && response.ok) {
if (createOnly || target.documentId === currentDocumentId()) {
applyMindmapApiMutationToFileTree(target);
}
var cloned = response.clone();
void cloned.json().then(function(payload) {
if (createOnly || target.documentId === currentDocumentId()) {
applyMindmapApiMutationToFileTree(target, payload);
}
}).catch(function() {
if (createOnly || target.documentId === currentDocumentId()) {
applyMindmapApiMutationToFileTree(target, null);
}
});
}
return response;
});
};
}
async function archiveLocalFileTreeAsset(row, assetId) {
if (!assetId) return false;
await dispatchTreeCommand(row || document.body, {
action: 'archive',
workspaceId: resolveWorkspaceId(row || document.body),
documentId: assetId
});
removeFileTreeAssetRow(assetId);
return true;
}
async function deleteSingleFileTreeAsset(detail, trigger) {
var assetId = String(detail && detail.assetId || '').trim();
if (!assetId) return false;
var row = trigger && trigger.closest ? trigger.closest('.tree-row[data-shell-mode="filetree"]') : null;
if (!row) row = document.querySelector('.tree-row[data-shell-mode="filetree"][data-asset-id="' + cssEscape(assetId) + '"]');
if (currentSourceKind() === 'local_folder') {
return archiveLocalFileTreeAsset(row, assetId);
}
var documentId = String(detail && detail.documentId || '').trim();
var kind = classifySidebarFileTreeAsset(row);
if (kind === 'mindmap') {
var response = await fetch('/api/mindmap/' + encodeURIComponent(documentId) + '/' + encodeURIComponent(assetId), { method: 'DELETE' });
if (!response.ok) throw new Error('mindmap_delete_failed_' + response.status);
removeFileTreeAssetRow(assetId);
return true;
}
if (kind === 'table') {
var tableResponse = await fetch('/api/tables/' + encodeURIComponent(assetId), { method: 'DELETE' });
if (!tableResponse.ok) throw new Error('table_delete_failed_' + tableResponse.status);
removeFileTreeAssetRow(assetId);
return true;
}
await postSidebarFileTreeJson('/api/media/batch', { action: 'delete', assetIds: [assetId] });
removeFileTreeAssetRow(assetId);
return true;
}
async function insertUploadedAssetIntoEditor(asset) {
var editorRoot = document.querySelector('.editor-surface .ProseMirror');
var editor = editorRoot && editorRoot.editor;
if (!editor || !editor.chain) return false;
var title = uploadedAssetTitle(asset);
var url = uploadedAssetUrl(asset);
var url = localAssetOpenUrl(asset, false) || uploadedAssetUrl(asset);
var type = uploadedAssetType(asset);
var assetId = String(asset && asset.id || '').trim();
var sizeLabel = uploadedFileSize(asset);
@@ -2730,7 +2912,7 @@ const SIDEBAR_TREE_JS: &str = r##"
}
var isLocalAsset = isLocalUploadedAsset(asset);
var userId = '';
var onlyOfficeUrl = isLocalAsset ? '' : buildOnlyOfficeAssetOpenUrl(asset, userId);
var onlyOfficeUrl = buildOnlyOfficeAssetOpenUrl(asset, userId);
if (onlyOfficeUrl && assetId) {
userId = await fetchCurrentOnlyOfficeUserId();
onlyOfficeUrl = buildOnlyOfficeAssetOpenUrl(asset, userId);
@@ -2738,7 +2920,7 @@ const SIDEBAR_TREE_JS: &str = r##"
var href = onlyOfficeUrl || url;
if (href) {
var storedHref = onlyOfficeUrl
? buildOnlyOfficeOpenPath({
? (isLocalAsset ? onlyOfficeUrl : buildOnlyOfficeOpenPath({
fileUrl: '',
fileName: title,
fileType: inferOnlyOfficeFileType(title, asset && asset.mime_type) || 'docx',
@@ -2746,7 +2928,7 @@ const SIDEBAR_TREE_JS: &str = r##"
documentId: String(asset && (asset.document_id || asset.documentId) || currentDocumentId() || '').trim(),
userId: userId || '',
mode: 'edit'
})
}))
: href;
var inserted = editor.chain().focus().insertContent({
type: 'paragraph',
@@ -3138,6 +3320,7 @@ const SIDEBAR_TREE_JS: &str = r##"
var documentId = detail.documentId || '';
var workspaceId = detail.workspaceId || resolveWorkspaceId(trigger || document.body);
var title = detail.title || '';
var isAsset = detail.contextKind === 'filetree' && detail.assetId && detail.rowKind !== 'document' && detail.rowKind !== 'index';
if (action === 'open-right') {
dispatchSidebarEvent('tree.page.open-right', detail);
return;
@@ -3166,6 +3349,15 @@ const SIDEBAR_TREE_JS: &str = r##"
void copyTreeContextValue(documentId || detail.assetId || detail.rowId || '', 'copy-id');
return;
}
if (action === 'delete-trash' && isAsset) {
if (!window.confirm('' + title + '')) return;
void deleteSingleFileTreeAsset(detail, trigger).catch(function(error) {
window.alert(error && error.message ? error.message : '');
}).then(function() {
if (currentSourceKind() === 'local_folder') void refreshLocalFolderSidebarSnapshot();
});
return;
}
if (action === 'new-file') {
void createPage(trigger || document.body, documentId || null);
return;
@@ -3224,12 +3416,17 @@ const SIDEBAR_TREE_JS: &str = r##"
convertToPreviousSiblingChild(trigger, detail);
return;
}
if (action === 'delete-trash' && documentId) {
var deleteTargetId = documentId || String(detail.rowId || '').trim();
if (action === 'delete-trash' && deleteTargetId) {
if (!window.confirm('' + title + '')) return;
void dispatchTreeCommand(trigger || document.body, {
action: 'archive',
workspaceId: workspaceId,
documentId: documentId
documentId: deleteTargetId
}).then(function() {
if (currentSourceKind() === 'local_folder') void refreshLocalFolderSidebarSnapshot();
}).catch(function(error) {
window.alert(error && error.message ? error.message : '');
});
}
}
@@ -3303,6 +3500,7 @@ const SIDEBAR_TREE_JS: &str = r##"
{ action: 'open-right', icon: 'open_in_new', label: '', shortcut: 'Alt + O' },
{ action: 'rename', icon: 'edit', label: '', shortcut: 'F2' },
{ action: 'copy-id', icon: 'tag', label: ' ID' },
{ action: 'delete-trash', icon: 'delete', label: '', shortcut: 'Del', danger: true },
{ separator: true },
{ action: 'copy-path', icon: 'content_copy', label: 'Copy Path' },
{ action: 'refresh', icon: 'refresh', label: 'Refresh' },
@@ -3499,6 +3697,7 @@ const SIDEBAR_TREE_JS: &str = r##"
function buildSidebarFileTreeDeletePlan(rows) {
var docRows = [];
var folderRows = [];
var assetRows = [];
rows.forEach(function(row) {
var kind = fileTreeRowKind(row);
@@ -3506,19 +3705,32 @@ const SIDEBAR_TREE_JS: &str = r##"
docRows.push(row);
return;
}
if (kind === 'folder' && currentSourceKind() === 'local_folder') {
folderRows.push(row);
return;
}
if (fileTreeRowAssetId(row)) assetRows.push(row);
});
var selectedDocRowIds = new Set(docRows.map(function(row) { return row.getAttribute('data-row-id') || ''; }).filter(Boolean));
var selectedContainerRowIds = new Set(docRows.concat(folderRows).map(function(row) { return row.getAttribute('data-row-id') || ''; }).filter(Boolean));
var topDocRows = [];
var topFolderRows = [];
var seenDocs = new Set();
var seenFolders = new Set();
docRows.forEach(function(row) {
var documentId = fileTreeRowDocumentId(row);
if (!documentId || seenDocs.has(documentId)) return;
if (hasSelectedDocumentAncestor(row, selectedDocRowIds)) return;
if (hasSelectedDocumentAncestor(row, selectedContainerRowIds)) return;
seenDocs.add(documentId);
topDocRows.push(row);
});
var selectedTopDocRows = new Set(topDocRows.map(function(row) { return row.getAttribute('data-row-id') || ''; }).filter(Boolean));
folderRows.forEach(function(row) {
var rowId = row.getAttribute('data-row-id') || '';
if (!rowId || seenFolders.has(rowId)) return;
if (hasSelectedDocumentAncestor(row, selectedContainerRowIds)) return;
seenFolders.add(rowId);
topFolderRows.push(row);
});
var selectedTopContainerRows = new Set(topDocRows.concat(topFolderRows).map(function(row) { return row.getAttribute('data-row-id') || ''; }).filter(Boolean));
var fileAssetRows = [];
var mindmapRows = [];
var tableRows = [];
@@ -3526,7 +3738,7 @@ const SIDEBAR_TREE_JS: &str = r##"
assetRows.forEach(function(row) {
var assetId = fileTreeRowAssetId(row);
if (!assetId || seenAssets.has(assetId)) return;
if (hasSelectedDocumentAncestor(row, selectedTopDocRows)) return;
if (hasSelectedDocumentAncestor(row, selectedTopContainerRows)) return;
seenAssets.add(assetId);
var assetKind = classifySidebarFileTreeAsset(row);
if (assetKind === 'mindmap') mindmapRows.push(row);
@@ -3535,6 +3747,7 @@ const SIDEBAR_TREE_JS: &str = r##"
});
return {
docRows: topDocRows,
folderRows: topFolderRows,
fileAssetRows: fileAssetRows,
mindmapRows: mindmapRows,
tableRows: tableRows
@@ -3543,11 +3756,13 @@ const SIDEBAR_TREE_JS: &str = r##"
function sidebarFileTreeDeleteConfirmText(plan) {
var docCount = plan.docRows.length;
var folderCount = plan.folderRows.length;
var fileCount = plan.fileAssetRows.length;
var mindmapCount = plan.mindmapRows.length;
var tableCount = plan.tableRows.length;
var parts = [];
if (docCount > 0) parts.push(docCount + ' ');
if (folderCount > 0) parts.push(folderCount + ' ');
if (fileCount > 0) parts.push(fileCount + ' 10 ');
if (mindmapCount > 0) parts.push(mindmapCount + ' 10 ');
if (tableCount > 0) parts.push(tableCount + ' 线');
@@ -3638,7 +3853,7 @@ const SIDEBAR_TREE_JS: &str = r##"
async function deleteSelectedSidebarFileTreeRows(trigger) {
var rows = selectedSidebarFileTreeRows();
var plan = buildSidebarFileTreeDeletePlan(rows);
var total = plan.docRows.length + plan.fileAssetRows.length + plan.mindmapRows.length + plan.tableRows.length;
var total = plan.docRows.length + plan.folderRows.length + plan.fileAssetRows.length + plan.mindmapRows.length + plan.tableRows.length;
if (total === 0) return false;
if (!window.confirm(sidebarFileTreeDeleteConfirmText(plan))) return false;
var failures = [];
@@ -3656,6 +3871,19 @@ const SIDEBAR_TREE_JS: &str = r##"
failures.push(documentId);
}
}
for (var fd = 0; fd < plan.folderRows.length; fd += 1) {
var folderRow = plan.folderRows[fd];
var folderId = folderRow.getAttribute('data-row-id') || folderRow.getAttribute('data-node-id') || '';
try {
await dispatchTreeCommand(trigger || folderRow, {
action: 'archive',
workspaceId: resolveWorkspaceId(folderRow),
documentId: folderId
});
} catch (error) {
failures.push(folderId);
}
}
if (plan.fileAssetRows.length > 0) {
var fileAssetIds = plan.fileAssetRows.map(fileTreeRowAssetId).filter(Boolean);
try {
@@ -3679,9 +3907,17 @@ const SIDEBAR_TREE_JS: &str = r##"
var mindmapRow = plan.mindmapRows[m];
var mindmapId = fileTreeRowAssetId(mindmapRow);
try {
var mindmapDocId = fileTreeRowDocumentId(mindmapRow);
var mindmapResponse = await fetch('/api/mindmap/' + encodeURIComponent(mindmapDocId) + '/' + encodeURIComponent(mindmapId), { method: 'DELETE' });
if (!mindmapResponse.ok) throw new Error('mindmap_delete_failed_' + mindmapResponse.status);
if (currentSourceKind() === 'local_folder') {
await dispatchTreeCommand(trigger || mindmapRow, {
action: 'archive',
workspaceId: resolveWorkspaceId(mindmapRow),
documentId: mindmapId
});
} else {
var mindmapDocId = fileTreeRowDocumentId(mindmapRow);
var mindmapResponse = await fetch('/api/mindmap/' + encodeURIComponent(mindmapDocId) + '/' + encodeURIComponent(mindmapId), { method: 'DELETE' });
if (!mindmapResponse.ok) throw new Error('mindmap_delete_failed_' + mindmapResponse.status);
}
removeFileTreeAssetRow(mindmapId);
} catch (error) {
failures.push(mindmapId);
@@ -3707,6 +3943,7 @@ const SIDEBAR_TREE_JS: &str = r##"
return false;
}
document.documentElement.setAttribute('data-mnote-filetree-bulk-delete-applied', 'true');
if (currentSourceKind() === 'local_folder') void refreshLocalFolderSidebarSnapshot();
return true;
}
@@ -7939,7 +8176,16 @@ const SIDEBAR_TREE_JS: &str = r##"
window.addEventListener('tree:title-updated', function(event) {
var detail = event.detail || {};
var previousDocumentId = detail.previousDocumentId || (detail.payload && detail.payload.result && detail.payload.result.previousDocumentId) || '';
if (previousDocumentId && previousDocumentId !== detail.documentId) {
removeDocumentRowForMode('page', previousDocumentId);
removeDocumentRowForMode('filetree', previousDocumentId);
if (currentDocumentId() === previousDocumentId) {
navigateToDocument(detail.documentId, detail.workspaceId || currentWorkspaceId(), { treeView: activeSidebarTreeMode() });
}
}
updateTitleEverywhere(detail.documentId, detail.title);
if (currentSourceKind() === 'local_folder') void refreshLocalFolderSidebarSnapshot();
document.documentElement.setAttribute('data-mnote-title-local-applied', 'true');
});
@@ -7947,12 +8193,24 @@ const SIDEBAR_TREE_JS: &str = r##"
var detail = event.detail || {};
var body = detail.body || {};
var action = String(body.action || '').trim();
var result = detail.result || {};
if (action === 'create') {
applyCreatedDocumentLocally(detail.result || {}, body.parentId || null, body.title || '');
applyCreatedDocumentLocally(result, body.parentId || null, body.title || '');
if (localCommandNeedsProjectionRefresh(action, result)) void refreshLocalFolderSidebarSnapshot();
return;
}
if (action === 'rename' && body.documentId && body.title) {
updateTitleEverywhere(body.documentId, body.title);
var newDocumentId = commandDocumentId(result, body.documentId);
if (newDocumentId && newDocumentId !== body.documentId) {
removeDocumentRowForMode('page', body.documentId);
removeDocumentRowForMode('filetree', body.documentId);
if (currentDocumentId() === body.documentId) {
navigateToDocument(newDocumentId, body.workspaceId || currentWorkspaceId(), { treeView: activeSidebarTreeMode() });
}
} else {
updateTitleEverywhere(body.documentId, body.title);
}
if (localCommandNeedsProjectionRefresh(action, result)) void refreshLocalFolderSidebarSnapshot();
document.documentElement.setAttribute('data-mnote-tree-local-command-applied', 'rename');
return;
}
@@ -7960,12 +8218,14 @@ const SIDEBAR_TREE_JS: &str = r##"
if (applyMoveDocumentDelta({ documentId: body.documentId, parentId: body.parentId || null, sortOrder: body.sortOrder })) {
document.documentElement.setAttribute('data-mnote-tree-local-command-applied', 'move');
}
if (localCommandNeedsProjectionRefresh(action, result)) void refreshLocalFolderSidebarSnapshot();
return;
}
if ((action === 'purge' || action === 'delete' || action === 'archive') && body.documentId) {
if (applyRemoveDocumentDelta({ documentId: body.documentId })) {
document.documentElement.setAttribute('data-mnote-tree-local-command-applied', 'remove');
}
if (localCommandNeedsProjectionRefresh(action, result)) void refreshLocalFolderSidebarSnapshot();
}
});
@@ -8215,6 +8475,11 @@ const TREE_LIVE_CONTROLLER_JS: &str = r##"
function start() {
var bootstrap = readBootstrap();
if (bootstrap.disabled === true || bootstrap.transport === 'disabled') {
applyTransport('disabled');
applyStatus('disabled');
return;
}
var params = new URLSearchParams(window.location.search);
var sourceKind = (params.get('sourceKind') || '').trim();
if (sourceKind === 'local_folder') {
@@ -8291,6 +8556,9 @@ pub fn PageLayout(
/// 是否显示管理员授权入口
#[prop(optional)]
show_admin_access_policy: bool,
/// 是否启用树实时流
#[prop(optional, default = true)]
enable_tree_live: bool,
) -> impl IntoView {
let sidebar_tree_html = sidebar_tree_html.unwrap_or_default();
@@ -8321,7 +8589,8 @@ pub fn PageLayout(
});
let tree_live_bootstrap = serde_json::json!({
"schema": "mnote.tree_live_bootstrap.v1",
"transport": "convex-command-log-ws",
"disabled": !enable_tree_live,
"transport": if enable_tree_live { "convex-command-log-ws" } else { "disabled" },
"workspaceId": null,
"rootIds": [],
"initialRevision": null,
@@ -8452,6 +8721,7 @@ mod tests {
assert!(SIDEBAR_TREE_JS.contains("openConvexAssetFromFileTree"));
assert!(SIDEBAR_TREE_JS.contains("buildMindmapOpenPath"));
assert!(SIDEBAR_TREE_JS.contains("isMindmapAssetDetail"));
assert!(SIDEBAR_TREE_JS.contains("^思维导图"));
assert!(!SIDEBAR_TREE_JS.contains("openMindmapAssetInDocumentShell"));
assert!(SIDEBAR_TREE_JS.contains("data-mnote-last-mindmap-asset-open-mode"));
assert!(SIDEBAR_TREE_JS.contains("mindmap-object-shell"));
@@ -8514,6 +8784,11 @@ mod tests {
assert!(SIDEBAR_TREE_JS.contains("function currentRootUri()"));
assert!(SIDEBAR_TREE_JS.contains("sourceKind: currentSourceKind()"));
assert!(SIDEBAR_TREE_JS.contains("rootUri: currentRootUri()"));
assert!(SIDEBAR_TREE_JS
.contains("if (sourceKind && !payload.sourceKind) payload.sourceKind = sourceKind"));
assert!(
SIDEBAR_TREE_JS.contains("if (rootUri && !payload.rootUri) payload.rootUri = rootUri")
);
assert!(SIDEBAR_TREE_JS.contains("pageContext: scopedContext.pageContext"));
assert!(
SIDEBAR_TREE_JS.contains("if (currentSourceKind() === 'local_folder') return false;"),
@@ -8605,6 +8880,8 @@ mod tests {
fn sidebar_tree_runtime_polls_local_folder_without_browser_reload() {
assert!(SIDEBAR_TREE_JS.contains("startLocalFolderSidebarWatch"));
assert!(SIDEBAR_TREE_JS.contains("refreshLocalFolderSidebarSnapshot"));
assert!(SIDEBAR_TREE_JS.contains("function currentWorkspaceId()"));
assert!(SIDEBAR_TREE_JS.contains("var rootUri = currentRootUri();"));
assert!(SIDEBAR_TREE_JS.contains("/api/tree/local-folder-watch"));
assert!(SIDEBAR_TREE_JS.contains("/api/tree/projections/sidebar"));
assert!(SIDEBAR_TREE_JS.contains("/api/tree/projections/file"));
@@ -8626,11 +8903,23 @@ mod tests {
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)"));
assert!(SIDEBAR_TREE_JS.contains("if (isLocalUploadedAsset(asset)) return '';"));
assert!(SIDEBAR_TREE_JS.contains("buildLocalOnlyOfficeOpenUrl"));
assert!(SIDEBAR_TREE_JS.contains("localOfficeUrl"));
assert!(SIDEBAR_TREE_JS.contains("refreshLocalFolderSidebarSnapshot"));
assert!(SIDEBAR_TREE_JS.contains("wolai:local-assets-changed"));
}
#[test]
fn sidebar_runtime_routes_local_mindmap_and_office_assets() {
assert!(SIDEBAR_TREE_JS.contains("withLocalMindmapSourceParams"));
assert!(SIDEBAR_TREE_JS.contains("url.searchParams.set('sourceKind', 'local_folder')"));
assert!(SIDEBAR_TREE_JS.contains("url.searchParams.set('rootUri', rootUri)"));
assert!(SIDEBAR_TREE_JS
.contains("data-mnote-last-mindmap-asset-open-mode', 'local-mindmap-object-shell'"));
assert!(SIDEBAR_TREE_JS.contains("buildLocalOnlyOfficeOpenUrl"));
assert!(SIDEBAR_TREE_JS.contains("isLocalAsset ? onlyOfficeUrl"));
}
#[test]
fn sidebar_tree_runtime_contains_dev_hot_reload_client() {
assert!(SIDEBAR_TREE_JS.contains("installMnoteDevHotReload"));
@@ -8687,7 +8976,7 @@ mod tests {
.contains("renderFileRows('', groupRowsByParent(rows), activeId, activeRowId)"));
assert!(SIDEBAR_TREE_JS.contains("renderFileRows(nodeId, grouped, activeId, activeRowId)"));
assert!(SIDEBAR_TREE_JS.contains("normalizeMindmapFileTreeTitle"));
assert!(SIDEBAR_TREE_JS.contains("shortMindmapFileName(assetId || title)"));
assert!(SIDEBAR_TREE_JS.contains("shortMindmapFileName(assetId)"));
assert!(
SIDEBAR_TREE_JS.contains("var title = isFileTreeProjectionPageRow(rowKind, assetId)")
);