export const createSidebarAttachmentOpenRuntime = (dependencies = {}) => { const { attachmentClassForFileName, buildLocalFileOpenUrl, buildLocalOnlyOfficeOpenUrl, buildOnlyOfficeOpenPath, buildOnlyOfficeOpenUrl, buildPdfPreviewOpenUrl: injectedBuildPdfPreviewOpenUrl, closestAction: injectedClosestAction, currentDocumentId, currentRootUri, currentWorkspaceSourcePayload, fileTreeIconKindForFileName, hydrateEditorAttachmentMeta: injectedHydrateEditorAttachmentMeta, inferCodeAttachmentLanguage, inferOnlyOfficeFileType, isCodeAttachmentFileName, isPdfAttachmentFileName, localFilePathFromAssetId, openLocalOfficeFileInActiveTab, openLocalResourceInActiveTab, openTreeContextMenu, resolveWorkspaceId, triggerBrowserDownload, uploadedFileSize, } = dependencies; const closestAction = typeof injectedClosestAction === 'function' ? injectedClosestAction : function(target, selector) { var node = target && target.nodeType === Node.TEXT_NODE ? target.parentElement : target; return node && typeof node.closest === 'function' ? node.closest(selector) : null; }; const hydrateEditorAttachmentMeta = typeof injectedHydrateEditorAttachmentMeta === 'function' ? injectedHydrateEditorAttachmentMeta : async function() {}; var activeEditorAttachmentLink = null; var attachmentActionsHideTimer = 0; var editorAttachmentMissingByKey = new Map(); var editorAttachmentRefreshSeqByKey = new Map(); var editorAttachmentLinkSelector = '.editor-surface .ProseMirror a.mnote-uploaded-attachment-row'; var editorAttachmentEnhanceSelector = '.editor-surface .ProseMirror a[href], ' + editorAttachmentLinkSelector; function attachmentQueryParams(href) { try { return new URL(String(href || ''), window.location.origin).searchParams; } catch (_) { return new URLSearchParams(); } } function localFileOpenPathFromHref(href) { try { var url = new URL(String(href || ''), window.location.origin); if (url.pathname !== '/api/local-folder/files/open') return ''; return String(url.searchParams.get('path') || '').trim(); } catch (_) { return ''; } } function decodeLocalEncodedPath(value) { var path = String(value || '').trim().replace(/~([0-9A-Fa-f]{2})/g, '%$1'); if (!path) return ''; try { return decodeURIComponent(path); } catch (_) { return path; } } function localMarkdownPathFromDocumentId(documentId) { var raw = String(documentId || '').trim(); if (raw.indexOf('local-md:') !== 0) return ''; return decodeLocalEncodedPath(raw.slice('local-md:'.length)); } function normalizeLocalRelativePath(path) { var parts = String(path || '').replace(/\\/g, '/').split('/'); var normalized = []; for (var i = 0; i < parts.length; i += 1) { var part = parts[i]; if (!part || part === '.') continue; if (part === '..') { if (normalized.length) normalized.pop(); continue; } normalized.push(part); } return normalized.join('/'); } function localMarkdownAttachmentPathFromHref(href, documentId) { var raw = String(href || '').trim(); if (!raw || raw.indexOf('#') === 0 || /^[a-z][a-z0-9+.-]*:/i.test(raw)) return ''; if (raw.indexOf('/documents/') === 0 || raw.indexOf('/api/') === 0) return ''; var documentPath = localMarkdownPathFromDocumentId(documentId); if (!documentPath) return ''; var slashIndex = documentPath.lastIndexOf('/'); var documentDir = slashIndex >= 0 ? documentPath.slice(0, slashIndex) : ''; var decoded = raw; try { decoded = decodeURIComponent(raw); } catch (_) {} if (decoded.indexOf('../') === 0 || decoded.indexOf('/../') >= 0) return ''; var joined = documentDir ? documentDir + '/' + decoded : decoded; return normalizeLocalRelativePath(joined); } function localFileOpenRootUriFromHref(href) { try { var url = new URL(String(href || ''), window.location.origin); if (url.pathname !== '/api/local-folder/files/open') return ''; return String(url.searchParams.get('rootUri') || '').trim(); } catch (_) { return ''; } } function localFileOpenKeyFromHref(href) { var localFilePath = localFileOpenPathFromHref(href); if (!localFilePath) return ''; var rootUri = localFileOpenRootUriFromHref(href) || currentRootUri() || ''; return String(rootUri || '').trim() + '\n' + localFilePath; } function buildLocalFileStatusUrl(relativePath, rootUri) { var effectiveRootUri = String(rootUri || currentRootUri() || '').trim(); if (!effectiveRootUri || !relativePath) return ''; var url = new URL('/api/local-folder/files/stat', window.location.origin); url.searchParams.set('rootUri', effectiveRootUri); url.searchParams.set('path', relativePath); return url.toString(); } function buildLocalFileOpenUrlForRoot(relativePath, rootUri, download) { var effectiveRootUri = String(rootUri || currentRootUri() || '').trim(); if (!effectiveRootUri || !relativePath) return ''; var url = new URL('/api/local-folder/files/open', window.location.origin); url.searchParams.set('rootUri', effectiveRootUri); url.searchParams.set('path', relativePath); if (download) url.searchParams.set('download', 'true'); return url.toString(); } function fileUriToPath(uri) { var value = String(uri || '').trim(); if (value.indexOf('file://') !== 0) return ''; try { var url = new URL(value); return decodeURIComponent(url.pathname || ''); } catch (_) { var raw = value.slice('file://'.length); if (raw.indexOf('localhost/') === 0) raw = raw.slice('localhost'.length); if (raw.charAt(0) !== '/') raw = '/' + raw; try { raw = decodeURIComponent(raw); } catch (_) {} return raw; } } function fileUriFromPath(path) { var value = String(path || '').trim(); if (!value) return ''; return 'file://' + value.split('/').map(function(part, index) { return index === 0 ? '' : encodeURIComponent(part); }).join('/'); } function localAttachmentOpenParts(rawHref, attachmentRef, paneContext) { if (attachmentRef && typeof attachmentRef === 'object') { var refRelativePath = String(attachmentRef.relativePath || '').trim(); if (refRelativePath) { return { rootUri: String(attachmentRef.ownerRootUri || currentRootUri() || '').trim(), relativePath: refRelativePath }; } var absolutePath = String(attachmentRef.resolvedAbsolutePath || '').trim() || fileUriToPath(attachmentRef.resolvedUri); if (absolutePath) { var slash = absolutePath.lastIndexOf('/'); if (slash > 0) { return { rootUri: fileUriFromPath(absolutePath.slice(0, slash)), relativePath: absolutePath.slice(slash + 1) }; } } } var apiPath = localFileOpenPathFromHref(rawHref); if (apiPath) { return { rootUri: localFileOpenRootUriFromHref(rawHref) || currentRootUri() || '', relativePath: apiPath }; } var markdownPath = localMarkdownAttachmentPathFromHref(rawHref, paneContext && paneContext.documentId); if (markdownPath) { return { rootUri: currentRootUri() || '', relativePath: markdownPath }; } return null; } function buildPdfPreviewOpenUrl(fileUrl, fileName) { if (typeof injectedBuildPdfPreviewOpenUrl === 'function') { return injectedBuildPdfPreviewOpenUrl(fileUrl, fileName); } var rawUrl = String(fileUrl || '').trim(); if (!rawUrl) return ''; var url = new URL('/pdf-preview', window.location.origin); url.searchParams.set('fileUrl', rawUrl); if (fileName) url.searchParams.set('fileName', fileName); return url.toString(); } function setEditorAttachmentMissingState(link, missing) { if (!(link instanceof HTMLAnchorElement)) return; var value = Boolean(missing); if (value) { if (link.getAttribute('data-mnote-attachment-missing') === 'true' && link.classList.contains('mnote-uploaded-attachment-missing')) return; setAttributeIfChanged(link, 'data-mnote-attachment-missing', 'true'); link.classList.add('mnote-uploaded-attachment-missing'); setAttributeIfChanged(link, 'aria-label', (link.textContent || '附件') + '(文件不存在)'); } else { if (link.getAttribute('data-mnote-attachment-missing') !== 'true') return; link.removeAttribute('data-mnote-attachment-missing'); link.classList.remove('mnote-uploaded-attachment-missing'); link.removeAttribute('aria-label'); } } function setEditorAttachmentUnauthorizedState(link, unauthorized) { if (!(link instanceof HTMLAnchorElement)) return; var value = Boolean(unauthorized); if (value) { if (link.getAttribute('data-mnote-attachment-unauthorized') === 'true' && link.classList.contains('mnote-uploaded-attachment-unauthorized')) return; setAttributeIfChanged(link, 'data-mnote-attachment-unauthorized', 'true'); link.classList.add('mnote-uploaded-attachment-unauthorized'); setAttributeIfChanged(link, 'aria-label', (link.textContent || '附件') + '(无权访问)'); } else { if (link.getAttribute('data-mnote-attachment-unauthorized') !== 'true') return; link.removeAttribute('data-mnote-attachment-unauthorized'); link.classList.remove('mnote-uploaded-attachment-unauthorized'); if (link.getAttribute('data-mnote-attachment-missing') !== 'true') link.removeAttribute('aria-label'); } } function setAttributeIfChanged(element, name, value) { var nextValue = String(value || ''); if (element.getAttribute(name) === nextValue) return; element.setAttribute(name, nextValue); } function setEditorAttachmentMissingStateByHref(href, missing, targetKeyOverride) { var targetKey = String(targetKeyOverride || '').trim() || localFileOpenKeyFromHref(href); if (!targetKey) return; if (missing) { editorAttachmentMissingByKey.set(targetKey, true); } else { editorAttachmentMissingByKey.delete(targetKey); } document.querySelectorAll(editorAttachmentEnhanceSelector + ', .editor-surface .ProseMirror a[href*="/api/local-folder/files/open"]').forEach(function(candidate) { if (!(candidate instanceof HTMLAnchorElement)) return; var candidateHref = candidate.getAttribute('href') || candidate.href || ''; var candidateRef = attachmentRefForHref(candidateHref, editorAttachmentPaneContext(candidate), candidate.textContent || ''); var candidateParts = localAttachmentOpenParts(candidateHref, candidateRef, editorAttachmentPaneContext(candidate)); var candidateKey = candidateParts ? String(candidateParts.rootUri || '').trim() + '\n' + String(candidateParts.relativePath || '').trim() : localFileOpenKeyFromHref(candidateHref); if (candidateKey !== targetKey) return; setEditorAttachmentMissingState(candidate, missing); }); } async function refreshLocalAttachmentExistence(link) { if (!(link instanceof HTMLAnchorElement)) return; if (link.classList.contains('mnote-page-block-link')) return; var href = link.getAttribute('href') || link.href || ''; var paneContext = editorAttachmentPaneContext(link); var attachmentRef = attachmentRefForHref(href, paneContext, link ? link.textContent : ''); var openParts = localAttachmentOpenParts(href, attachmentRef, paneContext); var attachmentKey = openParts ? String(openParts.rootUri || '').trim() + '\n' + String(openParts.relativePath || '').trim() : localFileOpenKeyFromHref(href); if (!attachmentKey) return; var refreshSeq = (editorAttachmentRefreshSeqByKey.get(attachmentKey) || 0) + 1; editorAttachmentRefreshSeqByKey.set(attachmentKey, refreshSeq); var localFilePath = openParts ? openParts.relativePath : localFileOpenPathFromHref(href); if (!localFilePath) return; var statusUrl = buildLocalFileStatusUrl(localFilePath, openParts ? openParts.rootUri : localFileOpenRootUriFromHref(href)); if (!statusUrl) return; try { var response = await fetch(statusUrl, { headers: { accept: 'application/json' }, cache: 'no-store' }); var payload = await response.json().catch(function() { return null; }); if (editorAttachmentRefreshSeqByKey.get(attachmentKey) !== refreshSeq) return; if (!response.ok || !payload || payload.ok !== true || !payload.result) { setEditorAttachmentStatusErrorByHref(href, String(response.status || 'stat_failed'), attachmentKey); return; } var exists = Boolean(response.ok && payload && payload.ok === true && payload.result && payload.result.exists === true); setEditorAttachmentStatusErrorByHref(href, '', attachmentKey); setEditorAttachmentMissingStateByHref(href, !exists, attachmentKey); } catch (_) {} } function setEditorAttachmentStatusErrorByHref(href, status, targetKeyOverride) { var targetKey = String(targetKeyOverride || '').trim() || localFileOpenKeyFromHref(href); if (!targetKey) return; document.querySelectorAll(editorAttachmentEnhanceSelector + ', .editor-surface .ProseMirror a[href*="/api/local-folder/files/open"]').forEach(function(candidate) { if (!(candidate instanceof HTMLAnchorElement)) return; var candidateHref = candidate.getAttribute('href') || candidate.href || ''; var candidateRef = attachmentRefForHref(candidateHref, editorAttachmentPaneContext(candidate), candidate.textContent || ''); var candidateParts = localAttachmentOpenParts(candidateHref, candidateRef, editorAttachmentPaneContext(candidate)); var candidateKey = candidateParts ? String(candidateParts.rootUri || '').trim() + '\n' + String(candidateParts.relativePath || '').trim() : localFileOpenKeyFromHref(candidateHref); if (candidateKey !== targetKey) return; if (status) { setAttributeIfChanged(candidate, 'data-mnote-attachment-status-error', status); } else { candidate.removeAttribute('data-mnote-attachment-status-error'); } }); } function refreshEditorLocalAttachmentExistence() { document.querySelectorAll(editorAttachmentEnhanceSelector + ', .editor-surface .ProseMirror a[href*="/api/local-folder/files/open"]').forEach(function(link) { if (link instanceof HTMLAnchorElement && link.classList.contains('mnote-page-block-link')) return; if (link instanceof HTMLAnchorElement) void refreshLocalAttachmentExistence(link); }); } window.__mnoteRefreshEditorLocalAttachmentExistence = refreshEditorLocalAttachmentExistence; var attachmentExistenceRefreshFrame = 0; function scheduleEditorLocalAttachmentExistenceRefresh() { if (attachmentExistenceRefreshFrame) return; attachmentExistenceRefreshFrame = window.requestAnimationFrame(function() { attachmentExistenceRefreshFrame = 0; refreshEditorLocalAttachmentExistence(); }); } function fileNameFromPath(path) { var value = String(path || '').trim(); return value.indexOf('/') >= 0 ? value.split('/').pop() : value; } function isOnlyOfficeAttachmentHref(href) { try { var url = new URL(String(href || ''), window.location.origin); return url.pathname === '/onlyoffice' && (url.searchParams.has('assetId') || url.searchParams.has('fileName')); } catch (_) { return false; } } function normalizeOnlyOfficeAttachmentHref(href) { try { var url = new URL(String(href || ''), window.location.origin); if (url.pathname !== '/onlyoffice') return String(href || ''); return buildOnlyOfficeOpenUrl({ fileUrl: url.searchParams.get('fileUrl') || '', fileName: url.searchParams.get('fileName') || '未命名附件', fileType: url.searchParams.get('fileType') || inferOnlyOfficeFileType(url.searchParams.get('fileName') || '', ''), assetId: url.searchParams.get('assetId') || '', documentId: url.searchParams.get('documentId') || currentDocumentId() || '', userId: url.searchParams.get('userId') || '', mode: url.searchParams.get('mode') || 'view' }); } catch (_) { return String(href || ''); } } function isOfficeFileName(fileName) { 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 currentPageAggregate() { var script = document.getElementById('__MNOTE_PAGE_AGGREGATE__'); if (!(script instanceof HTMLScriptElement)) return null; try { return JSON.parse(script.textContent || 'null'); } catch (_) { return null; } } function attachmentRefForHref(rawHref, paneContext, label) { var aggregate = currentPageAggregate(); var refs = Array.isArray(aggregate?.body?.attachmentRefs) ? aggregate.body.attachmentRefs : []; if (!refs.length) return null; var href = String(rawHref || '').trim(); var textLabel = String(label || '').trim(); var absoluteHref = ''; try { absoluteHref = new URL(href, window.location.href).href; } catch (_) {} var localPath = normalizeLocalRelativePath( localFileOpenPathFromHref(href) || localMarkdownAttachmentPathFromHref(href, paneContext && paneContext.documentId) ); return refs.find(function(ref) { if (!ref || typeof ref !== 'object') return false; var refRelativePath = normalizeLocalRelativePath(ref.relativePath || ''); var refNormalizedHref = normalizeLocalRelativePath(ref.normalizedHref || ref.rawHref || '').replace(/^\.\//, ''); return String(ref.rawHref || '') === href || String(ref.normalizedHref || '') === href || String(ref.resolvedUri || '') === href || (absoluteHref && String(ref.resolvedUri || '') === absoluteHref) || (localPath && refRelativePath && refRelativePath === localPath) || (localPath && refNormalizedHref && localPath.endsWith('/' + refNormalizedHref)) || (!href && textLabel && String(ref.label || '').trim() === textLabel); }) || null; } function attachmentFileSizeLabel(attachmentRef) { if (!attachmentRef || typeof attachmentRef !== 'object') return ''; if (typeof uploadedFileSize === 'function') return uploadedFileSize(attachmentRef); var size = Number(attachmentRef.file_size || attachmentRef.fileSize || 0); if (!Number.isFinite(size) || size <= 0) return ''; if (size >= 1024 * 1024) return (size / 1024 / 1024).toFixed(size >= 10 * 1024 * 1024 ? 1 : 2) + ' MB'; if (size >= 1024) return (size / 1024).toFixed(size >= 100 * 1024 ? 0 : 2) + ' KB'; return String(Math.round(size)) + ' B'; } function detailFromEditorAttachmentLink(link) { var rawHref = link instanceof HTMLAnchorElement ? (link.getAttribute('href') || '') : ''; var params = attachmentQueryParams(rawHref); var paneContext = editorAttachmentPaneContext(link); var attachmentRef = attachmentRefForHref(rawHref, paneContext, link ? link.textContent : ''); if (!rawHref && attachmentRef) rawHref = String(attachmentRef.rawHref || attachmentRef.normalizedHref || attachmentRef.resolvedUri || '').trim(); var openParts = localAttachmentOpenParts(rawHref, attachmentRef, paneContext); var localFilePath = openParts && openParts.relativePath ? openParts.relativePath : ( String(attachmentRef?.relativePath || '').trim() || localFileOpenPathFromHref(rawHref) || localMarkdownAttachmentPathFromHref(rawHref, paneContext.documentId) ); var localRootUri = openParts && openParts.rootUri ? openParts.rootUri : currentRootUri(); var fileName = params.get('fileName') || String(attachmentRef?.label || '').trim() || fileNameFromPath(localFilePath) || (link ? link.textContent : '') || '未命名附件'; var fileType = params.get('fileType') || inferOnlyOfficeFileType(fileName, '') || String(attachmentRef?.ext || '').trim(); var assetId = params.get('assetId') || (link ? link.getAttribute('data-asset-id') : '') || (localFilePath ? 'local-file:' + localFilePath : ''); var fileUrl = params.get('fileUrl') || (localFilePath ? buildLocalFileOpenUrlForRoot(localFilePath, localRootUri, false) : '') || String(attachmentRef?.resolvedUri || '').trim(); var documentId = params.get('documentId') || paneContext.documentId || ''; var href = rawHref; if (!isOnlyOfficeAttachmentHref(rawHref) && fileType) { fileUrl = localFilePath ? buildLocalFileOpenUrlForRoot(localFilePath, localRootUri, false) : rawHref; href = buildOnlyOfficeOpenUrl({ fileUrl: fileUrl, fileName: fileName, fileType: fileType, assetId: assetId, documentId: documentId, userId: '', mode: 'view' }); } else if (isOnlyOfficeAttachmentHref(rawHref)) { href = normalizeOnlyOfficeAttachmentHref(rawHref); } return { href: href, fileUrl: fileUrl, fileName: fileName, title: fileName, fileType: fileType, assetId: assetId, documentId: documentId, workspaceId: paneContext.workspaceId, paneRole: paneContext.paneRole, localRootUri: localRootUri, localRelativePath: localFilePath, fileSize: (link ? link.getAttribute('data-file-size') : '') || '', attachmentRef: attachmentRef || null, authorized: typeof attachmentRef?.authorized === 'boolean' ? attachmentRef.authorized : null, exists: typeof attachmentRef?.exists === 'boolean' ? attachmentRef.exists : null, openKind: String(attachmentRef?.openKind || '').trim() }; } function enhanceEditorAttachmentLink(link) { if (!(link instanceof HTMLAnchorElement)) return; var href = link.getAttribute('href') || ''; if (link.classList.contains('mnote-page-block-link')) return; var params = attachmentQueryParams(href); var paneContext = editorAttachmentPaneContext(link); var attachmentRef = attachmentRefForHref(href, paneContext, link ? link.textContent : ''); if (!href && attachmentRef) href = String(attachmentRef.rawHref || attachmentRef.normalizedHref || attachmentRef.resolvedUri || '').trim(); var openParts = localAttachmentOpenParts(href, attachmentRef, paneContext); var localFilePath = openParts && openParts.relativePath ? openParts.relativePath : (localFileOpenPathFromHref(href) || localMarkdownAttachmentPathFromHref(href, paneContext.documentId)); var resolvedPathName = fileNameFromPath(localFilePath || fileUriToPath(attachmentRef && attachmentRef.resolvedUri)); var fileName = params.get('fileName') || resolvedPathName || link.textContent || ''; var className = link.getAttribute('class') || ''; var shouldEnhance = isOnlyOfficeAttachmentHref(href) || className.indexOf('mnote-uploaded-attachment-row') >= 0 || isOfficeFileName(fileName) || Boolean(localFilePath); if (!shouldEnhance) return; var assetId = params.get('assetId') || link.getAttribute('data-asset-id') || (localFilePath ? 'local-file:' + localFilePath : ''); if (assetId) setAttributeIfChanged(link, 'data-asset-id', assetId); attachmentClassForFileName(fileName).split(/\s+/).forEach(function(name) { if (name) link.classList.add(name); }); if (attachmentRef && typeof attachmentRef === 'object') { if (typeof attachmentRef.exists === 'boolean') { setEditorAttachmentMissingState(link, attachmentRef.exists === false); } if (typeof attachmentRef.authorized === 'boolean') { setEditorAttachmentUnauthorizedState(link, attachmentRef.authorized === false); } var fileSizeLabel = attachmentFileSizeLabel(attachmentRef); if (fileSizeLabel) setAttributeIfChanged(link, 'data-file-size', fileSizeLabel); } setAttributeIfChanged(link, 'target', '_blank'); setAttributeIfChanged(link, 'rel', 'noopener noreferrer nofollow'); if (localFilePath && !isOnlyOfficeAttachmentHref(href)) { var localAttachmentKey = openParts ? String(openParts.rootUri || '').trim() + '\n' + String(openParts.relativePath || '').trim() : localFileOpenKeyFromHref(href); var isProjectionMissing = typeof attachmentRef?.exists === 'boolean' && attachmentRef.exists === false; setEditorAttachmentMissingState(link, isProjectionMissing || editorAttachmentMissingByKey.has(localAttachmentKey)); void refreshLocalAttachmentExistence(link); return; } if (isOnlyOfficeAttachmentHref(href)) { setAttributeIfChanged(link, 'href', buildOnlyOfficeOpenPath({ fileUrl: params.get('fileUrl') || '', fileName: fileName || '未命名附件', fileType: params.get('fileType') || inferOnlyOfficeFileType(fileName, ''), assetId: assetId, documentId: params.get('documentId') || paneContext.documentId || '', userId: params.get('userId') || '', mode: params.get('mode') || 'view' })); } void hydrateEditorAttachmentMeta(link); } function enhanceEditorAttachmentLinks() { document.querySelectorAll(editorAttachmentEnhanceSelector).forEach(enhanceEditorAttachmentLink); } window.__mnoteEnhanceEditorAttachmentLinks = function() { observeEditorAttachmentRoots(); enhanceEditorAttachmentLinks(); }; var lastEditorAttachmentMouseOpen = { at: 0, href: '' }; function ensureAttachmentActions() { var existing = document.querySelector('[data-testid="mnote-attachment-actions"]'); if (existing instanceof HTMLElement) return existing; var actions = document.createElement('div'); actions.className = 'mnote-attachment-actions'; actions.setAttribute('data-testid', 'mnote-attachment-actions'); actions.hidden = true; actions.innerHTML = '' + '' + ''; actions.addEventListener('mouseenter', function() { if (attachmentActionsHideTimer) window.clearTimeout(attachmentActionsHideTimer); }); actions.addEventListener('mouseleave', scheduleHideAttachmentActions); document.body.appendChild(actions); return actions; } function positionAttachmentActions(link) { if (!(link instanceof HTMLElement)) return; var actions = ensureAttachmentActions(); var rect = link.getBoundingClientRect(); actions.hidden = false; actions.style.left = Math.min(window.innerWidth - 76, Math.max(8, rect.right + 6)) + 'px'; actions.style.top = Math.max(8, rect.top + (rect.height - 28) / 2) + 'px'; activeEditorAttachmentLink = link; } function scheduleHideAttachmentActions() { if (attachmentActionsHideTimer) window.clearTimeout(attachmentActionsHideTimer); attachmentActionsHideTimer = window.setTimeout(function() { var actions = document.querySelector('[data-testid="mnote-attachment-actions"]'); if (actions instanceof HTMLElement) actions.hidden = true; activeEditorAttachmentLink = null; }, 220); } async function openEditorAttachmentDetail(detail) { if (!detail) return; if (detail.authorized === false) { document.documentElement.setAttribute('data-mnote-attachment-open-blocked', 'unauthorized'); return; } if (detail.exists === false) { document.documentElement.setAttribute('data-mnote-attachment-open-blocked', 'missing'); return; } if (!detail.href) return; document.documentElement.removeAttribute('data-mnote-attachment-open-blocked'); var localFilePath = localFilePathFromAssetId(detail.assetId) || String(detail.localRelativePath || '').trim(); if (localFilePath) { var localRootUri = String(detail.localRootUri || currentRootUri() || '').trim(); if (!detail.localRootUri && await openLocalOfficeFileInActiveTab(detail, 'view')) return; var localFileName = detail.fileName || localFilePath.split('/').pop() || localFilePath; var localFileUrl = buildLocalFileOpenUrlForRoot(localFilePath, localRootUri, false); var localOpenUrl = isPdfAttachmentFileName(localFileName) ? buildPdfPreviewOpenUrl(localFileUrl, localFileName) : localFileUrl; var localOfficeType = inferOnlyOfficeFileType(localFileName, ''); var localOfficeUrl = localOfficeType ? buildOnlyOfficeOpenUrl({ fileUrl: localFileUrl, fileName: localFileName, fileType: localOfficeType, assetId: detail.assetId || ('local-file:' + localFilePath), documentId: detail.documentId || currentDocumentId() || '', userId: '', mode: 'view', workspaceId: detail.workspaceId || resolveWorkspaceId(document.body) || '', sourceKind: 'local_folder', rootUri: localRootUri }) : ''; // 非 Office 本地文件:用对应图标类型打开 active tab,失败则新窗口 void openLocalResourceInActiveTab({ path: localFilePath, title: localFileName, kind: localOfficeType ? 'office' : fileTreeIconKindForFileName(localFileName), assetId: detail.assetId, documentId: detail.documentId || currentDocumentId() || '', workspaceId: detail.workspaceId || resolveWorkspaceId(document.body) || '', href: localOpenUrl, officeUrl: localOfficeUrl, rootUri: localRootUri, paneRole: detail.paneRole || 'primary' }).then(function(opened) { if (!opened) window.open(localOfficeUrl || localOpenUrl || detail.href, '_blank', 'noopener,noreferrer'); }); return; } if (isPdfAttachmentFileName(detail.fileName)) { void openPdfEditorAttachment(detail); return; } if (isCodeAttachmentFileName(detail.fileName)) { void openCodeEditorAttachment(detail); return; } var fileType = String(detail.fileType || '').trim(); if (fileType && typeof window.__mnoteDocumentPaneRuntime?.openResourceInActiveTab === 'function') { void window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({ objectIdentity: 'resource:onlyoffice:' + (detail.documentId || '') + ':' + (detail.assetId || ''), assetId: detail.assetId || '', title: detail.fileName || '附件', fileName: detail.fileName || '附件', kind: 'office', officeUrl: detail.href, documentId: detail.documentId || '', workspaceId: detail.workspaceId || '', paneRole: detail.paneRole || 'primary' }); return; } window.open(detail.href, '_blank', 'noopener,noreferrer'); } function openEditorAttachmentNewWindow(detail, mode) { if (!detail) return; var requestedMode = mode === 'edit' ? 'edit' : 'view'; var localFilePath = localFilePathFromAssetId(detail.assetId); if (localFilePath) { var localFileName = localFilePath.split('/').pop() || localFilePath; var localOfficeUrl = buildLocalOnlyOfficeOpenUrl(localFilePath, localFileName, String(detail.documentId || currentDocumentId() || '').trim(), detail.assetId, requestedMode); var localFileUrl = buildLocalFileOpenUrl(localFilePath, false); var localPreviewUrl = isPdfAttachmentFileName(localFileName) ? buildPdfPreviewOpenUrl(localFileUrl, localFileName) : ''; window.open(localOfficeUrl || localPreviewUrl || localFileUrl || detail.href, '_blank', 'noopener,noreferrer'); return; } var href = detail.href || detail.fileUrl; if (detail.fileType && isOnlyOfficeAttachmentHref(href)) { try { var url = new URL(href, window.location.origin); url.searchParams.set('mode', requestedMode); href = url.toString(); } catch (_) {} } else if (detail.fileType) { href = buildOnlyOfficeOpenUrl({ fileUrl: detail.fileUrl || href || '', fileName: detail.fileName || '未命名附件', fileType: detail.fileType, assetId: detail.assetId || '', documentId: detail.documentId || currentDocumentId() || '', userId: '', mode: requestedMode }); } window.open(href, '_blank', 'noopener,noreferrer'); } async function openEditorAttachmentEditTab(detail) { if (!detail) return false; var localFilePath = localFilePathFromAssetId(detail.assetId); if (localFilePath) { if (await openLocalOfficeFileInActiveTab(detail, 'edit')) return true; } var href = detail.href || detail.fileUrl; if (detail.fileType && isOnlyOfficeAttachmentHref(href)) { try { var url = new URL(href, window.location.origin); url.searchParams.set('mode', 'edit'); href = url.toString(); } catch (_) {} } else if (detail.fileType) { href = buildOnlyOfficeOpenUrl({ fileUrl: detail.fileUrl || href || '', fileName: detail.fileName || '未命名附件', fileType: detail.fileType, assetId: detail.assetId || '', documentId: detail.documentId || currentDocumentId() || '', userId: '', mode: 'edit' }); } if (detail.fileType && typeof window.__mnoteDocumentPaneRuntime?.openResourceInActiveTab === 'function') { var didOpenEditTab = await window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({ objectIdentity: 'resource:onlyoffice:' + (detail.documentId || '') + ':' + (detail.assetId || ''), assetId: detail.assetId || '', title: detail.fileName || '附件', fileName: detail.fileName || '附件', kind: 'office', officeUrl: href, documentId: detail.documentId || '', workspaceId: detail.workspaceId || '', paneRole: detail.paneRole || 'primary' }); if (!didOpenEditTab && href) window.open(href, '_blank', 'noopener,noreferrer'); return didOpenEditTab; } if (href) window.open(href, '_blank', 'noopener,noreferrer'); return false; } async function resolveEditorAttachmentUrl(detail) { var assetId = String(detail && detail.assetId || '').trim(); var localFilePath = localFilePathFromAssetId(assetId); if (localFilePath) { var localUrl = buildLocalFileOpenUrl(localFilePath, false); if (localUrl) { return { url: localUrl, asset: { file_name: String(detail && detail.fileName || '').trim(), fileSize: String(detail && detail.fileSize || '').trim() } }; } } if (assetId) document.documentElement.setAttribute('data-mnote-media-sign-retired', 'true'); var url = String(detail && (detail.fileUrl || detail.href) || '').trim(); if (!url) throw new Error('附件链接不可用'); return { url: url, asset: {} }; } async function openPdfEditorAttachment(detail) { try { var resolved = await resolveEditorAttachmentUrl(detail); var title = String(detail.fileName || resolved.asset.file_name || 'PDF').trim() || 'PDF'; var previewUrl = buildPdfPreviewOpenUrl(resolved.url, title); if (typeof window.__mnoteDocumentPaneRuntime?.openResourceInActiveTab === 'async function') { var didOpenPdfTab = await window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({ objectIdentity: 'resource:pdf:' + (detail.documentId || '') + ':' + (detail.assetId || previewUrl || resolved.url), assetId: detail.assetId || '', title: title, fileName: title, kind: 'pdf', href: previewUrl, documentId: detail.documentId || '', workspaceId: detail.workspaceId || '', paneRole: detail.paneRole || 'primary' }); if (didOpenPdfTab) return; } window.open(previewUrl || resolved.url, '_blank', 'noopener,noreferrer'); } catch (error) { await window.mnote.alert(error && error.message ? error.message : '打开 PDF 失败'); } } async function openCodeEditorAttachment(detail) { var resolved = null; try { resolved = await resolveEditorAttachmentUrl(detail); var size = Number(resolved.asset && (resolved.asset.file_size || resolved.asset.fileSize) || 0); if (Number.isFinite(size) && size > 1024 * 1024) { window.open(resolved.url, '_blank', 'noopener,noreferrer'); return; } var response = await fetch(resolved.url, { method: 'GET', credentials: 'include', cache: 'no-store' }); if (!response.ok) throw new Error('读取附件内容失败'); var text = await response.text(); if (text.length > 1024 * 1024) { window.open(resolved.url, '_blank', 'noopener,noreferrer'); return; } var editorRoot = document.querySelector('.editor-surface .ProseMirror'); var editor = editorRoot && editorRoot.editor; if (!editor || !editor.chain) { window.open(resolved.url, '_blank', 'noopener,noreferrer'); return; } var title = String(detail.fileName || resolved.asset.file_name || '附件').trim() || '附件'; var language = inferCodeAttachmentLanguage(title); editor.chain().focus().insertContent([ { type: 'paragraph', content: [{ type: 'text', text: title }] }, { type: 'codeBlock', attrs: { language: language }, content: text ? [{ type: 'text', text: text.replace(/\r\n?/g, '\n') }] : [] } ]).run(); } catch (error) { console.warn('[mnote attachment] open code attachment failed', error); if (resolved && resolved.url) { window.open(resolved.url, '_blank', 'noopener,noreferrer'); return; } await window.mnote.alert(error && error.message ? error.message : '打开代码附件失败'); } } async function openEditorAttachmentDownload(detail) { if (!detail) return; var localFilePath = localFilePathFromAssetId(detail.assetId) || String(detail.localRelativePath || '').trim(); if (localFilePath) { var localDownloadUrl = buildLocalFileOpenUrlForRoot(localFilePath, detail.localRootUri || currentRootUri(), true); if (localDownloadUrl) { triggerBrowserDownload(localDownloadUrl); return; } } if (detail.assetId) document.documentElement.setAttribute('data-mnote-media-sign-retired', 'true'); var localRelativePath = String(detail.localRelativePath || '').trim(); if (localRelativePath) { var localPathDownloadUrl = buildLocalFileOpenUrl(localRelativePath, true); if (localPathDownloadUrl) { triggerBrowserDownload(localPathDownloadUrl); return; } } var target = detail.fileUrl || detail.href; if (!target) return; window.open(target, '_blank', 'noopener,noreferrer'); } function openEditorAttachmentMenu(link, trigger, point) { var detail = detailFromEditorAttachmentLink(link); var rect = trigger && trigger.getBoundingClientRect ? trigger.getBoundingClientRect() : link.getBoundingClientRect(); var x = point && typeof point.x === 'number' ? point.x : rect.right; var y = point && typeof point.y === 'number' ? point.y : rect.bottom + 4; openTreeContextMenu('attachment', detail, x, y, trigger || link); } function openEditorAttachmentLink(link) { enhanceEditorAttachmentLink(link); openEditorAttachmentDetail(detailFromEditorAttachmentLink(link)); } enhanceEditorAttachmentLinks(); 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 a.mnote-uploaded-attachment-row, .editor-surface .ProseMirror p, .editor-surface .ProseMirror span, .editor-surface .ProseMirror div') || node.querySelector?.('.editor-surface .ProseMirror, .editor-surface .ProseMirror a[href], .editor-surface .ProseMirror a.mnote-uploaded-attachment-row') ); } 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 }); }); } var attachmentAggregateObserver = null; var attachmentObservedAggregateScript = null; function observePageAggregateScript() { if (typeof MutationObserver !== 'function') return; var script = document.getElementById('__MNOTE_PAGE_AGGREGATE__'); if (!(script instanceof HTMLScriptElement)) return; if (script === attachmentObservedAggregateScript) return; if (attachmentAggregateObserver) attachmentAggregateObserver.disconnect(); attachmentObservedAggregateScript = script; attachmentAggregateObserver = new MutationObserver(function() { scheduleEditorAttachmentEnhance(); scheduleEditorLocalAttachmentExistenceRefresh(); }); attachmentAggregateObserver.observe(script, { attributes: true, characterData: true, 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) { if (node instanceof HTMLElement && node.id === '__MNOTE_PAGE_AGGREGATE__') { observePageAggregateScript(); return true; } return addedNodeMayContainEditorAttachmentLink(node); }); }); if (!shouldEnhance) return; scheduleEditorAttachmentEnhance(); }); attachmentEditorObserver.observe(document.documentElement, { childList: true, subtree: true }); observeEditorAttachmentRoots(); observePageAggregateScript(); window.addEventListener('mnote:editor-attachment-links-changed', function() { window.__mnoteEnhanceEditorAttachmentLinks(); scheduleEditorAttachmentEnhance(); }); window.addEventListener('mnote:page-aggregate-synced', function() { window.__mnoteEnhanceEditorAttachmentLinks(); scheduleEditorAttachmentEnhance(); scheduleEditorLocalAttachmentExistenceRefresh(); }); window.addEventListener('mnote:primary-document-activated', function() { window.__mnoteEnhanceEditorAttachmentLinks(); scheduleEditorAttachmentEnhance(); scheduleEditorLocalAttachmentExistenceRefresh(); }); window.addEventListener('tree:delta', scheduleEditorLocalAttachmentExistenceRefresh); window.addEventListener('tree:resync', scheduleEditorLocalAttachmentExistenceRefresh); [ 'mnote:leptos-tiptap-spike:ready', 'mnote:leptos-tiptap-spike:change', 'mnote:leptos-tiptap-spike:state' ].forEach(function(eventName) { window.addEventListener(eventName, function() { enhanceEditorAttachmentLinks(); observeEditorAttachmentRoots(); }, true); }); function interceptEditorAttachmentLink(event) { var editorAttachmentLink = closestAction(event.target, editorAttachmentLinkSelector); if (!(editorAttachmentLink instanceof HTMLAnchorElement)) return; event.preventDefault(); event.stopPropagation(); if (typeof event.stopImmediatePropagation === 'function') event.stopImmediatePropagation(); var href = editorAttachmentLink.href || editorAttachmentLink.getAttribute('href') || ''; if (lastEditorAttachmentMouseOpen.href === href && Date.now() - lastEditorAttachmentMouseOpen.at < 900) return; openEditorAttachmentLink(editorAttachmentLink); } function suppressEditorAttachmentLinkDefault(event) { var editorAttachmentLink = closestAction(event.target, editorAttachmentLinkSelector); if (!(editorAttachmentLink instanceof HTMLAnchorElement)) return; event.preventDefault(); event.stopPropagation(); if (typeof event.stopImmediatePropagation === 'function') event.stopImmediatePropagation(); if (Number(event.button || 0) !== 0) return; lastEditorAttachmentMouseOpen = { at: Date.now(), href: editorAttachmentLink.href || editorAttachmentLink.getAttribute('href') || '' }; openEditorAttachmentLink(editorAttachmentLink); } window.addEventListener('mousedown', suppressEditorAttachmentLinkDefault, true); window.addEventListener('click', interceptEditorAttachmentLink, true); document.addEventListener('mouseover', function(event) { var link = closestAction(event.target, editorAttachmentLinkSelector); if (!(link instanceof HTMLAnchorElement)) return; if (attachmentActionsHideTimer) window.clearTimeout(attachmentActionsHideTimer); enhanceEditorAttachmentLink(link); positionAttachmentActions(link); }); document.addEventListener('mouseout', function(event) { var link = closestAction(event.target, editorAttachmentLinkSelector); if (!(link instanceof HTMLAnchorElement)) return; var next = event.relatedTarget; var actions = document.querySelector('[data-testid="mnote-attachment-actions"]'); if (next && (link.contains(next) || (actions && actions.contains(next)))) return; scheduleHideAttachmentActions(); }); return { detailFromEditorAttachmentLink, enhanceEditorAttachmentLink, enhanceEditorAttachmentLinks, openCodeEditorAttachment, openEditorAttachmentDetail, openEditorAttachmentDownload, openEditorAttachmentEditTab, openEditorAttachmentNewWindow, refreshEditorLocalAttachmentExistence, }; };