fix: stabilize local attachment icons and office proxy urls
This commit is contained in:
@@ -122,6 +122,16 @@ import {
|
||||
}
|
||||
};
|
||||
|
||||
const enhanceEditorAttachmentLinksSoon = () => {
|
||||
const run = () => {
|
||||
if (typeof window.__mnoteEnhanceEditorAttachmentLinks === 'function') {
|
||||
window.__mnoteEnhanceEditorAttachmentLinks();
|
||||
}
|
||||
};
|
||||
run();
|
||||
[120, 500, 1200].forEach((delayMs) => window.setTimeout(run, delayMs));
|
||||
};
|
||||
|
||||
const pageAggregateUrl = (bootstrap) => {
|
||||
const url = new URL(`/api/page-aggregate/${encodeURIComponent(bootstrap.documentId)}`, window.location.origin);
|
||||
url.searchParams.set('sourceKind', bootstrap.sourceKind || 'local_folder');
|
||||
@@ -357,6 +367,7 @@ import {
|
||||
runtimeDescriptor.root.setAttribute('data-runtime-mount-id', String(mountId));
|
||||
runtimeDescriptor.root.setAttribute('data-editor-host-kind', 'leptos_tiptap_island');
|
||||
setStatus(runtimeDescriptor, 'mounting-editor');
|
||||
enhanceEditorAttachmentLinksSoon();
|
||||
paneViewRegistry.set(paneRole, view);
|
||||
return view;
|
||||
} catch (error) {
|
||||
@@ -572,6 +583,7 @@ import {
|
||||
} else {
|
||||
setStatus(runtimeDescriptor, 'ready');
|
||||
}
|
||||
enhanceEditorAttachmentLinksSoon();
|
||||
};
|
||||
view.onError = (event) => {
|
||||
const payload = normalizeEnvelopePayload(event);
|
||||
@@ -580,12 +592,15 @@ import {
|
||||
view.onChange = (event) => {
|
||||
scheduleSlashMenuPosition(runtimeDescriptor.root);
|
||||
handleSessionChange(session, view, event);
|
||||
enhanceEditorAttachmentLinksSoon();
|
||||
};
|
||||
view.onSave = (event) => {
|
||||
handleSessionChange(session, view, event);
|
||||
enhanceEditorAttachmentLinksSoon();
|
||||
};
|
||||
view.onState = () => {
|
||||
scheduleSlashMenuPosition(runtimeDescriptor.root);
|
||||
enhanceEditorAttachmentLinksSoon();
|
||||
};
|
||||
view.onKeydown = (event) => {
|
||||
if (event.key === '/') scheduleSlashMenuPosition(runtimeDescriptor.root);
|
||||
@@ -664,6 +679,7 @@ import {
|
||||
runtimeDescriptor.root.setAttribute('data-runtime-mount-id', String(mountId));
|
||||
runtimeDescriptor.root.setAttribute('data-editor-host-kind', 'leptos_tiptap_island');
|
||||
setStatus(runtimeDescriptor, 'mounting-editor');
|
||||
enhanceEditorAttachmentLinksSoon();
|
||||
paneViewRegistry.set(runtimeDescriptor.paneRole, view);
|
||||
} catch (error) {
|
||||
unmountEditorViewBinding(view);
|
||||
|
||||
@@ -321,6 +321,51 @@ export const localFileOpenUrlForTiptap = (value, context) => {
|
||||
return url.toString();
|
||||
};
|
||||
|
||||
export const localFileOpenPathFromTiptapHref = (href) => {
|
||||
try {
|
||||
const 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 '';
|
||||
}
|
||||
};
|
||||
|
||||
export const fileNameFromPath = (path) => {
|
||||
const value = String(path || '').trim();
|
||||
return value.includes('/') ? value.split('/').pop() : value;
|
||||
};
|
||||
|
||||
export const attachmentClassForFileName = (fileName) => {
|
||||
const name = String(fileName || '').trim().toLowerCase();
|
||||
const ext = name.includes('.') ? name.split('.').pop() : '';
|
||||
if (['doc', 'docx', 'odt', 'rtf'].includes(ext)) return 'mnote-uploaded-attachment-row mnote-uploaded-attachment-word';
|
||||
if (['ppt', 'pptx', 'odp'].includes(ext)) return 'mnote-uploaded-attachment-row mnote-uploaded-attachment-ppt';
|
||||
if (['xls', 'xlsx', 'ods', 'csv'].includes(ext)) return 'mnote-uploaded-attachment-row mnote-uploaded-attachment-sheet';
|
||||
if (ext === 'pdf') return 'mnote-uploaded-attachment-row mnote-uploaded-attachment-pdf';
|
||||
if (['md', 'markdown', 'txt', 'json', 'js', 'ts', 'tsx', 'jsx', 'rs', 'py', 'go', 'java', 'kt', 'swift', 'c', 'cpp', 'h', 'hpp', 'css', 'scss', 'html', 'xml', 'yaml', 'yml', 'toml', 'ini', 'sh', 'bash', 'zsh', 'sql', 'vue', 'svelte'].includes(ext)) {
|
||||
return 'mnote-uploaded-attachment-row mnote-uploaded-attachment-code';
|
||||
}
|
||||
return 'mnote-uploaded-attachment-row mnote-uploaded-attachment-file';
|
||||
};
|
||||
|
||||
export const mergeClassNames = (...values) => {
|
||||
const seen = new Set();
|
||||
return values
|
||||
.flatMap((value) => String(value || '').split(/\s+/))
|
||||
.filter((name) => {
|
||||
if (!name || seen.has(name)) return false;
|
||||
seen.add(name);
|
||||
return true;
|
||||
})
|
||||
.join(' ');
|
||||
};
|
||||
|
||||
export const localAttachmentClassForTiptapHref = (href) => {
|
||||
const localFilePath = localFileOpenPathFromTiptapHref(href);
|
||||
return localFilePath ? attachmentClassForFileName(fileNameFromPath(localFilePath)) : '';
|
||||
};
|
||||
|
||||
export const localizeTiptapAssetUrls = (node, context) => {
|
||||
if (!node || typeof node !== 'object') return node;
|
||||
if (node.type === 'image' && node.attrs && typeof node.attrs.src === 'string') {
|
||||
@@ -329,7 +374,18 @@ export const localizeTiptapAssetUrls = (node, context) => {
|
||||
if (Array.isArray(node.marks)) {
|
||||
node.marks = node.marks.map((mark) => {
|
||||
if (!mark || mark.type !== 'link' || !mark.attrs || typeof mark.attrs.href !== 'string') return mark;
|
||||
return { ...mark, attrs: { ...mark.attrs, href: localFileOpenUrlForTiptap(mark.attrs.href, context) } };
|
||||
const href = localFileOpenUrlForTiptap(mark.attrs.href, context);
|
||||
const attachmentClass = localAttachmentClassForTiptapHref(href);
|
||||
const attrs = attachmentClass
|
||||
? {
|
||||
...mark.attrs,
|
||||
href,
|
||||
class: mergeClassNames(mark.attrs.class, attachmentClass),
|
||||
target: mark.attrs.target || '_blank',
|
||||
rel: mark.attrs.rel || 'noopener noreferrer nofollow',
|
||||
}
|
||||
: { ...mark.attrs, href };
|
||||
return { ...mark, attrs };
|
||||
});
|
||||
}
|
||||
if (Array.isArray(node.content)) {
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
// 当前先承接 SIDEBAR_TREE_JS 中的纯辅助函数。
|
||||
// SIDEBAR_TREE_JS 会优先委托到 window.__mnoteLocalUploadRuntime;
|
||||
// 模块未加载时,inline fallback 保持旧行为。
|
||||
import {
|
||||
editorDocumentFromTiptapDocument,
|
||||
legacyBlocksFromEditorDocument,
|
||||
} from './document-tiptap-conversion-runtime.js';
|
||||
|
||||
function uploadedAssetTitle(asset) {
|
||||
return String(asset && (asset.file_name || asset.title || asset.name) || '未命名附件').trim() || '未命名附件';
|
||||
@@ -11,6 +15,117 @@ function uploadedAssetUrl(asset) {
|
||||
return String(asset && (asset.sourcePath || asset.file_url || asset.signedUrl || asset.signed_url || asset.thumbnail_url) || '').trim();
|
||||
}
|
||||
|
||||
function dispatchUploadedEditorChange(editorRoot, editor, deps) {
|
||||
deps = deps || {};
|
||||
if (!(editorRoot instanceof HTMLElement) || !editor || typeof editor.getJSON !== 'function') return;
|
||||
var runtimeRoot = editorRoot.closest('[data-testid="mnote-leptos-tiptap-island-editor-root"]');
|
||||
if (!(runtimeRoot instanceof HTMLElement) || typeof CustomEvent !== 'function') return;
|
||||
try {
|
||||
editorRoot.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
} catch (_) {
|
||||
// ignore
|
||||
}
|
||||
var currentDocumentId = typeof deps.currentDocumentId === 'function' ? deps.currentDocumentId() : '';
|
||||
var payload = {
|
||||
protocol: 'mnote.leptos_tiptap.bridge.v1',
|
||||
runtime: '8123-leptos-tiptap-runtime',
|
||||
version: '1.1.0',
|
||||
source: 'mnote:leptos-tiptap-spike',
|
||||
event: 'mnote:leptos-tiptap-spike:change',
|
||||
payload: {
|
||||
documentId: String(currentDocumentId || '').trim() || null,
|
||||
workspaceId: null,
|
||||
title: '',
|
||||
content: editor.getJSON(),
|
||||
meta: {
|
||||
dirtyCount: Date.now(),
|
||||
editorFocused: document.activeElement === editorRoot || editorRoot.contains(document.activeElement),
|
||||
slashOpen: false,
|
||||
toolbarOpen: false,
|
||||
selectedBlockIndex: null,
|
||||
revision: null,
|
||||
conflictDetectionKey: null,
|
||||
readOnly: false
|
||||
}
|
||||
}
|
||||
};
|
||||
runtimeRoot.dispatchEvent(new CustomEvent('mnote:leptos-tiptap-spike:change', {
|
||||
detail: payload,
|
||||
bubbles: true
|
||||
}));
|
||||
}
|
||||
|
||||
function currentConflictDetectionKey() {
|
||||
var script = document.getElementById('__MNOTE_PAGE_AGGREGATE__');
|
||||
if (!script) return '';
|
||||
try {
|
||||
var body = JSON.parse(script.textContent || 'null');
|
||||
return String(body && (body.conflictDetectionKey || body.conflict_detection_key || body.fileVersion || body.file_version) || '').trim();
|
||||
} catch (_) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function persistLocalFolderSelfChangeSuppression(documentId, expiresAt) {
|
||||
var doc = String(documentId || '').trim();
|
||||
if (!doc) return;
|
||||
window.__mnoteLocalFolderSelfChangeSuppressions = window.__mnoteLocalFolderSelfChangeSuppressions || new Map();
|
||||
window.__mnoteLocalFolderSelfChangeSuppressions.set(doc, expiresAt);
|
||||
try {
|
||||
var key = 'mnote.localFolder.selfChangeSuppressions.v1';
|
||||
var existing = JSON.parse(window.sessionStorage.getItem(key) || '{}');
|
||||
existing[doc] = expiresAt;
|
||||
window.sessionStorage.setItem(key, JSON.stringify(existing));
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
async function persistUploadedEditorChange(editor, asset, deps) {
|
||||
deps = deps || {};
|
||||
var isLocalAsset = typeof deps.isLocalUploadedAsset === 'function' ? deps.isLocalUploadedAsset(asset) : isLocalUploadedAsset(asset);
|
||||
if (!isLocalAsset || !editor || typeof editor.getJSON !== 'function') return;
|
||||
var documentId = String(
|
||||
asset && (asset.document_id || asset.documentId)
|
||||
|| (typeof deps.currentDocumentId === 'function' ? deps.currentDocumentId() : '')
|
||||
|| ''
|
||||
).trim();
|
||||
var rootUri = String(
|
||||
asset && (asset.root_uri || asset.rootUri)
|
||||
|| (typeof deps.currentRootUri === 'function' ? deps.currentRootUri() : currentRootUri())
|
||||
|| ''
|
||||
).trim();
|
||||
if (!documentId || !rootUri) return;
|
||||
var tiptapDocument = editor.getJSON();
|
||||
var editorDocument = editorDocumentFromTiptapDocument({ documentId: documentId }, tiptapDocument);
|
||||
var content = legacyBlocksFromEditorDocument(editorDocument);
|
||||
var savePayload = {
|
||||
documentId: documentId,
|
||||
sourceKind: 'local_folder',
|
||||
rootUri: rootUri,
|
||||
expectedFileVersion: currentConflictDetectionKey(),
|
||||
contentFormat: 'editorBlocks',
|
||||
editorSource: 'local-upload-runtime',
|
||||
editorDocument: editorDocument,
|
||||
content: content,
|
||||
tiptapDocument: tiptapDocument,
|
||||
blockCount: Array.isArray(editorDocument.blocks) ? editorDocument.blocks.length : 0
|
||||
};
|
||||
persistLocalFolderSelfChangeSuppression(documentId, Date.now() + 5000);
|
||||
var response = await fetch('/api/documents/save', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify(savePayload)
|
||||
});
|
||||
var result = await response.json().catch(function() { return null; });
|
||||
if (!response.ok || !result || result.ok !== true) {
|
||||
var message = result && (result.message || (result.error && result.error.message)) || ('save_failed_' + response.status);
|
||||
document.documentElement.setAttribute('data-mnote-last-upload-save-error', message);
|
||||
throw new Error(message);
|
||||
}
|
||||
document.documentElement.setAttribute('data-mnote-last-upload-saved', 'true');
|
||||
document.documentElement.removeAttribute('data-mnote-last-upload-save-error');
|
||||
}
|
||||
|
||||
function currentRootUri() {
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
var fromUrl = (params.get('rootUri') || '').trim();
|
||||
@@ -270,6 +385,8 @@ async function insertUploadedAssetIntoEditor(asset, targetRoot, deps) {
|
||||
document.documentElement.setAttribute('data-mnote-last-upload-inserted', 'true');
|
||||
document.documentElement.setAttribute('data-mnote-last-upload-asset-id', assetId || '');
|
||||
document.documentElement.removeAttribute('data-mnote-last-upload-insert-error');
|
||||
dispatchUploadedEditorChange(editorRoot, editor, deps);
|
||||
await persistUploadedEditorChange(editor, asset, deps);
|
||||
} else {
|
||||
document.documentElement.setAttribute('data-mnote-last-upload-inserted', 'false');
|
||||
document.documentElement.setAttribute('data-mnote-last-upload-insert-error', 'insert_content_failed');
|
||||
|
||||
@@ -219,13 +219,18 @@ export const createSidebarAttachmentOpenRuntime = (dependencies = {}) => {
|
||||
|| isOfficeFileName(fileName)
|
||||
|| Boolean(localFilePath);
|
||||
if (!shouldEnhance) return;
|
||||
link.setAttribute('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);
|
||||
attachmentClassForFileName(fileName).split(/\s+/).forEach(function(name) {
|
||||
if (name) link.classList.add(name);
|
||||
});
|
||||
link.setAttribute('target', '_blank');
|
||||
link.setAttribute('rel', 'noopener noreferrer nofollow');
|
||||
if (localFilePath && !isOnlyOfficeAttachmentHref(href)) {
|
||||
void refreshLocalAttachmentExistence(link);
|
||||
return;
|
||||
}
|
||||
link.setAttribute('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 (isOnlyOfficeAttachmentHref(href)) {
|
||||
link.setAttribute('href', buildOnlyOfficeOpenPath({
|
||||
fileUrl: params.get('fileUrl') || '',
|
||||
@@ -237,11 +242,6 @@ export const createSidebarAttachmentOpenRuntime = (dependencies = {}) => {
|
||||
mode: params.get('mode') || 'view'
|
||||
}));
|
||||
}
|
||||
attachmentClassForFileName(fileName).split(/\s+/).forEach(function(name) {
|
||||
if (name) link.classList.add(name);
|
||||
});
|
||||
link.setAttribute('target', '_blank');
|
||||
link.setAttribute('rel', 'noopener noreferrer nofollow');
|
||||
void hydrateEditorAttachmentMeta(link);
|
||||
}
|
||||
|
||||
@@ -253,6 +253,12 @@ export const createSidebarAttachmentOpenRuntime = (dependencies = {}) => {
|
||||
observeEditorAttachmentRoots();
|
||||
enhanceEditorAttachmentLinks();
|
||||
};
|
||||
var attachmentInitialEnhanceAttempts = 0;
|
||||
var attachmentInitialEnhanceTimer = window.setInterval(function() {
|
||||
attachmentInitialEnhanceAttempts += 1;
|
||||
enhanceEditorAttachmentLinks();
|
||||
if (attachmentInitialEnhanceAttempts >= 120) window.clearInterval(attachmentInitialEnhanceTimer);
|
||||
}, 500);
|
||||
|
||||
function ensureAttachmentActions() {
|
||||
var existing = document.querySelector('[data-testid="mnote-attachment-actions"]');
|
||||
@@ -600,6 +606,26 @@ export const createSidebarAttachmentOpenRuntime = (dependencies = {}) => {
|
||||
window.__mnoteEnhanceEditorAttachmentLinks();
|
||||
scheduleEditorAttachmentEnhance();
|
||||
});
|
||||
[
|
||||
'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, '.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"]');
|
||||
|
||||
@@ -1234,6 +1234,7 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
buildOnlyOfficeOpenPath: buildOnlyOfficeOpenPath,
|
||||
inferOnlyOfficeFileType: inferOnlyOfficeFileType,
|
||||
currentDocumentId: currentDocumentId,
|
||||
currentRootUri: currentRootUri,
|
||||
uploadedAttachmentClass: uploadedAttachmentClass,
|
||||
uploadedFileSize: uploadedFileSize,
|
||||
cssEscape: cssEscape,
|
||||
|
||||
Reference in New Issue
Block a user