Files
mnote/rust/crates/mnote-web/browser/sidebar-attachment-open-runtime.js
T

806 lines
35 KiB
JavaScript

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 = '' +
'<button type="button" class="mnote-attachment-action" data-testid="mnote-attachment-action-download" data-attachment-action="download" aria-label="下载附件"><span class="material-symbols-outlined" data-icon="download" aria-hidden="true"></span></button>' +
'<button type="button" class="mnote-attachment-action" data-testid="mnote-attachment-action-menu" data-attachment-action="menu" aria-label="更多操作"><span class="material-symbols-outlined" data-icon="more_horiz" aria-hidden="true"></span></button>';
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,
};
};