// MNote 本地上传运行时外置模块。 // 当前先承接 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() || '未命名附件'; } function uploadedAssetUrl(asset) { return String(asset && (asset.sourcePath || asset.file_url || asset.signedUrl || asset.signed_url || asset.thumbnail_url) || '').trim(); } function uploadedAssetMarkdownHref(asset) { var href = String(asset && (asset.markdownHref || asset.markdown_href) || '').trim(); if (href) { var normalizedHref = href.replace(/\\/g, '/'); if (normalizedHref.indexOf('../') === 0 || normalizedHref.indexOf('/../') >= 0) return ''; return href; } var relativePath = String(asset && (asset.markdownRelativePath || asset.markdown_relative_path) || '').trim().replace(/\\/g, '/'); if (!relativePath) return ''; if (relativePath.indexOf('../') === 0 || relativePath.indexOf('/../') >= 0) return ''; if (relativePath.indexOf('./') === 0) return relativePath; return './' + relativePath; } 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 firstNonEmptyString(values) { for (var i = 0; i < values.length; i += 1) { var value = String(values[i] || '').trim(); if (value) return value; } return ''; } function makeSaveOperationToken(prefix, documentId) { var doc = String(documentId || 'unknown').replace(/[^a-zA-Z0-9._:-]+/g, '-').slice(0, 80); var random = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function' ? crypto.randomUUID() : Date.now().toString(36) + '-' + Math.random().toString(36).slice(2); return prefix + ':' + doc + ':' + random; } function currentConflictDetectionKey(documentId, rootUri) { try { if (typeof window.__mnoteDebugDocumentSessions?.snapshot === 'function') { var snapshot = window.__mnoteDebugDocumentSessions.snapshot(); var sessions = Array.isArray(snapshot && snapshot.sessions) ? snapshot.sessions : []; var targetDocumentId = String(documentId || '').trim(); var targetRootUri = String(rootUri || '').trim(); for (var i = 0; i < sessions.length; i += 1) { var session = sessions[i] || {}; if (targetDocumentId && String(session.documentId || '').trim() !== targetDocumentId) continue; if (targetRootUri && String(session.rootUri || '').trim() !== targetRootUri) continue; var sessionKey = firstNonEmptyString([ session.lastExternalConflictDetectionKey, session.conflictDetectionKey, session.fileVersion ]); if (sessionKey) return sessionKey; } } } catch (_) { // 继续尝试从 page aggregate script 读取版本。 } var script = document.getElementById('__MNOTE_PAGE_AGGREGATE__'); if (script) { try { var aggregate = JSON.parse(script.textContent || 'null'); var body = aggregate && typeof aggregate.body === 'object' ? aggregate.body : null; var key = firstNonEmptyString([ body && body.conflictDetectionKey, body && body.conflict_detection_key, body && body.fileVersion, body && body.file_version, aggregate && aggregate.conflictDetectionKey, aggregate && aggregate.conflict_detection_key, aggregate && aggregate.fileVersion, aggregate && aggregate.file_version ]); if (key) return key; } catch (_) { // ignore } } return ''; } function localUploadSessionConflict(documentId, rootUri) { try { if (typeof window.__mnoteDebugDocumentSessions?.snapshot !== 'function') return null; var snapshot = window.__mnoteDebugDocumentSessions.snapshot(); var sessions = Array.isArray(snapshot && snapshot.sessions) ? snapshot.sessions : []; var targetDocumentId = String(documentId || '').trim(); var targetRootUri = String(rootUri || '').trim(); for (var i = 0; i < sessions.length; i += 1) { var session = sessions[i] || {}; if (targetDocumentId && String(session.documentId || '').trim() !== targetDocumentId) continue; if (targetRootUri && String(session.rootUri || '').trim() !== targetRootUri) continue; var status = String(session.status || '').trim(); if (status === 'external-change-conflict' || session.hasExternalConflict === true || session.lastExternalConflictEnvelope) { return { status: status || 'external-change-conflict', dirtyState: String(session.dirtyState || ''), message: String(session.lastExternalConflictEnvelope?.message || '当前文档存在文件冲突,请先处理冲突后再上传附件') }; } } } catch (_) { return null; } return null; } 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(documentId, rootUri), writeIntentId: makeSaveOperationToken('intent:local-upload', documentId), saveOperationId: makeSaveOperationToken('save:local-upload', documentId), 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); if (response.status === 409 && asset && asset.id) { document.documentElement.setAttribute('data-mnote-last-upload-orphaned-asset-id', String(asset.id || '')); document.documentElement.setAttribute('data-mnote-last-upload-orphaned-policy', 'asset-kept-reference-unsaved-retry-required'); } throw new Error(message); } var saved = result.result && typeof result.result === 'object' ? result.result : {}; var fileVersion = String(saved.fileVersion || saved.conflictDetectionKey || saved.conflict_detection_key || '').trim(); try { window.dispatchEvent(new CustomEvent('mnote:local-upload-editor-save-completed', { detail: { documentId: documentId, rootUri: rootUri, fileVersion: fileVersion, writeIntentId: String(saved.writeIntentId || savePayload.writeIntentId || '').trim(), saveOperationId: String(saved.saveOperationId || savePayload.saveOperationId || '').trim(), revision: Number.isInteger(saved.revision) ? saved.revision : null, serialized: JSON.stringify(tiptapDocument), source: 'local-upload-runtime' } })); } catch (_) {} document.documentElement.setAttribute('data-mnote-last-upload-saved', 'true'); document.documentElement.removeAttribute('data-mnote-last-upload-save-error'); document.documentElement.removeAttribute('data-mnote-last-upload-orphaned-asset-id'); document.documentElement.removeAttribute('data-mnote-last-upload-orphaned-policy'); } function currentRootUri() { var params = new URLSearchParams(window.location.search); var fromUrl = (params.get('rootUri') || '').trim(); if (fromUrl) return fromUrl; return document.body instanceof HTMLElement ? (document.body.getAttribute('data-mnote-root-uri') || '').trim() : ''; } function editorUploadRootFromElementWithDeps(target, deps) { if (deps && typeof deps.editorUploadRootFromElement === 'function') { return deps.editorUploadRootFromElement(target); } if (!(target instanceof Element)) return null; var selector = String(deps && deps.rootSelector || '[data-editor-host-kind="leptos_tiptap_island"], [data-editor-host-kind="leptos_tiptap_resource"]'); var root = target.closest(selector); return root instanceof HTMLElement ? root : null; } function resolveEditorUploadContext(detail, deps) { var root = null; var selector = detail && detail.editorRootSelector ? String(detail.editorRootSelector) : ''; if (selector) { try { var selected = document.querySelector(selector); if (selected instanceof HTMLElement) root = selected; } catch (_) {} } if (!root && window.__mnoteIntendedSlashRoot instanceof HTMLElement && window.__mnoteIntendedSlashRoot.isConnected) { root = window.__mnoteIntendedSlashRoot; } if (!root && window.__mnoteLastEditorUploadRoot instanceof HTMLElement && window.__mnoteLastEditorUploadRoot.isConnected) { root = window.__mnoteLastEditorUploadRoot; } if (!root && document.activeElement instanceof Element) { root = editorUploadRootFromElementWithDeps(document.activeElement, deps); } if (!root) { var rootSelector = String(deps && deps.rootSelector || '[data-editor-host-kind="leptos_tiptap_island"], [data-editor-host-kind="leptos_tiptap_resource"]'); var focused = document.querySelector(rootSelector + ' .ProseMirror:focus-within'); root = editorUploadRootFromElementWithDeps(focused, deps); } if (!root) { root = document.querySelector('[data-editor-host-kind="leptos_tiptap_island"][data-pane-role="primary"]'); } var pane = root instanceof Element ? root.closest('.document-pane[data-pane-role]') : null; var shell = root instanceof Element ? root.closest('.document-shell[data-document-id]') : null; var currentDocumentId = deps && typeof deps.currentDocumentId === 'function' ? deps.currentDocumentId : function() { return ''; }; var resolveWorkspaceId = deps && typeof deps.resolveWorkspaceId === 'function' ? deps.resolveWorkspaceId : function() { return ''; }; return { root: root instanceof HTMLElement ? root : null, documentId: String( detail && detail.documentId || (root instanceof HTMLElement && root.getAttribute('data-document-id')) || (pane instanceof HTMLElement && pane.getAttribute('data-pane-document-id')) || (shell instanceof HTMLElement && shell.getAttribute('data-document-id')) || currentDocumentId() || '' ).trim(), workspaceId: String( detail && detail.workspaceId || (root instanceof HTMLElement && root.getAttribute('data-workspace-id')) || (pane instanceof HTMLElement && pane.getAttribute('data-pane-workspace-id')) || (shell instanceof HTMLElement && shell.getAttribute('data-workspace-id')) || resolveWorkspaceId(document.body) || '' ).trim() }; } function editorRootFromUploadOptions(options, deps) { if (options && options.editorRoot instanceof HTMLElement) return options.editorRoot; if (window.__mnoteIntendedSlashRoot instanceof HTMLElement && window.__mnoteIntendedSlashRoot.isConnected) { return window.__mnoteIntendedSlashRoot; } if (window.__mnoteLastEditorUploadRoot instanceof HTMLElement && window.__mnoteLastEditorUploadRoot.isConnected) { return window.__mnoteLastEditorUploadRoot; } var rootSelector = String(deps && deps.rootSelector || '[data-editor-host-kind="leptos_tiptap_island"], [data-editor-host-kind="leptos_tiptap_resource"]'); var focused = document.querySelector(rootSelector + ' .ProseMirror:focus-within'); return editorUploadRootFromElementWithDeps(focused, deps); } function openEditorUploadFilePicker(detail, deps) { var uploadContext = resolveEditorUploadContext(detail || {}, deps || {}); var input = document.createElement('input'); input.type = 'file'; input.multiple = detail && detail.multiple !== false; if (detail && detail.accept) input.accept = String(detail.accept); input.style.position = 'fixed'; input.style.left = '-9999px'; input.style.top = '-9999px'; document.body.appendChild(input); input.addEventListener('change', function() { var files = Array.from(input.files || []); input.remove(); var resolveWorkspaceId = deps && typeof deps.resolveWorkspaceId === 'function' ? deps.resolveWorkspaceId : function() { return ''; }; var currentDocumentId = deps && typeof deps.currentDocumentId === 'function' ? deps.currentDocumentId : function() { return ''; }; var uploadFilesWithResolvedTarget = deps && typeof deps.uploadFilesWithResolvedTarget === 'function' ? deps.uploadFilesWithResolvedTarget : null; if (!uploadFilesWithResolvedTarget) return; void uploadFilesWithResolvedTarget(files, { workspaceId: uploadContext.workspaceId || resolveWorkspaceId(document.body), documentId: uploadContext.documentId || currentDocumentId(), targetRowId: null, uploadIntent: 'editor.markdown.attach' }, { insertIntoEditor: detail && detail.insertIntoEditor !== false, editorRoot: uploadContext.root }); }, { once: true }); input.click(); } async function fetchWithTimeout(input, init, timeoutMs, label) { var controller = typeof AbortController === 'function' ? new AbortController() : null; var timer = 0; var timeout = Math.max(1000, Number(timeoutMs) || 15000); try { if (controller) { timer = window.setTimeout(function() { controller.abort(); }, timeout); } var nextInit = Object.assign({}, init || {}); if (controller) nextInit.signal = controller.signal; return await fetch(input, nextInit); } catch (error) { if (error && error.name === 'AbortError') { throw new Error((label || '请求') + '超时'); } throw error; } finally { if (timer) window.clearTimeout(timer); } } function uploadTimeoutMsForFile(file, requestedMs) { var requested = Number(requestedMs) || 0; var size = Number(file && file.size || 0); if (!Number.isFinite(size) || size <= 0) return Math.max(requested, 15000); var sizeBased = 15000 + Math.ceil(size / (1024 * 1024)) * 12000; return Math.min(10 * 60 * 1000, Math.max(requested, 60000, sizeBased)); } async function uploadLocalFolderAsset(file, plan, context) { var rootUri = String(context && context.rootUri || '').trim() || currentRootUri(); var documentId = String( plan && plan.targetDocumentId || context && context.documentId || '' ).trim(); var hasFolderTarget = plan && Object.prototype.hasOwnProperty.call(plan, 'targetRelativePath'); var uploadIntent = String( plan && plan.uploadIntent || (hasFolderTarget ? 'filetree.folder.drop' : 'editor.markdown.attach') ).trim(); if (!rootUri || !uploadIntent) { throw new Error('本地 Markdown 上传缺少 rootUri 或 documentId'); } var localForm = new FormData(); localForm.append('file', file); localForm.append('rootUri', rootUri); localForm.append('uploadIntent', uploadIntent); if (documentId) localForm.append('documentId', documentId); if (hasFolderTarget) localForm.append('targetRelativePath', String(plan.targetRelativePath || '')); localForm.append('kind', file && String(file.type || '').indexOf('image/') === 0 ? 'image' : 'attachment'); var localResponse = await fetchWithTimeout('/api/local-folder/assets/upload', { method: 'POST', credentials: 'include', body: localForm }, uploadTimeoutMsForFile(file, context && context.timeoutMs), '本地上传'); var localPayload = await localResponse.json().catch(function() { return null; }); if (!localResponse.ok || !localPayload || !localPayload.asset) { throw new Error(localPayload && localPayload.error ? localPayload.error : '上传失败'); } return { asset: localPayload.asset, documentId: documentId }; } async function uploadMediaAsset(file, plan, context) { document.documentElement.setAttribute('data-mnote-media-upload-retired', 'true'); throw new Error('旧 Convex Files 上传链已退役;local-first 附件请使用本地文件夹上传目标。'); } async function insertUploadedAssetIntoEditor(asset, targetRoot, deps) { deps = deps || {}; var editorRoot = targetRoot instanceof HTMLElement ? targetRoot.querySelector('.editor-surface .ProseMirror') : document.querySelector('.editor-surface .ProseMirror'); if (targetRoot instanceof HTMLElement) window.__mnoteLastEditorUploadRoot = targetRoot; var editor = editorRoot && editorRoot.editor; if (!editor || !editor.chain) { document.documentElement.setAttribute('data-mnote-last-upload-inserted', 'false'); document.documentElement.setAttribute('data-mnote-last-upload-insert-error', 'editor_unavailable'); return false; } var title = typeof deps.uploadedAssetTitle === 'function' ? deps.uploadedAssetTitle(asset) : uploadedAssetTitle(asset); var markdownHref = typeof deps.uploadedAssetMarkdownHref === 'function' ? deps.uploadedAssetMarkdownHref(asset) : uploadedAssetMarkdownHref(asset); var localOpenUrl = typeof deps.localAssetOpenUrl === 'function' ? deps.localAssetOpenUrl(asset, false) : localAssetOpenUrl(asset, false); var fallbackUrl = typeof deps.uploadedAssetUrl === 'function' ? deps.uploadedAssetUrl(asset) : uploadedAssetUrl(asset); var imageUrl = localOpenUrl || fallbackUrl || markdownHref; var url = markdownHref || localOpenUrl || fallbackUrl; var type = typeof deps.uploadedAssetType === 'function' ? deps.uploadedAssetType(asset) : uploadedAssetType(asset); var assetId = String(asset && asset.id || '').trim(); var sizeLabel = typeof deps.uploadedFileSize === 'function' ? deps.uploadedFileSize(asset) : uploadedFileSize(asset); try { if (type === 'image' && imageUrl) { var imageInserted = editor.chain().focus().setImage({ src: imageUrl, alt: title, title: title }).run() === true; if (imageInserted) { 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_image_failed'); } return imageInserted; } var isLocalAsset = typeof deps.isLocalUploadedAsset === 'function' ? deps.isLocalUploadedAsset(asset) : isLocalUploadedAsset(asset); var href = markdownHref || url; if (href) { var inserted = editor.chain().focus().insertContent([ { type: 'paragraph', content: [{ type: 'text', text: title, marks: [{ type: 'link', attrs: { href: href, target: '_blank', rel: 'noopener noreferrer nofollow', class: typeof deps.uploadedAttachmentClass === 'function' ? deps.uploadedAttachmentClass(asset) : uploadedAttachmentClass(asset) } }] }] }, { type: 'paragraph' } ]).focus('end').run() === true; if (inserted) { 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'); } window.setTimeout(function annotateUploadedLink(attempt) { if (targetRoot instanceof HTMLElement) window.__mnoteLastEditorUploadRoot = targetRoot; if (typeof deps.enhanceEditorAttachmentLinks === 'function') deps.enhanceEditorAttachmentLinks(); var cssEscape = typeof deps.cssEscape === 'function' ? deps.cssEscape : function(value) { return String(value || '').replace(/"/g, '\\"'); }; var selector = assetId ? '.editor-surface .ProseMirror a[href*="' + cssEscape(assetId) + '"]' : '.editor-surface .ProseMirror a'; var link = targetRoot instanceof HTMLElement ? targetRoot.querySelector(selector) : document.querySelector(selector); if (!(link instanceof HTMLElement) && assetId) { var scope = targetRoot instanceof HTMLElement ? targetRoot : document; link = Array.from(scope.querySelectorAll('.editor-surface .ProseMirror a')).find(function(candidate) { var href = String(candidate.getAttribute('href') || ''); try { href = decodeURIComponent(href); } catch (_) {} return href.indexOf(assetId) >= 0; }) || null; } if (link instanceof HTMLElement) { if (assetId) link.setAttribute('data-asset-id', assetId); if (sizeLabel) link.setAttribute('data-file-size', sizeLabel); } else if (Number(attempt) < 20) { window.setTimeout(function() { annotateUploadedLink(Number(attempt) + 1); }, 50); } }, 0, 0); return inserted; } } catch (error) { console.warn('[mnote upload] insert uploaded asset failed', error); } return false; } async function uploadFileToMediaAsset(file, plan, options, deps) { deps = deps || {}; options = options || {}; var getCurrentSourceKind = typeof deps.currentSourceKind === 'function' ? deps.currentSourceKind : function() { return ''; }; var getCurrentRootUri = typeof deps.currentRootUri === 'function' ? deps.currentRootUri : currentRootUri; var getCurrentDocumentId = typeof deps.currentDocumentId === 'function' ? deps.currentDocumentId : function() { return ''; }; var resolveEditorRoot = typeof deps.editorRootFromUploadOptions === 'function' ? deps.editorRootFromUploadOptions : function(nextOptions) { return editorRootFromUploadOptions(nextOptions, deps); }; var insertIntoEditor = typeof deps.insertUploadedAssetIntoEditor === 'function' ? deps.insertUploadedAssetIntoEditor : function(asset, targetRoot) { return insertUploadedAssetIntoEditor(asset, targetRoot, deps); }; var dispatch = typeof deps.dispatchEvent === 'function' ? deps.dispatchEvent : function(event) { return window.dispatchEvent(event); }; var EventCtor = typeof deps.CustomEvent === 'function' ? deps.CustomEvent : window.CustomEvent; if (getCurrentSourceKind() === 'local_folder') { var rootUri = getCurrentRootUri(); var documentId = String(plan && plan.targetDocumentId || getCurrentDocumentId() || '').trim(); if (String(plan && plan.uploadIntent || '').trim() === 'editor.markdown.attach' && documentId) { var conflict = localUploadSessionConflict(documentId, rootUri); if (conflict) { document.documentElement.setAttribute('data-mnote-last-upload-blocked-conflict', 'true'); document.documentElement.setAttribute('data-mnote-last-upload-save-error', conflict.message || '当前文档存在文件冲突,请先处理冲突后再上传附件'); throw new Error(conflict.message || '当前文档存在文件冲突,请先处理冲突后再上传附件'); } document.documentElement.removeAttribute('data-mnote-last-upload-blocked-conflict'); } if (String(plan && plan.uploadIntent || '').trim() === 'editor.markdown.attach' && documentId) { persistLocalFolderSelfChangeSuppression(documentId, Date.now() + 8000); } var localResult = await uploadLocalFolderAsset(file, plan, { rootUri: rootUri, documentId: documentId, timeoutMs: uploadTimeoutMsForFile(file, 15000) }); var localAsset = localResult && localResult.asset ? localResult.asset : null; if (!localAsset) { throw new Error('上传失败'); } if (options.insertIntoEditor) { await insertIntoEditor(localAsset, resolveEditorRoot(options)); } if (!options.insertIntoEditor && typeof deps.refreshLocalFolderSidebarSnapshot === 'function') { void deps.refreshLocalFolderSidebarSnapshot(); } dispatch(new EventCtor('wolai:local-assets-changed', { detail: { docId: documentId, asset: localAsset, assetIds: [localAsset.id] } })); return localAsset; } var mediaResult = await uploadMediaAsset(file, plan, { timeoutMs: 15000 }); var mediaAsset = mediaResult && mediaResult.asset ? mediaResult.asset : null; if (!mediaAsset) { throw new Error('上传失败'); } if (typeof deps.appendUploadedAssetRow === 'function') { deps.appendUploadedAssetRow(mediaAsset, plan && plan.targetDocumentId); } if (options.insertIntoEditor) { await insertIntoEditor(mediaAsset, resolveEditorRoot(options)); } dispatch(new EventCtor('wolai:assets-changed', { detail: { docId: plan && plan.targetDocumentId, asset: mediaAsset, assetIds: [mediaAsset.id] } })); return mediaAsset; } async function uploadFilesWithResolvedTarget(files, detail, options, deps) { deps = deps || {}; var resolveFileTreeUploadTarget = typeof deps.resolveFileTreeUploadTarget === 'function' ? deps.resolveFileTreeUploadTarget : null; var uploadFileToMediaAsset = typeof deps.uploadFileToMediaAsset === 'function' ? deps.uploadFileToMediaAsset : null; if (!resolveFileTreeUploadTarget || !uploadFileToMediaAsset) { throw new Error('上传运行时缺少目标解析或上传函数'); } var list = Array.from(files || []).filter(Boolean); if (!list.length) return []; var plan = await resolveFileTreeUploadTarget(detail || {}); var uploaded = []; var errors = []; for (var i = 0; i < list.length; i += 1) { try { uploaded.push(await uploadFileToMediaAsset(list[i], plan, options || {})); } catch (error) { errors.push(list[i].name + ': ' + (error && error.message ? error.message : '上传失败')); } } if (errors.length) { var message = '部分文件上传失败:\n' + errors.slice(0, 6).join('\n') + (errors.length > 6 ? '\n...' : ''); if (typeof deps.alert === 'function') { deps.alert(message); } else { window.alert(message); } } return uploaded; } function localAssetOpenUrl(asset, download, context) { if (!isLocalUploadedAsset(asset)) return ''; var rootUri = String(context && context.rootUri || asset && (asset.rootUri || asset.root_uri) || '').trim() || currentRootUri(); var rootRelativePath = String(asset && (asset.rootRelativePath || asset.root_relative_path) || '').trim(); if (!rootUri || !rootRelativePath) return ''; var url = new URL('/api/local-folder/files/open', window.location.origin); url.searchParams.set('rootUri', rootUri); url.searchParams.set('path', rootRelativePath); if (download) url.searchParams.set('download', 'true'); return url.toString(); } function uploadedAssetType(asset) { return String(asset && (asset.asset_type || asset.assetType || asset.mime_type || '') || '').trim(); } function fileTreeIconKindForFileName(fileName) { var name = String(fileName || '').trim().toLowerCase(); var ext = name.indexOf('.') >= 0 ? name.split('.').pop() : ''; if (['doc', 'docx', 'odt', 'rtf', 'ppt', 'pptx', 'odp', 'xls', 'xlsx', 'ods', 'csv'].indexOf(ext) >= 0) return 'office'; if (ext === 'md' || ext === 'markdown') return 'markdown'; if (ext === 'pdf') return 'pdf'; if (['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'bmp'].indexOf(ext) >= 0) return 'image'; if (['html', 'htm', 'css', 'scss', 'less', 'js', 'jsx', 'ts', 'tsx', 'mjs', 'cjs', 'vue', 'svelte', 'astro'].indexOf(ext) >= 0) return 'web'; if (['json', 'jsonc', 'json5', 'toml', 'yaml', 'yml', 'ini', 'env', 'xml', 'lock', 'hcl', 'tf', 'tfvars', 'nix', 'properties', 'conf', 'cfg', 'config', 'service', 'desktop', 'gitignore', 'gitattributes', 'editorconfig', 'npmrc', 'prettierrc', 'eslintrc'].indexOf(ext) >= 0 || ['.dockerignore', '.editorconfig', '.env', '.eslintrc', '.gitattributes', '.gitignore', '.npmrc', '.prettierrc', 'dockerfile', 'containerfile', 'makefile', 'cmakelists.txt', 'gemfile', 'rakefile', 'procfile'].indexOf(name) >= 0) return 'config'; if (['rs', 'py', 'go', 'java', 'c', 'cpp', 'h', 'hpp', 'cs', 'php', 'rb', 'sh', 'bash', 'zsh', 'sql', 'lua', 'dart', 'kt', 'kts', 'swift', 'scala', 'gradle', 'groovy', 'clj', 'ex', 'exs', 'erl', 'hrl', 'fs', 'fsx', 'r', 'jl', 'm', 'mm', 'pl', 'pm', 'ps1', 'bat', 'cmd', 'psm1', 'psd1', 'proto', 'graphql', 'gql', 'prisma', 'cmake', 'bazel', 'bzl'].indexOf(ext) >= 0) return 'code'; return ''; } function isLocalUploadedAsset(asset) { var id = String(asset && asset.id || '').trim(); return String(asset && asset.sourceKind || '').trim() === 'local_folder' || id.indexOf('local:asset:') === 0; } function uploadedAssetExtension(asset) { var match = uploadedAssetTitle(asset).toLowerCase().match(/\.([a-z0-9]+)$/); return match ? match[1] : ''; } function isNonOfficeAttachmentName(name, ext) { var codeFileNames = [ '.dockerignore', '.editorconfig', '.env', '.eslintrc', '.gitattributes', '.gitignore', '.npmrc', '.prettierrc', 'dockerfile', 'makefile', 'cmakelists.txt', 'gemfile', 'rakefile', 'procfile' ]; return [ 'pdf', 'toml', 'json', 'yaml', 'yml', 'md', 'markdown', 'txt', 'ini', 'env', 'xml', 'html', 'htm', 'css', 'scss', 'less', 'js', 'jsx', 'ts', 'tsx', 'mjs', 'cjs', 'py', 'rs', 'go', 'java', 'c', 'cpp', 'h', 'hpp', 'cs', 'php', 'rb', 'sh', 'bash', 'zsh', 'sql', 'lock', 'log', 'vue', 'svelte', 'astro', 'jsonc', 'json5', 'mts', 'cts', 'lua', 'dart', 'kt', 'kts', 'swift', 'scala', 'gradle', 'groovy', 'clj', 'ex', 'exs', 'erl', 'hrl', 'fs', 'fsx', 'r', 'jl', 'm', 'mm', 'pl', 'pm', 'ps1', 'bat', 'cmd', 'psm1', 'psd1', 'dockerfile', 'containerfile', 'proto', 'graphql', 'gql', 'prisma', 'tf', 'tfvars', 'hcl', 'nix', 'cmake', 'bazel', 'bzl', 'properties', 'conf', 'cfg', 'config', 'service', 'desktop', 'gitignore', 'gitattributes', 'editorconfig', 'npmrc', 'prettierrc', 'eslintrc' ].indexOf(ext) >= 0 || codeFileNames.indexOf(name) >= 0; } function attachmentExtensionFromFileName(fileName) { var name = String(fileName || '').trim().toLowerCase(); return name.indexOf('.') >= 0 ? name.split('.').pop() : ''; } function isPdfAttachmentFileName(fileName) { return attachmentExtensionFromFileName(fileName) === 'pdf'; } function isCodeAttachmentFileName(fileName) { var name = String(fileName || '').trim().toLowerCase(); var ext = attachmentExtensionFromFileName(name); return ext !== 'pdf' && isNonOfficeAttachmentName(name, ext); } function inferCodeAttachmentLanguage(fileName) { var name = String(fileName || '').trim().toLowerCase(); var ext = attachmentExtensionFromFileName(name); var byName = { 'dockerfile': 'dockerfile', 'makefile': 'makefile', 'cmakelists.txt': 'cmake', '.gitignore': 'gitignore', '.gitattributes': 'gitattributes', '.editorconfig': 'ini', '.env': 'dotenv' }; if (byName[name]) return byName[name]; var byExt = { bash: 'bash', bat: 'batch', c: 'c', cjs: 'javascript', cmd: 'batch', conf: 'text', cpp: 'cpp', cs: 'csharp', css: 'css', cts: 'typescript', dart: 'dart', dockerfile: 'dockerfile', env: 'dotenv', go: 'go', gql: 'graphql', gradle: 'groovy', graphql: 'graphql', h: 'c', hcl: 'hcl', hpp: 'cpp', htm: 'html', html: 'html', ini: 'ini', java: 'java', js: 'javascript', json: 'json', json5: 'json', jsonc: 'jsonc', jsx: 'javascript', kt: 'kotlin', kts: 'kotlin', less: 'less', log: 'text', lua: 'lua', m: 'objective-c', markdown: 'markdown', md: 'markdown', mjs: 'javascript', mts: 'typescript', nix: 'nix', php: 'php', pl: 'perl', pm: 'perl', prisma: 'prisma', proto: 'protobuf', ps1: 'powershell', py: 'python', r: 'r', rb: 'ruby', rs: 'rust', scss: 'scss', sh: 'bash', sql: 'sql', svelte: 'svelte', swift: 'swift', tf: 'terraform', tfvars: 'terraform', toml: 'toml', ts: 'typescript', tsx: 'typescript', txt: 'text', vue: 'vue', xml: 'xml', yaml: 'yaml', yml: 'yaml', zsh: 'bash' }; return byExt[ext] || 'text'; } function attachmentClassForFileName(fileName) { var name = String(fileName || '').trim().toLowerCase(); var ext = name.indexOf('.') >= 0 ? name.split('.').pop() : ''; if (['doc', 'docx', 'odt', 'rtf'].indexOf(ext) >= 0) return 'mnote-uploaded-attachment-row mnote-uploaded-attachment-word'; if (['ppt', 'pptx', 'odp'].indexOf(ext) >= 0) return 'mnote-uploaded-attachment-row mnote-uploaded-attachment-ppt'; if (['xls', 'xlsx', 'ods', 'csv'].indexOf(ext) >= 0) return 'mnote-uploaded-attachment-row mnote-uploaded-attachment-sheet'; if (ext === 'pdf') return 'mnote-uploaded-attachment-row mnote-uploaded-attachment-pdf'; if (isNonOfficeAttachmentName(name, ext)) { return 'mnote-uploaded-attachment-row mnote-uploaded-attachment-code'; } return 'mnote-uploaded-attachment-row mnote-uploaded-attachment-file'; } function uploadedAttachmentClass(asset) { return attachmentClassForFileName(uploadedAssetTitle(asset)); } function uploadedFileSize(asset) { var size = Number(asset && (asset.file_size || asset.fileSize) || 0); if (!Number.isFinite(size) || size <= 0) return ''; if (size >= 1024 * 1024) return (size / 1024 / 1024).toFixed(size >= 10 * 1024 * 1024 ? 1 : 2) + ' MB'; if (size >= 1024) return (size / 1024).toFixed(size >= 100 * 1024 ? 0 : 2) + ' KB'; return String(Math.round(size)) + ' B'; } window.__mnoteLocalUploadRuntime = { uploadedAssetTitle: uploadedAssetTitle, uploadedAssetUrl: uploadedAssetUrl, uploadedAssetMarkdownHref: uploadedAssetMarkdownHref, resolveEditorUploadContext: resolveEditorUploadContext, editorRootFromUploadOptions: editorRootFromUploadOptions, openEditorUploadFilePicker: openEditorUploadFilePicker, fetchWithTimeout: fetchWithTimeout, uploadTimeoutMsForFile: uploadTimeoutMsForFile, uploadLocalFolderAsset: uploadLocalFolderAsset, uploadMediaAsset: uploadMediaAsset, insertUploadedAssetIntoEditor: insertUploadedAssetIntoEditor, uploadFileToMediaAsset: uploadFileToMediaAsset, uploadFilesWithResolvedTarget: uploadFilesWithResolvedTarget, localAssetOpenUrl: localAssetOpenUrl, uploadedAssetType: uploadedAssetType, fileTreeIconKindForFileName: fileTreeIconKindForFileName, isLocalUploadedAsset: isLocalUploadedAsset, uploadedAssetExtension: uploadedAssetExtension, isNonOfficeAttachmentName: isNonOfficeAttachmentName, attachmentExtensionFromFileName: attachmentExtensionFromFileName, isPdfAttachmentFileName: isPdfAttachmentFileName, isCodeAttachmentFileName: isCodeAttachmentFileName, inferCodeAttachmentLanguage: inferCodeAttachmentLanguage, attachmentClassForFileName: attachmentClassForFileName, uploadedAttachmentClass: uploadedAttachmentClass, uploadedFileSize: uploadedFileSize };