diff --git a/design/10-review/process/16-mnote-web-runtime-module-maintainability-checklist-v1.md b/design/10-review/process/16-mnote-web-runtime-module-maintainability-checklist-v1.md index 899129b1..0b37e4b3 100644 --- a/design/10-review/process/16-mnote-web-runtime-module-maintainability-checklist-v1.md +++ b/design/10-review/process/16-mnote-web-runtime-module-maintainability-checklist-v1.md @@ -165,7 +165,7 @@ cargo test --manifest-path rust/Cargo.toml -p mnote-web --lib routes::web_shell - [x] C1. `sidebar-workspace-runtime.js`:workspace source switch、sidebar tabs、collapse/resize 状态。 - [ ] C2. `sidebar-page-tree-runtime.js`:page tree row selection、rename title patch、breadcrumb/title sync。 -- [ ] C3. `sidebar-filetree-open-runtime.js`:local markdown / resource open target、active resource tab dispatch。 +- [x] C3. `sidebar-filetree-open-runtime.js`:local markdown / resource open target、active resource tab dispatch。 - [ ] C4. `sidebar-filetree-command-runtime.js`:create/rename/delete/copy/move/trash/restore/purge command payload。 - [ ] C5. `sidebar-filetree-upload-runtime.js`:local upload target plan、drop/paste preflight、readonly guard。 - [ ] C6. `sidebar-attachment-open-runtime.js`:OnlyOffice/PDF/code/image open mode guard。 diff --git a/rust/crates/mnote-web/browser/sidebar-filetree-open-runtime.js b/rust/crates/mnote-web/browser/sidebar-filetree-open-runtime.js new file mode 100644 index 00000000..f3aa9043 --- /dev/null +++ b/rust/crates/mnote-web/browser/sidebar-filetree-open-runtime.js @@ -0,0 +1,422 @@ +export const createSidebarFileTreeOpenRuntime = (dependencies = {}) => { + const { + copyWorkspaceSourceParams, + currentDocumentId, + currentRootUri, + fileTreeIconKindForFileName, + getNavigationInFlight, + isCodeAttachmentFileName, + isNonOfficeAttachmentName, + openCodeEditorAttachment, + resolveWorkspaceId, + setNavigationInFlight, + shouldOpenLocalResourceInNewWindow, + uploadedFileSize, + } = dependencies; + + function inferOnlyOfficeFileType(fileName, mimeType) { + var name = String(fileName || '').trim().toLowerCase(); + var mt = String(mimeType || '').trim().toLowerCase(); + var ext = name.indexOf('.') >= 0 ? name.split('.').pop() : ''; + if (['doc', 'docx', 'odt', 'rtf'].indexOf(ext) >= 0) return ext; + if (['ppt', 'pptx', 'odp'].indexOf(ext) >= 0) return ext; + if (['xls', 'xlsx', 'ods', 'csv'].indexOf(ext) >= 0) return ext; + if (isNonOfficeAttachmentName(name, ext)) return ''; + if (mt.indexOf('wordprocessingml') >= 0) return 'docx'; + if (mt.indexOf('presentationml') >= 0) return 'pptx'; + if (mt.indexOf('spreadsheetml') >= 0) return 'xlsx'; + return ''; + } + + function buildOnlyOfficeOpenUrl(input) { + var target = new URL('/onlyoffice', window.location.origin); + var fileUrl = String(input.fileUrl || '').trim(); + if (fileUrl) { + try { + fileUrl = new URL(fileUrl, window.location.origin).toString(); + } catch (_error) {} + } + target.searchParams.set('fileUrl', fileUrl); + target.searchParams.set('fileName', input.fileName || '未命名资源'); + target.searchParams.set('fileType', input.fileType || 'docx'); + if (input.assetId) target.searchParams.set('assetId', input.assetId); + if (input.documentId) target.searchParams.set('documentId', input.documentId); + if (input.userId) target.searchParams.set('userId', input.userId); + target.searchParams.set('mode', input.mode || 'view'); + return target.toString(); + } + + function buildOnlyOfficeOpenPath(input) { + var _rto_ = window.__mnoteResourceOpenRuntime; + if (_rto_ && typeof _rto_.buildOnlyOfficeOpenPath === 'function') { + return _rto_.buildOnlyOfficeOpenPath(input); + } + var params = new URLSearchParams(); + params.set('fileUrl', input.fileUrl || ''); + params.set('fileName', input.fileName || '未命名资源'); + params.set('fileType', input.fileType || 'docx'); + if (input.assetId) params.set('assetId', input.assetId); + if (input.documentId) params.set('documentId', input.documentId); + if (input.userId) params.set('userId', input.userId); + params.set('mode', input.mode || 'view'); + if (input.documentId && input.assetId) { + return '/office/' + encodeURIComponent(input.documentId) + '/' + encodeURIComponent(input.assetId) + '?' + params.toString(); + } + return '/onlyoffice?' + params.toString(); + } + + function buildLocalOnlyOfficeOpenUrl(relativePath, fileName, documentId, assetId, mode) { + 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: mode || 'view' + }); + } + + async function openLocalOfficeFileInActiveTab(detail, mode) { + var localFilePath = localFilePathFromAssetId(detail.assetId); + if (!localFilePath) return false; + var localFileName = localFilePath.split('/').pop() || localFilePath; + var localOfficeUrl = buildLocalOnlyOfficeOpenUrl(localFilePath, localFileName, String(detail.documentId || currentDocumentId() || '').trim(), detail.assetId, mode || 'view'); + if (!localOfficeUrl) return false; + var opened = await openLocalResourceInActiveTab({ + path: localFilePath, + title: detail.fileName || localFileName, + kind: 'office', + assetId: detail.assetId, + documentId: detail.documentId || currentDocumentId() || '', + workspaceId: detail.workspaceId || resolveWorkspaceId(document.body) || '', + href: buildLocalFileOpenUrl(localFilePath, false), + officeUrl: localOfficeUrl, + paneRole: detail.paneRole || 'primary' + }); + if (!opened) window.open(localOfficeUrl, '_blank', 'noopener,noreferrer'); + return true; + } + + function buildMindmapOpenPath(documentId, assetId) { + var doc = String(documentId || '').trim(); + var map = String(assetId || '').trim(); + if (!doc || !map) return ''; + var targetUrl = new URL('/mindmap/' + encodeURIComponent(doc) + '/' + encodeURIComponent(map), window.location.origin); + copyWorkspaceSourceParams(targetUrl); + return targetUrl.pathname + targetUrl.search; + } + + function navigateToMindmapObject(documentId, assetId, workspaceId) { + var mindmapPath = buildMindmapOpenPath(documentId, assetId); + if (!mindmapPath) return false; + var targetUrl = new URL(mindmapPath, window.location.origin); + var url = targetUrl.pathname + targetUrl.search; + if (getNavigationInFlight() === url) return true; + if (typeof window.__mnoteDocumentPaneRuntime?.openPrimaryMindmap === 'function') { + setNavigationInFlight(url); + document.documentElement.setAttribute('data-mnote-navigation-pending', 'true'); + document.documentElement.setAttribute('data-mnote-navigation-target', assetId); + window.__mnoteDocumentPaneRuntime.openPrimaryMindmap({ + documentId: documentId, + mindmapId: assetId, + workspaceId: workspaceId || '', + url: targetUrl + }).then(function(){ + if (getNavigationInFlight() === url) setNavigationInFlight(''); + document.documentElement.removeAttribute('data-mnote-navigation-pending'); + }).catch(function(error){ + console.warn('mnote mindmap pane 内导航失败,将回退整页导航', error); + if (window.__mnoteTreeLiveEventSource && typeof window.__mnoteTreeLiveEventSource.close === 'function') { + window.__mnoteTreeLiveEventSource.close(); + } + window.location.assign(url); + }); + return true; + } + setNavigationInFlight(url); + document.documentElement.setAttribute('data-mnote-navigation-pending', 'true'); + document.documentElement.setAttribute('data-mnote-navigation-target', assetId); + if (window.__mnoteTreeLiveEventSource && typeof window.__mnoteTreeLiveEventSource.close === 'function') { + window.__mnoteTreeLiveEventSource.close(); + } + window.location.assign(url); + return true; + } + + function isMindmapAssetDetail(detail) { + var assetId = String(detail && detail.assetId || '').trim(); + var assetType = String(detail && detail.assetType || '').trim(); + if (assetType === 'mindmap') return true; + 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) { + var _rto_ = window.__mnoteResourceOpenRuntime; + if (_rto_ && typeof _rto_.localFilePathFromAssetId === 'function') { + return _rto_.localFilePathFromAssetId(assetId); + } + var value = String(assetId || '').trim(); + return value.indexOf('local-file:') === 0 ? value.slice('local-file:'.length) : value.indexOf('local:asset:') === 0 ? value.slice('local:asset:'.length) : ''; + } + + function buildLocalFileOpenUrl(relativePath, download) { + var _rto_ = window.__mnoteResourceOpenRuntime; + if (_rto_ && typeof _rto_.buildLocalFileOpenUrl === 'function') { + return _rto_.buildLocalFileOpenUrl(relativePath, download); + } + var rootUri = currentRootUri(); + if (!rootUri || !relativePath) return ''; + var url = new URL('/api/local-folder/files/open', window.location.origin); + url.searchParams.set('rootUri', rootUri); + url.searchParams.set('path', relativePath); + if (download) url.searchParams.set('download', 'true'); + return url.toString(); + } + + async function openLocalResourceInActiveTab(input) { + if (typeof window.__mnoteDocumentPaneRuntime?.openResourceInActiveTab !== 'function') return false; + var relativePath = String(input && input.path || '').trim(); + var rootUri = String(input && input.rootUri || currentRootUri() || '').trim(); + if (!relativePath || !rootUri) return false; + var title = String(input && input.title || '').trim() || relativePath.split('/').pop() || relativePath; + var kind = String(input && input.kind || fileTreeIconKindForFileName(title) || '').trim(); + var objectIdentity = 'resource:file:' + rootUri + ':' + relativePath; + return await window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({ + objectIdentity: objectIdentity, + assetId: String(input && input.assetId || '').trim() || 'local-file:' + relativePath, + title: title, + fileName: title, + kind: kind, + rootUri: rootUri, + path: relativePath, + 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(), + paneRole: String(input && input.paneRole || 'primary').trim() || 'primary' + }); + } + + function readFileTreeObjectIdentity(row) { + if (!row) return null; + var raw = row.getAttribute('data-object-identity') || ''; + if (!raw) return null; + try { + var parsed = JSON.parse(raw); + return parsed && typeof parsed === 'object' ? parsed : null; + } catch (_error) { + return null; + } + } + + async function fetchCurrentOnlyOfficeUserId() { + try { + var response = await fetch('/api/auth/whoami', { + method: 'GET', + credentials: 'include', + cache: 'no-store' + }); + var payload = await response.json().catch(function() { return null; }); + return String(payload && payload.userId || '').trim(); + } catch (_error) { + return ''; + } + } + + async function openConvexAssetFromFileTree(detail) { + var assetId = String(detail && detail.assetId || '').trim(); + if (!assetId) return; + var openTarget = String(detail && detail.openTarget || '').trim().toLowerCase(); + var forceNewWindow = openTarget === 'new-window'; + 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: sideLocalFilePath && sideRootUri ? 'resource:file:' + sideRootUri + ':' + sideLocalFilePath : String(detail.objectIdentity || detail.assetId || ''), + assetId: assetId, + 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) || '') + }); + } + return; + } + var localFilePath = localFilePathFromAssetId(assetId); + if (localFilePath) { + var localFileName = localFilePath.split('/').pop() || localFilePath; + if (isMindmapAssetDetail(detail) || String(detail && detail.assetType || '').trim() === 'mindmap') { + if (typeof window.__mnoteDocumentPaneRuntime?.openResourceInActiveTab === 'function') { + document.documentElement.setAttribute('data-mnote-last-mindmap-asset-open-mode', 'mindmap-resource-tab'); + document.documentElement.setAttribute('data-mnote-last-mindmap-asset-id', assetId); + void window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({ + objectIdentity: 'resource:mindmap:' + String(detail && detail.documentId || currentDocumentId() || '').trim() + ':' + assetId, + assetId: assetId, + mindmapId: assetId, + title: String(detail && detail.title || localFileName || '思维导图').trim(), + fileName: localFileName, + kind: 'mindmap', + documentId: String(detail && detail.documentId || currentDocumentId() || '').trim(), + workspaceId: String(detail.workspaceId || '').trim() + }); + return; + } + 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 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, + title: localFileName, + kind: 'office', + assetId: assetId, + documentId: String(detail && detail.documentId || currentDocumentId() || '').trim(), + workspaceId: String(detail.workspaceId || '').trim(), + officeUrl: localOfficeUrl + })) return; + window.open(localOfficeUrl, '_blank', 'noopener,noreferrer'); + return; + } + var localFileUrl = buildLocalFileOpenUrl(localFilePath, false); + if (localFileUrl) { + var shouldOpenInNewWindow = forceNewWindow || shouldOpenLocalResourceInNewWindow(localFileName); + if (!shouldOpenInNewWindow && await openLocalResourceInActiveTab({ + path: localFilePath, + title: localFileName, + kind: fileTreeIconKindForFileName(localFileName), + assetId: assetId, + documentId: String(detail && detail.documentId || currentDocumentId() || '').trim(), + workspaceId: String(detail.workspaceId || '').trim(), + href: localFileUrl + })) return; + window.open(localFileUrl, '_blank', 'noopener,noreferrer'); + } + return; + } + var documentId = String(detail && detail.documentId || '').trim(); + if (isMindmapAssetDetail(detail) && documentId) { + if (typeof window.__mnoteDocumentPaneRuntime?.openResourceInActiveTab === 'function') { + document.documentElement.setAttribute('data-mnote-last-mindmap-asset-open-mode', 'mindmap-resource-tab'); + document.documentElement.setAttribute('data-mnote-last-mindmap-asset-id', assetId); + void window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({ + objectIdentity: 'resource:mindmap:' + documentId + ':' + assetId, + assetId: assetId, + mindmapId: assetId, + title: String(detail.title || '思维导图').trim(), + fileName: String(detail.title || '思维导图').trim(), + kind: 'mindmap', + documentId: documentId, + workspaceId: String(detail.workspaceId || '').trim() + }); + return; + } + document.documentElement.setAttribute('data-mnote-last-mindmap-asset-open-mode', 'mindmap-object-shell'); + document.documentElement.setAttribute('data-mnote-last-mindmap-asset-id', assetId); + navigateToMindmapObject(documentId, assetId, String(detail.workspaceId || '').trim()); + return; + } + try { + var response = await fetch('/api/media/sign?assetId=' + encodeURIComponent(assetId), { + method: 'GET', + credentials: 'include' + }); + var payload = await response.json().catch(function() { return null; }); + if (!response.ok) { + throw new Error(payload && payload.error ? payload.error : '生成签名链接失败'); + } + var asset = payload && payload.asset && typeof payload.asset === 'object' ? payload.asset : {}; + var fileUrl = String(payload && payload.signedUrl || asset.file_url || asset.signed_url || '').trim(); + if (!fileUrl) throw new Error('附件链接不可用'); + var fileName = String(asset.file_name || detail.fileName || '未命名资源').trim() || '未命名资源'; + var fileType = inferOnlyOfficeFileType(fileName, asset.mime_type); + if (fileType) { + var userId = await fetchCurrentOnlyOfficeUserId(); + var officeUrl = buildOnlyOfficeOpenUrl({ + fileUrl: fileUrl, + fileName: fileName, + fileType: fileType, + assetId: assetId, + documentId: String(asset.document_id || detail.documentId || '').trim(), + userId: userId, + mode: forceEditMode ? 'edit' : 'view' + }); + if (typeof window.__mnoteDocumentPaneRuntime?.openResourceInActiveTab === 'function') { + var didOpen = await window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({ + objectIdentity: 'resource:onlyoffice:' + String(asset.document_id || detail.documentId || '').trim() + ':' + assetId, + assetId: assetId, + title: fileName, + fileName: fileName, + kind: 'office', + officeUrl: officeUrl, + documentId: String(asset.document_id || detail.documentId || '').trim(), + workspaceId: String(detail.workspaceId || '').trim() + }); + if (didOpen) return; + } + window.open(officeUrl, '_blank', 'noopener,noreferrer'); + return; + } + if (isCodeAttachmentFileName(fileName) && String(asset.document_id || detail.documentId || '').trim() === currentDocumentId()) { + await openCodeEditorAttachment({ + href: fileUrl, + fileUrl: fileUrl, + fileName: fileName, + assetId: assetId, + documentId: String(asset.document_id || detail.documentId || '').trim(), + fileSize: uploadedFileSize(asset) + }); + return; + } + window.open(fileUrl, '_blank', 'noopener,noreferrer'); + } catch (error) { + window.alert(error && error.message ? error.message : '打开附件失败'); + } + } + + window.addEventListener('tree.asset.open', function(event) { + void openConvexAssetFromFileTree(event.detail || {}); + }); + + + return { + buildLocalFileOpenUrl, + buildLocalOnlyOfficeOpenUrl, + buildMindmapOpenPath, + buildOnlyOfficeOpenPath, + buildOnlyOfficeOpenUrl, + fetchCurrentOnlyOfficeUserId, + inferOnlyOfficeFileType, + isMindmapAssetDetail, + localFilePathFromAssetId, + navigateToMindmapObject, + openConvexAssetFromFileTree, + openLocalOfficeFileInActiveTab, + openLocalResourceInActiveTab, + readFileTreeObjectIdentity, + }; +}; diff --git a/rust/crates/mnote-web/browser/sidebar-tree-runtime.js b/rust/crates/mnote-web/browser/sidebar-tree-runtime.js index b93a662b..580af23e 100644 --- a/rust/crates/mnote-web/browser/sidebar-tree-runtime.js +++ b/rust/crates/mnote-web/browser/sidebar-tree-runtime.js @@ -1,5 +1,6 @@ import { createSidebarWorkspaceRuntime } from './sidebar-workspace-runtime.js'; import { createSidebarTreeLiveApplyRuntime } from './sidebar-tree-live-apply-runtime.js'; +import { createSidebarFileTreeOpenRuntime } from './sidebar-filetree-open-runtime.js'; (function(){ if (window.__mnoteSidebarTreeRuntimeStarted) return; @@ -849,393 +850,34 @@ import { createSidebarTreeLiveApplyRuntime } from './sidebar-tree-live-apply-run window.dispatchEvent(new CustomEvent(name, { detail: detail })); } - function inferOnlyOfficeFileType(fileName, mimeType) { - var name = String(fileName || '').trim().toLowerCase(); - var mt = String(mimeType || '').trim().toLowerCase(); - var ext = name.indexOf('.') >= 0 ? name.split('.').pop() : ''; - if (['doc', 'docx', 'odt', 'rtf'].indexOf(ext) >= 0) return ext; - if (['ppt', 'pptx', 'odp'].indexOf(ext) >= 0) return ext; - if (['xls', 'xlsx', 'ods', 'csv'].indexOf(ext) >= 0) return ext; - if (isNonOfficeAttachmentName(name, ext)) return ''; - if (mt.indexOf('wordprocessingml') >= 0) return 'docx'; - if (mt.indexOf('presentationml') >= 0) return 'pptx'; - if (mt.indexOf('spreadsheetml') >= 0) return 'xlsx'; - return ''; - } - - function buildOnlyOfficeOpenUrl(input) { - var target = new URL('/onlyoffice', window.location.origin); - var fileUrl = String(input.fileUrl || '').trim(); - if (fileUrl) { - try { - fileUrl = new URL(fileUrl, window.location.origin).toString(); - } catch (_error) {} - } - target.searchParams.set('fileUrl', fileUrl); - target.searchParams.set('fileName', input.fileName || '未命名资源'); - target.searchParams.set('fileType', input.fileType || 'docx'); - if (input.assetId) target.searchParams.set('assetId', input.assetId); - if (input.documentId) target.searchParams.set('documentId', input.documentId); - if (input.userId) target.searchParams.set('userId', input.userId); - target.searchParams.set('mode', input.mode || 'view'); - return target.toString(); - } - - function buildOnlyOfficeOpenPath(input) { - var _rto_ = window.__mnoteResourceOpenRuntime; - if (_rto_ && typeof _rto_.buildOnlyOfficeOpenPath === 'function') { - return _rto_.buildOnlyOfficeOpenPath(input); - } - var params = new URLSearchParams(); - params.set('fileUrl', input.fileUrl || ''); - params.set('fileName', input.fileName || '未命名资源'); - params.set('fileType', input.fileType || 'docx'); - if (input.assetId) params.set('assetId', input.assetId); - if (input.documentId) params.set('documentId', input.documentId); - if (input.userId) params.set('userId', input.userId); - params.set('mode', input.mode || 'view'); - if (input.documentId && input.assetId) { - return '/office/' + encodeURIComponent(input.documentId) + '/' + encodeURIComponent(input.assetId) + '?' + params.toString(); - } - return '/onlyoffice?' + params.toString(); - } - - function buildLocalOnlyOfficeOpenUrl(relativePath, fileName, documentId, assetId, mode) { - 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: mode || 'view' - }); - } - - async function openLocalOfficeFileInActiveTab(detail, mode) { - var localFilePath = localFilePathFromAssetId(detail.assetId); - if (!localFilePath) return false; - var localFileName = localFilePath.split('/').pop() || localFilePath; - var localOfficeUrl = buildLocalOnlyOfficeOpenUrl(localFilePath, localFileName, String(detail.documentId || currentDocumentId() || '').trim(), detail.assetId, mode || 'view'); - if (!localOfficeUrl) return false; - var opened = await openLocalResourceInActiveTab({ - path: localFilePath, - title: detail.fileName || localFileName, - kind: 'office', - assetId: detail.assetId, - documentId: detail.documentId || currentDocumentId() || '', - workspaceId: detail.workspaceId || resolveWorkspaceId(document.body) || '', - href: buildLocalFileOpenUrl(localFilePath, false), - officeUrl: localOfficeUrl, - paneRole: detail.paneRole || 'primary' - }); - if (!opened) window.open(localOfficeUrl, '_blank', 'noopener,noreferrer'); - return true; - } - - function buildMindmapOpenPath(documentId, assetId) { - var doc = String(documentId || '').trim(); - var map = String(assetId || '').trim(); - if (!doc || !map) return ''; - var targetUrl = new URL('/mindmap/' + encodeURIComponent(doc) + '/' + encodeURIComponent(map), window.location.origin); - copyWorkspaceSourceParams(targetUrl); - return targetUrl.pathname + targetUrl.search; - } - - function navigateToMindmapObject(documentId, assetId, workspaceId) { - var mindmapPath = buildMindmapOpenPath(documentId, assetId); - if (!mindmapPath) return false; - var targetUrl = new URL(mindmapPath, window.location.origin); - var url = targetUrl.pathname + targetUrl.search; - if (mnoteNavigationInFlight === url) return true; - if (typeof window.__mnoteDocumentPaneRuntime?.openPrimaryMindmap === 'function') { - mnoteNavigationInFlight = url; - document.documentElement.setAttribute('data-mnote-navigation-pending', 'true'); - document.documentElement.setAttribute('data-mnote-navigation-target', assetId); - window.__mnoteDocumentPaneRuntime.openPrimaryMindmap({ - documentId: documentId, - mindmapId: assetId, - workspaceId: workspaceId || '', - url: targetUrl - }).then(function(){ - if (mnoteNavigationInFlight === url) mnoteNavigationInFlight = ''; - document.documentElement.removeAttribute('data-mnote-navigation-pending'); - }).catch(function(error){ - console.warn('mnote mindmap pane 内导航失败,将回退整页导航', error); - if (window.__mnoteTreeLiveEventSource && typeof window.__mnoteTreeLiveEventSource.close === 'function') { - window.__mnoteTreeLiveEventSource.close(); - } - window.location.assign(url); - }); - return true; - } - mnoteNavigationInFlight = url; - document.documentElement.setAttribute('data-mnote-navigation-pending', 'true'); - document.documentElement.setAttribute('data-mnote-navigation-target', assetId); - if (window.__mnoteTreeLiveEventSource && typeof window.__mnoteTreeLiveEventSource.close === 'function') { - window.__mnoteTreeLiveEventSource.close(); - } - window.location.assign(url); - return true; - } - - function isMindmapAssetDetail(detail) { - var assetId = String(detail && detail.assetId || '').trim(); - var assetType = String(detail && detail.assetType || '').trim(); - if (assetType === 'mindmap') return true; - 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) { - var _rto_ = window.__mnoteResourceOpenRuntime; - if (_rto_ && typeof _rto_.localFilePathFromAssetId === 'function') { - return _rto_.localFilePathFromAssetId(assetId); - } - var value = String(assetId || '').trim(); - return value.indexOf('local-file:') === 0 ? value.slice('local-file:'.length) : value.indexOf('local:asset:') === 0 ? value.slice('local:asset:'.length) : ''; - } - - function buildLocalFileOpenUrl(relativePath, download) { - var _rto_ = window.__mnoteResourceOpenRuntime; - if (_rto_ && typeof _rto_.buildLocalFileOpenUrl === 'function') { - return _rto_.buildLocalFileOpenUrl(relativePath, download); - } - var rootUri = currentRootUri(); - if (!rootUri || !relativePath) return ''; - var url = new URL('/api/local-folder/files/open', window.location.origin); - url.searchParams.set('rootUri', rootUri); - url.searchParams.set('path', relativePath); - if (download) url.searchParams.set('download', 'true'); - return url.toString(); - } - - async function openLocalResourceInActiveTab(input) { - if (typeof window.__mnoteDocumentPaneRuntime?.openResourceInActiveTab !== 'function') return false; - var relativePath = String(input && input.path || '').trim(); - var rootUri = String(input && input.rootUri || currentRootUri() || '').trim(); - if (!relativePath || !rootUri) return false; - var title = String(input && input.title || '').trim() || relativePath.split('/').pop() || relativePath; - var kind = String(input && input.kind || fileTreeIconKindForFileName(title) || '').trim(); - var objectIdentity = 'resource:file:' + rootUri + ':' + relativePath; - return await window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({ - objectIdentity: objectIdentity, - assetId: String(input && input.assetId || '').trim() || 'local-file:' + relativePath, - title: title, - fileName: title, - kind: kind, - rootUri: rootUri, - path: relativePath, - 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(), - paneRole: String(input && input.paneRole || 'primary').trim() || 'primary' - }); - } - - function readFileTreeObjectIdentity(row) { - if (!row) return null; - var raw = row.getAttribute('data-object-identity') || ''; - if (!raw) return null; - try { - var parsed = JSON.parse(raw); - return parsed && typeof parsed === 'object' ? parsed : null; - } catch (_error) { - return null; - } - } - - async function fetchCurrentOnlyOfficeUserId() { - try { - var response = await fetch('/api/auth/whoami', { - method: 'GET', - credentials: 'include', - cache: 'no-store' - }); - var payload = await response.json().catch(function() { return null; }); - return String(payload && payload.userId || '').trim(); - } catch (_error) { - return ''; - } - } - - async function openConvexAssetFromFileTree(detail) { - var assetId = String(detail && detail.assetId || '').trim(); - if (!assetId) return; - var openTarget = String(detail && detail.openTarget || '').trim().toLowerCase(); - var forceNewWindow = openTarget === 'new-window'; - 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: sideLocalFilePath && sideRootUri ? 'resource:file:' + sideRootUri + ':' + sideLocalFilePath : String(detail.objectIdentity || detail.assetId || ''), - assetId: assetId, - 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) || '') - }); - } - return; - } - var localFilePath = localFilePathFromAssetId(assetId); - if (localFilePath) { - var localFileName = localFilePath.split('/').pop() || localFilePath; - if (isMindmapAssetDetail(detail) || String(detail && detail.assetType || '').trim() === 'mindmap') { - if (typeof window.__mnoteDocumentPaneRuntime?.openResourceInActiveTab === 'function') { - document.documentElement.setAttribute('data-mnote-last-mindmap-asset-open-mode', 'mindmap-resource-tab'); - document.documentElement.setAttribute('data-mnote-last-mindmap-asset-id', assetId); - void window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({ - objectIdentity: 'resource:mindmap:' + String(detail && detail.documentId || currentDocumentId() || '').trim() + ':' + assetId, - assetId: assetId, - mindmapId: assetId, - title: String(detail && detail.title || localFileName || '思维导图').trim(), - fileName: localFileName, - kind: 'mindmap', - documentId: String(detail && detail.documentId || currentDocumentId() || '').trim(), - workspaceId: String(detail.workspaceId || '').trim() - }); - return; - } - 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 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, - title: localFileName, - kind: 'office', - assetId: assetId, - documentId: String(detail && detail.documentId || currentDocumentId() || '').trim(), - workspaceId: String(detail.workspaceId || '').trim(), - officeUrl: localOfficeUrl - })) return; - window.open(localOfficeUrl, '_blank', 'noopener,noreferrer'); - return; - } - var localFileUrl = buildLocalFileOpenUrl(localFilePath, false); - if (localFileUrl) { - var shouldOpenInNewWindow = forceNewWindow || shouldOpenLocalResourceInNewWindow(localFileName); - if (!shouldOpenInNewWindow && await openLocalResourceInActiveTab({ - path: localFilePath, - title: localFileName, - kind: fileTreeIconKindForFileName(localFileName), - assetId: assetId, - documentId: String(detail && detail.documentId || currentDocumentId() || '').trim(), - workspaceId: String(detail.workspaceId || '').trim(), - href: localFileUrl - })) return; - window.open(localFileUrl, '_blank', 'noopener,noreferrer'); - } - return; - } - var documentId = String(detail && detail.documentId || '').trim(); - if (isMindmapAssetDetail(detail) && documentId) { - if (typeof window.__mnoteDocumentPaneRuntime?.openResourceInActiveTab === 'function') { - document.documentElement.setAttribute('data-mnote-last-mindmap-asset-open-mode', 'mindmap-resource-tab'); - document.documentElement.setAttribute('data-mnote-last-mindmap-asset-id', assetId); - void window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({ - objectIdentity: 'resource:mindmap:' + documentId + ':' + assetId, - assetId: assetId, - mindmapId: assetId, - title: String(detail.title || '思维导图').trim(), - fileName: String(detail.title || '思维导图').trim(), - kind: 'mindmap', - documentId: documentId, - workspaceId: String(detail.workspaceId || '').trim() - }); - return; - } - document.documentElement.setAttribute('data-mnote-last-mindmap-asset-open-mode', 'mindmap-object-shell'); - document.documentElement.setAttribute('data-mnote-last-mindmap-asset-id', assetId); - navigateToMindmapObject(documentId, assetId, String(detail.workspaceId || '').trim()); - return; - } - try { - var response = await fetch('/api/media/sign?assetId=' + encodeURIComponent(assetId), { - method: 'GET', - credentials: 'include' - }); - var payload = await response.json().catch(function() { return null; }); - if (!response.ok) { - throw new Error(payload && payload.error ? payload.error : '生成签名链接失败'); - } - var asset = payload && payload.asset && typeof payload.asset === 'object' ? payload.asset : {}; - var fileUrl = String(payload && payload.signedUrl || asset.file_url || asset.signed_url || '').trim(); - if (!fileUrl) throw new Error('附件链接不可用'); - var fileName = String(asset.file_name || detail.fileName || '未命名资源').trim() || '未命名资源'; - var fileType = inferOnlyOfficeFileType(fileName, asset.mime_type); - if (fileType) { - var userId = await fetchCurrentOnlyOfficeUserId(); - var officeUrl = buildOnlyOfficeOpenUrl({ - fileUrl: fileUrl, - fileName: fileName, - fileType: fileType, - assetId: assetId, - documentId: String(asset.document_id || detail.documentId || '').trim(), - userId: userId, - mode: forceEditMode ? 'edit' : 'view' - }); - if (typeof window.__mnoteDocumentPaneRuntime?.openResourceInActiveTab === 'function') { - var didOpen = await window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({ - objectIdentity: 'resource:onlyoffice:' + String(asset.document_id || detail.documentId || '').trim() + ':' + assetId, - assetId: assetId, - title: fileName, - fileName: fileName, - kind: 'office', - officeUrl: officeUrl, - documentId: String(asset.document_id || detail.documentId || '').trim(), - workspaceId: String(detail.workspaceId || '').trim() - }); - if (didOpen) return; - } - window.open(officeUrl, '_blank', 'noopener,noreferrer'); - return; - } - if (isCodeAttachmentFileName(fileName) && String(asset.document_id || detail.documentId || '').trim() === currentDocumentId()) { - await openCodeEditorAttachment({ - href: fileUrl, - fileUrl: fileUrl, - fileName: fileName, - assetId: assetId, - documentId: String(asset.document_id || detail.documentId || '').trim(), - fileSize: uploadedFileSize(asset) - }); - return; - } - window.open(fileUrl, '_blank', 'noopener,noreferrer'); - } catch (error) { - window.alert(error && error.message ? error.message : '打开附件失败'); - } - } - - window.addEventListener('tree.asset.open', function(event) { - void openConvexAssetFromFileTree(event.detail || {}); + const sidebarFileTreeOpen = createSidebarFileTreeOpenRuntime({ + copyWorkspaceSourceParams, + currentDocumentId, + currentRootUri, + fileTreeIconKindForFileName: (...args) => fileTreeIconKindForFileName(...args), + getNavigationInFlight: () => mnoteNavigationInFlight, + isCodeAttachmentFileName: (...args) => isCodeAttachmentFileName(...args), + isNonOfficeAttachmentName: (...args) => isNonOfficeAttachmentName(...args), + openCodeEditorAttachment: (...args) => openCodeEditorAttachment(...args), + resolveWorkspaceId, + setNavigationInFlight: (value) => { mnoteNavigationInFlight = String(value || ''); }, + shouldOpenLocalResourceInNewWindow: (...args) => shouldOpenLocalResourceInNewWindow(...args), + uploadedFileSize: (...args) => uploadedFileSize(...args), }); + const inferOnlyOfficeFileType = (...args) => sidebarFileTreeOpen.inferOnlyOfficeFileType(...args); + const buildOnlyOfficeOpenUrl = (...args) => sidebarFileTreeOpen.buildOnlyOfficeOpenUrl(...args); + const buildOnlyOfficeOpenPath = (...args) => sidebarFileTreeOpen.buildOnlyOfficeOpenPath(...args); + const buildLocalOnlyOfficeOpenUrl = (...args) => sidebarFileTreeOpen.buildLocalOnlyOfficeOpenUrl(...args); + const openLocalOfficeFileInActiveTab = (...args) => sidebarFileTreeOpen.openLocalOfficeFileInActiveTab(...args); + const buildMindmapOpenPath = (...args) => sidebarFileTreeOpen.buildMindmapOpenPath(...args); + const navigateToMindmapObject = (...args) => sidebarFileTreeOpen.navigateToMindmapObject(...args); + const isMindmapAssetDetail = (...args) => sidebarFileTreeOpen.isMindmapAssetDetail(...args); + const localFilePathFromAssetId = (...args) => sidebarFileTreeOpen.localFilePathFromAssetId(...args); + const buildLocalFileOpenUrl = (...args) => sidebarFileTreeOpen.buildLocalFileOpenUrl(...args); + const openLocalResourceInActiveTab = (...args) => sidebarFileTreeOpen.openLocalResourceInActiveTab(...args); + const readFileTreeObjectIdentity = (...args) => sidebarFileTreeOpen.readFileTreeObjectIdentity(...args); + const fetchCurrentOnlyOfficeUserId = (...args) => sidebarFileTreeOpen.fetchCurrentOnlyOfficeUserId(...args); + const openConvexAssetFromFileTree = (...args) => sidebarFileTreeOpen.openConvexAssetFromFileTree(...args); function fileTreeRowsForUploadPreflight() { var runtimeFn = fileTreeRuntimeFunction('fileTreeRowsForUploadPreflight'); diff --git a/rust/crates/mnote-web/src/routes/mod.rs b/rust/crates/mnote-web/src/routes/mod.rs index d3ce4821..a9044691 100644 --- a/rust/crates/mnote-web/src/routes/mod.rs +++ b/rust/crates/mnote-web/src/routes/mod.rs @@ -134,6 +134,10 @@ pub fn build_router(state: AppState) -> Router { "/api/mnote-browser-runtime/sidebar-tree-live-apply-runtime.js", get(web_shell::sidebar_tree_live_apply_runtime_asset), ) + .route( + "/api/mnote-browser-runtime/sidebar-filetree-open-runtime.js", + get(web_shell::sidebar_filetree_open_runtime_asset), + ) .route( "/api/mnote-browser-runtime/sidebar-tree-runtime.js", get(web_shell::sidebar_tree_runtime_asset), @@ -589,6 +593,7 @@ mod tests { "/api/mnote-browser-runtime/sidebar-shell-runtime.js", "/api/mnote-browser-runtime/sidebar-workspace-runtime.js", "/api/mnote-browser-runtime/sidebar-tree-live-apply-runtime.js", + "/api/mnote-browser-runtime/sidebar-filetree-open-runtime.js", "/api/mnote-browser-runtime/sidebar-tree-runtime.js", "/api/mnote-browser-runtime/tree-live-controller.js", "/api/mnote-browser-runtime/tree-shell-runtime.js", diff --git a/rust/crates/mnote-web/src/routes/web_shell.rs b/rust/crates/mnote-web/src/routes/web_shell.rs index 29d1b11a..99796c38 100644 --- a/rust/crates/mnote-web/src/routes/web_shell.rs +++ b/rust/crates/mnote-web/src/routes/web_shell.rs @@ -770,6 +770,20 @@ pub async fn sidebar_tree_live_apply_runtime_asset() -> Response { .unwrap_or_else(|_| Response::new(Body::empty())) } +pub async fn sidebar_filetree_open_runtime_asset() -> Response { + const JS: &str = include_str!("../../browser/sidebar-filetree-open-runtime.js"); + Response::builder() + .status(StatusCode::OK) + .header( + header::CONTENT_TYPE, + "application/javascript; charset=utf-8", + ) + .header(header::CACHE_CONTROL, "no-store") + .header(HEADER_MNOTE_WEB_OWNER, "mnote-web") + .body(Body::from(JS)) + .unwrap_or_else(|_| Response::new(Body::empty())) +} + pub async fn filetree_keyboard_runtime_asset() -> Response { const JS: &str = include_str!("../../browser/filetree-keyboard-runtime.js"); Response::builder() diff --git a/rust/crates/mnote-web/src/ssr/pages/layout.rs b/rust/crates/mnote-web/src/ssr/pages/layout.rs index f7942cf5..30886ce3 100644 --- a/rust/crates/mnote-web/src/ssr/pages/layout.rs +++ b/rust/crates/mnote-web/src/ssr/pages/layout.rs @@ -201,6 +201,8 @@ mod tests { const SIDEBAR_TREE_RUNTIME_JS: &str = include_str!("../../../browser/sidebar-tree-runtime.js"); const SIDEBAR_TREE_LIVE_APPLY_RUNTIME_JS: &str = include_str!("../../../browser/sidebar-tree-live-apply-runtime.js"); + const SIDEBAR_FILETREE_OPEN_RUNTIME_JS: &str = + include_str!("../../../browser/sidebar-filetree-open-runtime.js"); const SIDEBAR_SHELL_RUNTIME_JS: &str = include_str!("../../../browser/sidebar-shell-runtime.js"); const SIDEBAR_WORKSPACE_RUNTIME_JS: &str = @@ -279,29 +281,33 @@ mod tests { assert!(SIDEBAR_TREE_RUNTIME_JS.contains("openConvexAssetFromFileTree")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("buildMindmapOpenPath")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("isMindmapAssetDetail")); - assert!(SIDEBAR_TREE_RUNTIME_JS.contains("^思维导图")); + assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains("^思维导图")); assert!(!SIDEBAR_TREE_RUNTIME_JS.contains("openMindmapAssetInDocumentShell")); - assert!(SIDEBAR_TREE_RUNTIME_JS.contains("data-mnote-last-mindmap-asset-open-mode")); - assert!(SIDEBAR_TREE_RUNTIME_JS.contains("mindmap-object-shell")); - assert!(SIDEBAR_TREE_RUNTIME_JS.contains("navigateToMindmapObject(documentId, assetId")); - assert!(SIDEBAR_TREE_RUNTIME_JS.contains("__mnoteDocumentPaneRuntime?.openPrimaryMindmap")); + assert!( + SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains("data-mnote-last-mindmap-asset-open-mode") + ); + assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains("mindmap-object-shell")); + assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS + .contains("navigateToMindmapObject(documentId, assetId")); + assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS + .contains("__mnoteDocumentPaneRuntime?.openPrimaryMindmap")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("shortMindmapFileName")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("assetType: assetType || null")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("data-object-identity")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("readFileTreeObjectIdentity")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("objectIdentity: objectIdentity")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("workspaceId: resolveWorkspaceId(fileRow)")); - assert!(SIDEBAR_TREE_RUNTIME_JS.contains("/api/media/sign?assetId=")); - assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function localFilePathFromAssetId")); - assert!(SIDEBAR_TREE_RUNTIME_JS.contains("/api/local-folder/files/open")); - assert!(SIDEBAR_TREE_RUNTIME_JS.contains("local-file:")); - assert!(SIDEBAR_TREE_RUNTIME_JS + assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains("/api/media/sign?assetId=")); + assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains("function localFilePathFromAssetId")); + assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains("/api/local-folder/files/open")); + assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains("local-file:")); + assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS .contains("value.indexOf('local:asset:') === 0 ? value.slice('local:asset:'.length)")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("fetchCurrentOnlyOfficeUserId")); - assert!(SIDEBAR_TREE_RUNTIME_JS.contains("/api/auth/whoami")); + assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains("/api/auth/whoami")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("buildOnlyOfficeOpenUrl")); - assert!(SIDEBAR_TREE_RUNTIME_JS.contains("target.searchParams.set('userId'")); - assert!(SIDEBAR_TREE_RUNTIME_JS.contains("window.open(officeUrl,")); + assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains("target.searchParams.set('userId'")); + assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains("window.open(officeUrl,")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("tree.filetree.internal-drop")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("tree.filetree.external-drop")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("beginFileTreeInlineRename")); @@ -805,7 +811,7 @@ mod tests { assert!(SIDEBAR_TREE_RUNTIME_JS.contains("url.searchParams.set('rootUri', rootUri)")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("if (!parentRow && currentSourceKind() === 'local_folder' && objectKind === 'mindmap') return false;")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("if (!appended && currentSourceKind() === 'local_folder') void refreshLocalFolderSidebarSnapshot();")); - assert!(SIDEBAR_TREE_RUNTIME_JS + assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS .contains("data-mnote-last-mindmap-asset-open-mode', 'local-mindmap-object-shell'")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("buildLocalOnlyOfficeOpenUrl")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("isLocalAsset ? onlyOfficeUrl")); @@ -858,8 +864,8 @@ mod tests { assert!(SIDEBAR_TREE_RUNTIME_JS .contains("function shouldOpenLocalResourceInNewWindow(fileName)")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("return false;")); - assert!(SIDEBAR_TREE_RUNTIME_JS.contains("var shouldOpenInNewWindow = forceNewWindow || shouldOpenLocalResourceInNewWindow(localFileName);")); - assert!(SIDEBAR_TREE_RUNTIME_JS + assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains("var shouldOpenInNewWindow = forceNewWindow || shouldOpenLocalResourceInNewWindow(localFileName);")); + assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS .contains("if (!shouldOpenInNewWindow && await openLocalResourceInActiveTab({")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains( "openTarget: e.shiftKey || e.ctrlKey || e.metaKey ? 'new-window' : 'active-tab'" @@ -1099,10 +1105,10 @@ mod tests { #[test] fn sidebar_tree_runtime_keeps_pdf_and_code_assets_out_of_onlyoffice() { - assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function inferOnlyOfficeFileType")); - assert!(SIDEBAR_TREE_RUNTIME_JS.contains("isNonOfficeAttachmentName(name, ext)")); - assert!(!SIDEBAR_TREE_RUNTIME_JS.contains("if (ext === 'pdf') return ext;")); - assert!(!SIDEBAR_TREE_RUNTIME_JS.contains("mt.indexOf('pdf') >= 0")); + assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains("function inferOnlyOfficeFileType")); + assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains("isNonOfficeAttachmentName(name, ext)")); + assert!(!SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains("if (ext === 'pdf') return ext;")); + assert!(!SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains("mt.indexOf('pdf') >= 0")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("mnote-uploaded-attachment-pdf")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("mnote-uploaded-attachment-code")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("'toml'")); @@ -1118,14 +1124,18 @@ mod tests { #[test] fn sidebar_tree_runtime_opens_office_assets_through_resource_shell() { - assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function buildOnlyOfficeOpenPath")); - assert!(SIDEBAR_TREE_RUNTIME_JS.contains( + assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains("function buildOnlyOfficeOpenPath")); + assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains( "return '/office/' + encodeURIComponent(input.documentId) + '/' + encodeURIComponent(input.assetId)" )); - assert!(SIDEBAR_TREE_RUNTIME_JS.contains("return '/onlyoffice?' + params.toString();")); - assert!(SIDEBAR_TREE_RUNTIME_JS + assert!( + SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains("return '/onlyoffice?' + params.toString();") + ); + assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS .contains("target.searchParams.set('mode', input.mode || 'view');")); - assert!(SIDEBAR_TREE_RUNTIME_JS.contains("params.set('mode', input.mode || 'view');")); + assert!( + SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains("params.set('mode', input.mode || 'view');") + ); assert!(SIDEBAR_TREE_RUNTIME_JS.contains( "{ action: 'open-edit-mode', icon: 'edit_note', label: '使用编辑模式打开' }" )); @@ -1139,7 +1149,7 @@ mod tests { assert!(SIDEBAR_TREE_RUNTIME_JS.contains("void openEditorAttachmentEditTab(detail);")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("openLocalOfficeFileInActiveTab(detail, 'edit')")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("openEditorAttachmentNewWindow(detail, 'edit')")); - assert!(SIDEBAR_TREE_RUNTIME_JS.contains("forceEditMode ? 'edit' : 'view'")); + assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains("forceEditMode ? 'edit' : 'view'")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("url.searchParams.set('mode', requestedMode);")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("mode: requestedMode")); } @@ -1158,9 +1168,9 @@ mod tests { assert!(SIDEBAR_TREE_RUNTIME_JS.contains("inferCodeAttachmentLanguage")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("if (isPdfAttachmentFileName(detail.fileName))")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("if (isCodeAttachmentFileName(detail.fileName))")); - assert!(SIDEBAR_TREE_RUNTIME_JS + assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS .contains("if (isCodeAttachmentFileName(fileName) && String(asset.document_id")); - assert!(SIDEBAR_TREE_RUNTIME_JS.contains("await openCodeEditorAttachment({")); + assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains("await openCodeEditorAttachment({")); } #[test]