export const createSidebarAttachmentOpenRuntime = (dependencies = {}) => { const { attachmentClassForFileName, buildLocalFileOpenUrl, buildLocalOnlyOfficeOpenUrl, buildOnlyOfficeOpenPath, buildOnlyOfficeOpenUrl, buildPdfPreviewOpenUrl: injectedBuildPdfPreviewOpenUrl, closestAction: injectedClosestAction, currentDocumentId, currentRootUri, currentWorkspaceSourcePayload, fileTreeIconKindForFileName, healLegacyOfficeAttachmentParagraphs, 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[data-mnote-attachment-link="true"], .editor-surface .ProseMirror a.mnote-uploaded-attachment-row, .editor-surface .ProseMirror a[href*="/api/local-folder/files/open"], .editor-surface .ProseMirror a[href*="/office-preview"]'; 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 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 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 setAttributeIfChanged(element, name, value) { var nextValue = String(value || ''); if (element.getAttribute(name) === nextValue) return; element.setAttribute(name, nextValue); } function setEditorAttachmentMissingStateByHref(href, missing) { var targetKey = localFileOpenKeyFromHref(href); if (!targetKey) return; if (missing) { editorAttachmentMissingByKey.set(targetKey, true); } else { editorAttachmentMissingByKey.delete(targetKey); } document.querySelectorAll('.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 || ''; if (localFileOpenKeyFromHref(candidateHref) !== targetKey) return; setEditorAttachmentMissingState(candidate, missing); }); } async function refreshLocalAttachmentExistence(link) { if (!(link instanceof HTMLAnchorElement)) return; var href = link.getAttribute('href') || link.href || ''; var attachmentKey = localFileOpenKeyFromHref(href); if (!attachmentKey) return; var refreshSeq = (editorAttachmentRefreshSeqByKey.get(attachmentKey) || 0) + 1; editorAttachmentRefreshSeqByKey.set(attachmentKey, refreshSeq); var localFilePath = localFileOpenPathFromHref(href); if (!localFilePath) return; var statusUrl = buildLocalFileStatusUrl(localFilePath, 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')); return; } var exists = Boolean(response.ok && payload && payload.ok === true && payload.result && payload.result.exists === true); setEditorAttachmentStatusErrorByHref(href, ''); setEditorAttachmentMissingStateByHref(href, !exists); } catch (_) {} } function setEditorAttachmentStatusErrorByHref(href, status) { var targetKey = localFileOpenKeyFromHref(href); if (!targetKey) return; document.querySelectorAll('.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 || ''; if (localFileOpenKeyFromHref(candidateHref) !== targetKey) return; if (status) { setAttributeIfChanged(candidate, 'data-mnote-attachment-status-error', status); } else { candidate.removeAttribute('data-mnote-attachment-status-error'); } }); } function refreshEditorLocalAttachmentExistence() { document.querySelectorAll('.editor-surface .ProseMirror a[href*="/api/local-folder/files/open"]').forEach(function(link) { if (link instanceof HTMLAnchorElement) void refreshLocalAttachmentExistence(link); }); } window.__mnoteRefreshEditorLocalAttachmentExistence = refreshEditorLocalAttachmentExistence; function scheduleEditorLocalAttachmentExistenceRefresh() { [120, 500, 1200].forEach(function(delayMs) { window.setTimeout(refreshEditorLocalAttachmentExistence, delayMs); }); } 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 detailFromEditorAttachmentLink(link) { var rawHref = link instanceof HTMLAnchorElement ? link.href : ''; var params = attachmentQueryParams(rawHref); var paneContext = editorAttachmentPaneContext(link); var localFilePath = localFileOpenPathFromHref(rawHref); var fileName = params.get('fileName') || fileNameFromPath(localFilePath) || (link ? link.textContent : '') || '未命名附件'; var fileType = params.get('fileType') || inferOnlyOfficeFileType(fileName, ''); var assetId = params.get('assetId') || (link ? link.getAttribute('data-asset-id') : '') || (localFilePath ? 'local-file:' + localFilePath : ''); var fileUrl = params.get('fileUrl') || ''; var documentId = params.get('documentId') || paneContext.documentId || ''; var href = rawHref; if (!isOnlyOfficeAttachmentHref(rawHref) && fileType) { fileUrl = rawHref; 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, fileSize: (link ? link.getAttribute('data-file-size') : '') || '' }; } function enhanceEditorAttachmentLink(link) { if (!(link instanceof HTMLAnchorElement)) return; var href = link.getAttribute('href') || ''; var params = attachmentQueryParams(href); var localFilePath = localFileOpenPathFromHref(href); var paneContext = editorAttachmentPaneContext(link); var fileName = params.get('fileName') || fileNameFromPath(localFilePath) || link.textContent || ''; var className = link.getAttribute('class') || ''; var shouldEnhance = isOnlyOfficeAttachmentHref(href) || className.indexOf('mnote-uploaded-attachment-row') >= 0 || isOfficeFileName(fileName) || Boolean(localFilePath); if (!shouldEnhance) return; setAttributeIfChanged(link, 'data-mnote-attachment-link', 'true'); 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); }); setAttributeIfChanged(link, 'target', '_blank'); setAttributeIfChanged(link, 'rel', 'noopener noreferrer nofollow'); if (localFilePath && !isOnlyOfficeAttachmentHref(href)) { setEditorAttachmentMissingState(link, editorAttachmentMissingByKey.has(localFileOpenKeyFromHref(href))); 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('.editor-surface .ProseMirror a[href]').forEach(enhanceEditorAttachmentLink); void healLegacyOfficeAttachmentParagraphs(); } window.__mnoteEnhanceEditorAttachmentLinks = function() { observeEditorAttachmentRoots(); enhanceEditorAttachmentLinks(); }; var attachmentInitialEnhanceAttempts = 0; var attachmentInitialEnhanceTimer = window.setInterval(function() { attachmentInitialEnhanceAttempts += 1; enhanceEditorAttachmentLinks(); if (attachmentInitialEnhanceAttempts >= 120) window.clearInterval(attachmentInitialEnhanceTimer); }, 500); var attachmentExistenceRefreshTimer = window.setInterval(function() { refreshEditorLocalAttachmentExistence(); }, 2500); window.addEventListener('beforeunload', function() { if (attachmentExistenceRefreshTimer) window.clearInterval(attachmentExistenceRefreshTimer); attachmentExistenceRefreshTimer = 0; }); 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 || !detail.href) return; var localFilePath = localFilePathFromAssetId(detail.assetId); if (localFilePath) { if (await openLocalOfficeFileInActiveTab(detail, 'view')) return; var localFileName = detail.fileName || localFilePath.split('/').pop() || localFilePath; var localFileUrl = buildLocalFileOpenUrl(localFilePath, false); var localOpenUrl = isPdfAttachmentFileName(localFileName) ? buildPdfPreviewOpenUrl(localFileUrl, localFileName) : localFileUrl; // 非 Office 本地文件:用对应图标类型打开 active tab,失败则新窗口 void openLocalResourceInActiveTab({ path: localFilePath, title: localFileName, kind: fileTreeIconKindForFileName(localFileName), assetId: detail.assetId, documentId: detail.documentId || currentDocumentId() || '', workspaceId: detail.workspaceId || resolveWorkspaceId(document.body) || '', href: localOpenUrl, paneRole: detail.paneRole || 'primary' }).then(function(opened) { if (!opened) window.open(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) { var response = await fetch('/api/media/sign?assetId=' + encodeURIComponent(assetId), { method: 'GET', credentials: 'include', cache: 'no-store' }); var payload = await response.json().catch(function() { return null; }); if (!response.ok || !payload) { throw new Error(payload && payload.error ? payload.error : '生成签名链接失败'); } var signedUrl = String(payload && payload.signedUrl || '').trim(); if (!signedUrl) throw new Error('附件链接不可用'); return { url: signedUrl, asset: payload.asset && typeof payload.asset === 'object' ? payload.asset : {} }; } 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 === '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) { window.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; } window.alert(error && error.message ? error.message : '打开代码附件失败'); } } async function openEditorAttachmentDownload(detail) { if (!detail) return; var localFilePath = localFilePathFromAssetId(detail.assetId); if (localFilePath) { var localDownloadUrl = buildLocalFileOpenUrl(localFilePath, true); if (localDownloadUrl) { triggerBrowserDownload(localDownloadUrl); return; } } if (detail.assetId) { try { var response = await fetch('/api/media/sign?assetId=' + encodeURIComponent(detail.assetId), { method: 'GET', credentials: 'include' }); var payload = await response.json().catch(function() { return null; }); var signedUrl = String(payload && payload.signedUrl || '').trim(); if (response.ok && signedUrl) { window.open(signedUrl, '_blank', 'noopener,noreferrer'); return; } } catch (_) {} } 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 p, .editor-surface .ProseMirror span, .editor-surface .ProseMirror div') || node.querySelector?.('.editor-surface .ProseMirror, .editor-surface .ProseMirror a[href]') ); } function observeEditorAttachmentRoots() { if (!attachmentEditorObserver) return; document.querySelectorAll('.editor-surface .ProseMirror').forEach(function(editor) { if (!(editor instanceof HTMLElement)) return; if (attachmentObservedEditors && attachmentObservedEditors.has(editor)) return; if (attachmentObservedEditors) attachmentObservedEditors.add(editor); attachmentEditorObserver.observe(editor, { childList: true, subtree: true }); }); } attachmentEditorObserver = new MutationObserver(function(records) { var shouldEnhance = Array.isArray(records) && records.some(function(record) { if (!record || record.type !== 'childList') return false; return Array.from(record.addedNodes || []).some(function(node) { return addedNodeMayContainEditorAttachmentLink(node); }); }); if (!shouldEnhance) return; scheduleEditorAttachmentEnhance(); }); attachmentEditorObserver.observe(document.documentElement, { childList: true, subtree: true }); observeEditorAttachmentRoots(); window.addEventListener('mnote:editor-attachment-links-changed', function() { window.__mnoteEnhanceEditorAttachmentLinks(); scheduleEditorAttachmentEnhance(); }); 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(); window.setTimeout(function() { enhanceEditorAttachmentLinks(); observeEditorAttachmentRoots(); }, 120); }, true); }); [120, 500, 1200, 2500].forEach(function(delayMs) { window.setTimeout(function() { enhanceEditorAttachmentLinks(); observeEditorAttachmentRoots(); }, delayMs); }); 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, }; };