feat: consolidate local-first mnote web runtime
This commit is contained in:
@@ -5,12 +5,14 @@ export const createSidebarAttachmentOpenRuntime = (dependencies = {}) => {
|
||||
buildLocalOnlyOfficeOpenUrl,
|
||||
buildOnlyOfficeOpenPath,
|
||||
buildOnlyOfficeOpenUrl,
|
||||
buildPdfPreviewOpenUrl: injectedBuildPdfPreviewOpenUrl,
|
||||
closestAction: injectedClosestAction,
|
||||
currentDocumentId,
|
||||
currentRootUri,
|
||||
currentWorkspaceSourcePayload,
|
||||
fileTreeIconKindForFileName,
|
||||
healLegacyOfficeAttachmentParagraphs,
|
||||
hydrateEditorAttachmentMeta: injectedHydrateEditorAttachmentMeta,
|
||||
inferCodeAttachmentLanguage,
|
||||
inferOnlyOfficeFileType,
|
||||
isCodeAttachmentFileName,
|
||||
@@ -28,9 +30,15 @@ export const createSidebarAttachmentOpenRuntime = (dependencies = {}) => {
|
||||
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 {
|
||||
@@ -60,6 +68,13 @@ export const createSidebarAttachmentOpenRuntime = (dependencies = {}) => {
|
||||
}
|
||||
}
|
||||
|
||||
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 '';
|
||||
@@ -69,13 +84,26 @@ export const createSidebarAttachmentOpenRuntime = (dependencies = {}) => {
|
||||
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) {
|
||||
link.setAttribute('data-mnote-attachment-missing', 'true');
|
||||
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');
|
||||
link.setAttribute('aria-label', (link.textContent || '附件') + '(文件不存在)');
|
||||
setAttributeIfChanged(link, 'aria-label', (link.textContent || '附件') + '(文件不存在)');
|
||||
} else {
|
||||
if (link.getAttribute('data-mnote-attachment-missing') !== 'true') return;
|
||||
link.removeAttribute('data-mnote-attachment-missing');
|
||||
@@ -84,9 +112,35 @@ export const createSidebarAttachmentOpenRuntime = (dependencies = {}) => {
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
@@ -94,11 +148,32 @@ export const createSidebarAttachmentOpenRuntime = (dependencies = {}) => {
|
||||
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);
|
||||
setEditorAttachmentMissingState(link, !exists);
|
||||
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);
|
||||
@@ -106,6 +181,12 @@ export const createSidebarAttachmentOpenRuntime = (dependencies = {}) => {
|
||||
}
|
||||
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;
|
||||
@@ -225,20 +306,21 @@ export const createSidebarAttachmentOpenRuntime = (dependencies = {}) => {
|
||||
|| isOfficeFileName(fileName)
|
||||
|| Boolean(localFilePath);
|
||||
if (!shouldEnhance) return;
|
||||
link.setAttribute('data-mnote-attachment-link', 'true');
|
||||
setAttributeIfChanged(link, 'data-mnote-attachment-link', 'true');
|
||||
var assetId = params.get('assetId') || link.getAttribute('data-asset-id') || (localFilePath ? 'local-file:' + localFilePath : '');
|
||||
if (assetId) link.setAttribute('data-asset-id', assetId);
|
||||
if (assetId) setAttributeIfChanged(link, 'data-asset-id', assetId);
|
||||
attachmentClassForFileName(fileName).split(/\s+/).forEach(function(name) {
|
||||
if (name) link.classList.add(name);
|
||||
});
|
||||
link.setAttribute('target', '_blank');
|
||||
link.setAttribute('rel', 'noopener noreferrer nofollow');
|
||||
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)) {
|
||||
link.setAttribute('href', buildOnlyOfficeOpenPath({
|
||||
setAttributeIfChanged(link, 'href', buildOnlyOfficeOpenPath({
|
||||
fileUrl: params.get('fileUrl') || '',
|
||||
fileName: fileName || '未命名附件',
|
||||
fileType: params.get('fileType') || inferOnlyOfficeFileType(fileName, ''),
|
||||
@@ -265,6 +347,14 @@ export const createSidebarAttachmentOpenRuntime = (dependencies = {}) => {
|
||||
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"]');
|
||||
@@ -308,18 +398,21 @@ export const createSidebarAttachmentOpenRuntime = (dependencies = {}) => {
|
||||
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: detail.fileName || localFilePath.split('/').pop() || localFilePath,
|
||||
kind: fileTreeIconKindForFileName(detail.fileName || localFilePath.split('/').pop() || localFilePath),
|
||||
title: localFileName,
|
||||
kind: fileTreeIconKindForFileName(localFileName),
|
||||
assetId: detail.assetId,
|
||||
documentId: detail.documentId || currentDocumentId() || '',
|
||||
workspaceId: detail.workspaceId || resolveWorkspaceId(document.body) || '',
|
||||
href: buildLocalFileOpenUrl(localFilePath, false),
|
||||
href: localOpenUrl,
|
||||
paneRole: detail.paneRole || 'primary'
|
||||
}).then(function(opened) {
|
||||
if (!opened) window.open(buildLocalFileOpenUrl(localFilePath, false) || detail.href, '_blank', 'noopener,noreferrer');
|
||||
if (!opened) window.open(localOpenUrl || detail.href, '_blank', 'noopener,noreferrer');
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -357,7 +450,8 @@ export const createSidebarAttachmentOpenRuntime = (dependencies = {}) => {
|
||||
var localFileName = localFilePath.split('/').pop() || localFilePath;
|
||||
var localOfficeUrl = buildLocalOnlyOfficeOpenUrl(localFilePath, localFileName, String(detail.documentId || currentDocumentId() || '').trim(), detail.assetId, requestedMode);
|
||||
var localFileUrl = buildLocalFileOpenUrl(localFilePath, false);
|
||||
window.open(localOfficeUrl || localFileUrl || detail.href, '_blank', 'noopener,noreferrer');
|
||||
var localPreviewUrl = isPdfAttachmentFileName(localFileName) ? buildPdfPreviewOpenUrl(localFileUrl, localFileName) : '';
|
||||
window.open(localOfficeUrl || localPreviewUrl || localFileUrl || detail.href, '_blank', 'noopener,noreferrer');
|
||||
return;
|
||||
}
|
||||
var href = detail.href || detail.fileUrl;
|
||||
@@ -464,7 +558,23 @@ export const createSidebarAttachmentOpenRuntime = (dependencies = {}) => {
|
||||
async function openPdfEditorAttachment(detail) {
|
||||
try {
|
||||
var resolved = await resolveEditorAttachmentUrl(detail);
|
||||
window.open(resolved.url, '_blank', 'noopener,noreferrer');
|
||||
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 失败');
|
||||
}
|
||||
@@ -612,6 +722,8 @@ export const createSidebarAttachmentOpenRuntime = (dependencies = {}) => {
|
||||
window.__mnoteEnhanceEditorAttachmentLinks();
|
||||
scheduleEditorAttachmentEnhance();
|
||||
});
|
||||
window.addEventListener('tree:delta', scheduleEditorLocalAttachmentExistenceRefresh);
|
||||
window.addEventListener('tree:resync', scheduleEditorLocalAttachmentExistenceRefresh);
|
||||
[
|
||||
'mnote:leptos-tiptap-spike:ready',
|
||||
'mnote:leptos-tiptap-spike:change',
|
||||
@@ -634,27 +746,35 @@ export const createSidebarAttachmentOpenRuntime = (dependencies = {}) => {
|
||||
});
|
||||
|
||||
function interceptEditorAttachmentLink(event) {
|
||||
var editorAttachmentLink = closestAction(event.target, '.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"]');
|
||||
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, '.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"]');
|
||||
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, '.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"]');
|
||||
var link = closestAction(event.target, editorAttachmentLinkSelector);
|
||||
if (!(link instanceof HTMLAnchorElement)) return;
|
||||
if (attachmentActionsHideTimer) window.clearTimeout(attachmentActionsHideTimer);
|
||||
enhanceEditorAttachmentLink(link);
|
||||
@@ -662,7 +782,7 @@ export const createSidebarAttachmentOpenRuntime = (dependencies = {}) => {
|
||||
});
|
||||
|
||||
document.addEventListener('mouseout', function(event) {
|
||||
var link = closestAction(event.target, '.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"]');
|
||||
var link = closestAction(event.target, editorAttachmentLinkSelector);
|
||||
if (!(link instanceof HTMLAnchorElement)) return;
|
||||
var next = event.relatedTarget;
|
||||
var actions = document.querySelector('[data-testid="mnote-attachment-actions"]');
|
||||
|
||||
Reference in New Issue
Block a user