feat: consolidate local-first mnote web runtime
This commit is contained in:
@@ -55,15 +55,91 @@ function dispatchUploadedEditorChange(editorRoot, editor, deps) {
|
||||
}));
|
||||
}
|
||||
|
||||
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 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) {
|
||||
@@ -101,7 +177,9 @@ async function persistUploadedEditorChange(editor, asset, deps) {
|
||||
documentId: documentId,
|
||||
sourceKind: 'local_folder',
|
||||
rootUri: rootUri,
|
||||
expectedFileVersion: currentConflictDetectionKey(),
|
||||
expectedFileVersion: currentConflictDetectionKey(documentId, rootUri),
|
||||
writeIntentId: makeSaveOperationToken('intent:local-upload', documentId),
|
||||
saveOperationId: makeSaveOperationToken('save:local-upload', documentId),
|
||||
contentFormat: 'editorBlocks',
|
||||
editorSource: 'local-upload-runtime',
|
||||
editorDocument: editorDocument,
|
||||
@@ -120,10 +198,32 @@ async function persistUploadedEditorChange(editor, asset, deps) {
|
||||
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() {
|
||||
@@ -391,7 +491,7 @@ async function insertUploadedAssetIntoEditor(asset, targetRoot, deps) {
|
||||
document.documentElement.setAttribute('data-mnote-last-upload-inserted', 'false');
|
||||
document.documentElement.setAttribute('data-mnote-last-upload-insert-error', 'insert_content_failed');
|
||||
}
|
||||
window.setTimeout(function() {
|
||||
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, '\\"'); };
|
||||
@@ -401,12 +501,22 @@ async function insertUploadedAssetIntoEditor(asset, targetRoot, deps) {
|
||||
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) {
|
||||
link.setAttribute('data-mnote-attachment-link', 'true');
|
||||
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, 0);
|
||||
return inserted;
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -434,6 +544,18 @@ async function uploadFileToMediaAsset(file, plan, options, deps) {
|
||||
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,
|
||||
@@ -446,7 +568,7 @@ async function uploadFileToMediaAsset(file, plan, options, deps) {
|
||||
if (options.insertIntoEditor) {
|
||||
await insertIntoEditor(localAsset, resolveEditorRoot(options));
|
||||
}
|
||||
if (typeof deps.refreshLocalFolderSidebarSnapshot === 'function') {
|
||||
if (!options.insertIntoEditor && typeof deps.refreshLocalFolderSidebarSnapshot === 'function') {
|
||||
void deps.refreshLocalFolderSidebarSnapshot();
|
||||
}
|
||||
dispatch(new EventCtor('wolai:local-assets-changed', {
|
||||
@@ -528,6 +650,9 @@ function fileTreeIconKindForFileName(fileName) {
|
||||
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 '';
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user