feat: EditorRuntimeActor - 三层缓存/delta/事件架构

Phase A — EditorRuntimeActor 内存缓存层
- 新增 editor_actor.rs: EditorBlockDocument 内存态 + apply_command + load_or_init
- block.rs 四个写工具(replace/insert/delete/move)接入 actor 路径
- editor_actor feature flag(MNOTE_WEB_ENABLE_EDITOR_ACTOR=true 默认开启)
- bridge-runtime 三个核心函数公开化
- rust-toolchain: 1.89 → stable(修复 spike WASM 编译阻塞)

Phase B — 编辑器增量 delta channel
- BlockDelta/DeltaOperation 类型 + actor.build_block_delta()
- leptos-tiptap spike: mnote:editor:block-delta CustomEvent 监听 + JSON patch
- DocumentAiAgentPanel: 拦截 blockDelta → window dispatchEvent
- 工具响应含 blockDelta 字段供前端消费

Phase C — 事件 stream delta
- broadcast channel 在 AppState/actor/SSE 三层贯通
- tree_events SSE 端点发 block.delta 事件
- 旧客户端降级兼容

环境修复
- rustc recursion_limit = 1024(修复 Leptos SSR 类型深度溢出)
- run-convex-deploy.js(封装 Convex function 部署到本地后端 3210)

ref: design/07-ai/process/7-13-page-block-editor-runtime-actor-v1.md
This commit is contained in:
lix-2026
2026-05-16 22:03:30 +08:00
parent d8bfaea306
commit f292c6710a
101 changed files with 13618 additions and 2416 deletions
+322 -24
View File
@@ -50,7 +50,7 @@ const SIDEBAR_TREE_JS: &str = r##"
pageAiGatewayHealthError: '',
pageAiLastToolCall: null,
pageAiProfiles: [],
pageAiActiveProfileName: 'default',
pageAiActiveProfileName: 'mnoteai',
pageAiProfileError: '',
pageAiProfileMemory: { memory: '', user: '', soul: '' },
pageAiProfileMemoryDrafts: { memory: '', user: '', soul: '' },
@@ -256,6 +256,83 @@ const SIDEBAR_TREE_JS: &str = r##"
}
}
function pageAiProjectionBlocks(aggregate) {
var blocks = aggregate && aggregate.body && aggregate.body.blockDocument && aggregate.body.blockDocument.blocks;
return Array.isArray(blocks) ? blocks : [];
}
function pageAiBlockText(block) {
return searchText(block && (block.text || block.title || block.content) || '');
}
function pageAiSelectedBlockIdsFromSelection() {
try {
var selection = window.getSelection ? window.getSelection() : null;
if (!selection || selection.rangeCount === 0 || searchText(selection.toString() || '') === '') return [];
var range = selection.getRangeAt(0);
var editor = document.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"] .editor-surface .ProseMirror');
if (!(editor instanceof HTMLElement)) return [];
return Array.from(editor.children).filter(function(node) {
if (!(node instanceof HTMLElement)) return false;
try {
return range.intersectsNode(node);
} catch (_) {
return false;
}
}).map(function(node) {
return searchText(node.getAttribute('data-id') || node.id || '');
}).filter(Boolean);
} catch (_) {
return [];
}
}
function pageAiBlocksToPageXml(blocks, aggregate) {
var revision = aggregate && aggregate.body ? String(aggregate.body.revision || '') : '';
var pageId = currentDocumentId() || 'current-page';
var lines = ['<page id="' + escapeHtml(pageId) + '" revision="' + escapeHtml(revision) + '">'];
blocks.forEach(function(block) {
var blockId = String(block && (block.blockId || block.id) || '');
var type = String(block && block.type || 'paragraph');
var revisionRef = String(block && block.revisionRef || '');
var level = block && block.attrs && block.attrs.level ? ' level="' + escapeHtml(block.attrs.level) + '"' : '';
lines.push(' <block id="' + escapeHtml(blockId) + '" type="' + escapeHtml(type) + '" revisionRef="' + escapeHtml(revisionRef) + '"' + level + '>' + escapeHtml(pageAiBlockText(block)) + '</block>');
});
lines.push('</page>');
return lines.join('\n');
}
function buildPageAiContext(contextSnapshot, scope, selectedText) {
var aggregate = contextSnapshot.aggregate || {};
var body = aggregate.body || {};
var allBlocks = pageAiProjectionBlocks(aggregate);
var selectedBlockIds = scope === 'selection' ? pageAiSelectedBlockIdsFromSelection() : [];
var selectedSet = {};
selectedBlockIds.forEach(function(id) { selectedSet[id] = true; });
var selectedBlocks = selectedBlockIds.length
? allBlocks.filter(function(block) { return selectedSet[String(block.blockId || block.id || '')]; })
: [];
var contextBlocks = selectedBlocks.length ? selectedBlocks : allBlocks.slice(0, 120);
var truncated = !selectedBlocks.length && allBlocks.length > contextBlocks.length;
return {
schema: 'mnote.page_ai_context.v1',
workspaceId: resolveWorkspaceId(document.body),
documentId: currentDocumentId(),
scope: scope,
revision: body.revision || null,
conflictDetectionKey: body.conflictDetectionKey || null,
selectedText: selectedText || '',
selectedBlockIds: selectedBlockIds,
allowedTargetBlockIds: selectedBlockIds,
selectedBlocks: selectedBlocks,
contextBlocks: contextBlocks,
pageText: contextBlocks.map(pageAiBlockText).filter(Boolean).join('\n'),
pageXml: pageAiBlocksToPageXml(contextBlocks, aggregate),
truncated: truncated,
warnings: truncated ? [{ code: 'page_ai_context_truncated', message: ' AI context 120 ' }] : []
};
}
function pageAiScopedPageContext(contextSnapshot) {
var aggregate = contextSnapshot.aggregate || {};
var body = contextSnapshot.body || {};
@@ -264,6 +341,7 @@ const SIDEBAR_TREE_JS: &str = r##"
var scope = pageUiState.pageAiContextScope || 'page';
var title = aggregate.head && aggregate.head.title ? aggregate.head.title : '';
var selectedText = scope === 'selection' ? currentPageAiSelectedText() : '';
var aiContext = buildPageAiContext(contextSnapshot, scope, selectedText);
return {
pageContext: {
contextScope: scope,
@@ -277,10 +355,11 @@ const SIDEBAR_TREE_JS: &str = r##"
pageSubtreeSource: scope === 'page' ? (contextSnapshot.pageSubtreeSource || 'none') : 'scope:' + scope,
evidence: selectedText ? [{ kind: 'selection', text: selectedText }] : null,
pageOptions: scope === 'options' || scope === 'page' ? currentPageOptions() : null,
contentAccess: 'mnote.page.get'
contentAccess: 'mnote.doc.fetch',
aiContext: aiContext
},
selectedText: selectedText,
selectedBlockId: null
selectedBlockId: aiContext.selectedBlockIds[0] || null
};
}
@@ -1160,6 +1239,7 @@ const SIDEBAR_TREE_JS: &str = r##"
if (!documentId) return;
var escaped = cssEscape(documentId);
var escapedDocRowId = cssEscape('doc:' + documentId);
var isCurrentDocument = currentDocumentId() === documentId;
var pageSelectors = [
'.tree-row[data-shell-mode="page"][data-node-id="' + escaped + '"] > .tree-link > .tree-link-title',
'.wolai-page-row[data-node-id="' + escaped + '"] > .wolai-row-title',
@@ -1174,6 +1254,22 @@ const SIDEBAR_TREE_JS: &str = r##"
document.querySelectorAll('.tree-row[data-shell-mode="filetree"][data-row-id="' + escapedDocRowId + '"] > .tree-link > .tree-link-title').forEach(function(node) {
if (node instanceof HTMLElement) node.textContent = fileTreePageTitle(title);
});
document.querySelectorAll('[data-page-title-input="true"][data-document-id="' + escaped + '"]').forEach(function(node) {
if (!(node instanceof HTMLTextAreaElement)) return;
node.value = title;
node.setAttribute('data-title-last-saved', title);
node.setAttribute('data-title-save-status', 'saved');
node.style.height = 'auto';
node.style.height = Math.max(48, node.scrollHeight) + 'px';
});
document.querySelectorAll('[data-document-pane="true"][data-pane-document-id="' + escaped + '"] [data-page-title-current="true"]').forEach(function(node) {
if (node instanceof HTMLElement) node.textContent = title;
});
if (isCurrentDocument) {
document.title = title;
var topbarCurrent = document.querySelector('.wolai-breadcrumb-current [data-page-title-current]');
if (topbarCurrent instanceof HTMLElement) topbarCurrent.textContent = title;
}
}
function fileTreePageTitle(title) {
@@ -1396,7 +1492,29 @@ const SIDEBAR_TREE_JS: &str = r##"
return pageChanged || fileChanged;
}
function moveDocumentRowForMode(mode, documentId, parentId) {
function sortOrderFromDelta(data) {
var raw = data && (data.sortOrder ?? data.sort_order);
var value = Number(raw);
return Number.isFinite(value) && value >= 0 ? Math.floor(value) : null;
}
function insertTreeNodeAtSortOrder(targetContainer, node, sortOrder) {
if (!targetContainer || !node) return false;
if (sortOrder === null || sortOrder === undefined) {
targetContainer.appendChild(node);
return true;
}
var siblings = Array.from(targetContainer.querySelectorAll(':scope > .tree-node')).filter(function(candidate) {
return candidate !== node;
});
var targetIndex = Math.max(0, Math.min(Number(sortOrder), siblings.length));
var referenceNode = siblings[targetIndex] || null;
if (referenceNode) targetContainer.insertBefore(node, referenceNode);
else targetContainer.appendChild(node);
return true;
}
function moveDocumentRowForMode(mode, documentId, parentId, sortOrder) {
var row = document.querySelector(rowSelectorForDocument(mode, documentId));
var node = row ? row.closest('.tree-node') : null;
var root = treeRootForMode(mode);
@@ -1410,16 +1528,16 @@ const SIDEBAR_TREE_JS: &str = r##"
} else {
row.removeAttribute('data-parent-id');
}
targetContainer.appendChild(node);
return true;
return insertTreeNodeAtSortOrder(targetContainer, node, sortOrder);
}
function applyMoveDocumentDelta(data) {
var documentId = documentIdFromDelta(data);
if (!documentId) return false;
var parentId = parentIdFromDelta(data);
var movedPage = moveDocumentRowForMode('page', documentId, parentId);
var movedFile = moveDocumentRowForMode('filetree', documentId, parentId);
var sortOrder = sortOrderFromDelta(data);
var movedPage = moveDocumentRowForMode('page', documentId, parentId, sortOrder);
var movedFile = moveDocumentRowForMode('filetree', documentId, parentId, sortOrder);
return movedPage || movedFile;
}
@@ -3994,7 +4112,11 @@ const SIDEBAR_TREE_JS: &str = r##"
var selected = pageUiState.pageAiProfiles.find(function(profile) {
return profile && profile.active;
});
return pageAiProfileValue(selected) || 'default';
return pageAiProfileValue(selected) || 'mnoteai';
}
function pageAiMnoteToolModel() {
return String(document.documentElement.getAttribute('data-mnote-page-ai-tool-model') || 'deepseek-v4-flash').trim() || 'deepseek-v4-flash';
}
function pageAiCurrentProfileRecord() {
@@ -4006,10 +4128,12 @@ const SIDEBAR_TREE_JS: &str = r##"
function pageAiCurrentModelLabel() {
var profile = pageAiCurrentProfileRecord();
if (!profile) return ' Hermes ';
var toolModel = pageAiMnoteToolModel();
if (!profile) return 'tool: ' + toolModel;
var model = String(profile.model || '').trim();
var gateway = String(profile.gateway || '').trim();
return [model, gateway].filter(Boolean).join(' · ') || ' Hermes ';
var profileLabel = [model, gateway].filter(Boolean).join(' · ') || ' Hermes ';
return 'tool: ' + toolModel + ' · profile: ' + profileLabel;
}
function pageAiNormalizeProfiles(payload) {
@@ -4085,9 +4209,10 @@ const SIDEBAR_TREE_JS: &str = r##"
}
function pageAiSetActiveProfile(profileName) {
var next = String(profileName || '').trim() || 'default';
var next = String(profileName || '').trim() || 'mnoteai';
pageUiState.pageAiActiveProfileName = next;
document.documentElement.setAttribute('data-mnote-page-ai-profile', next);
document.documentElement.setAttribute('data-mnote-page-ai-tool-model', pageAiMnoteToolModel());
}
function pageAiSetRunStatus(status, runId) {
@@ -4186,10 +4311,20 @@ const SIDEBAR_TREE_JS: &str = r##"
var writesCurrentPage = [
'mnote.page.save',
'mnote.page.update.title',
'mnote.page.update.options'
'mnote.page.update.options',
'mnote.doc.apply.block.ops',
'mnote.block.replace',
'mnote.block.insert.after',
'mnote.block.delete',
'mnote.block.move.after'
].indexOf(normalizedTool) >= 0 || [
'mnote.page.update_title',
'mnote.page.update_options'
'mnote.page.update_options',
'mnote.doc.apply_block_ops',
'mnote.block.replace',
'mnote.block.insert_after',
'mnote.block.delete',
'mnote.block.move_after'
].indexOf(String(toolName || '').trim()) >= 0;
if (!writesCurrentPage) return;
var documentId = pageAiToolEventDeepFindString(toolEvent, ['documentId', 'document_id'], 0) || currentDocumentId();
@@ -4352,6 +4487,7 @@ const SIDEBAR_TREE_JS: &str = r##"
scope: String(tool && (tool.scope || tool.requiredScope || '') || '').trim(),
kind: String(tool && (tool.kind || tool.mode || '') || '').trim(),
status: String(tool && (tool.status || tool.permission || 'available') || '').trim(),
enabled: tool && tool.enabled !== false,
unavailableReason: String(tool && (tool.unavailableReason || tool.reason || '') || '').trim()
};
}).filter(Boolean);
@@ -4394,7 +4530,7 @@ const SIDEBAR_TREE_JS: &str = r##"
async function pageAiLoadTools() {
try {
var response = await fetch('/api/hermes/client/tools?scope=mnote', {
var response = await fetch('/api/hermes/client/tools?scope=mnote&profile=' + encodeURIComponent(pageAiCurrentProfile()), {
headers: { 'accept': 'application/json' }
});
var payload = await response.json().catch(function(){ return null; });
@@ -4483,14 +4619,17 @@ const SIDEBAR_TREE_JS: &str = r##"
var payload = await response.json().catch(function(){ return null; });
if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'profiles_failed_' + response.status));
var profiles = pageAiNormalizeProfiles(payload);
pageUiState.pageAiProfiles = profiles.length ? profiles : [{ name: 'default', active: true }];
pageUiState.pageAiProfiles = profiles.length ? profiles : [{ name: 'mnoteai', active: true }];
var current = pageAiCurrentProfile();
var hasCurrent = pageUiState.pageAiProfiles.some(function(profile) { return pageAiProfileValue(profile) === current; });
var hasMnoteAi = pageUiState.pageAiProfiles.some(function(profile) { return pageAiProfileValue(profile) === 'mnoteai'; });
var active = pageAiProfileValue(pageUiState.pageAiProfiles.find(function(profile) { return profile.active; }));
pageAiSetActiveProfile(active || pageAiCurrentProfile());
pageAiSetActiveProfile(hasCurrent && current !== 'default' ? current : (hasMnoteAi ? 'mnoteai' : (active || current)));
pageUiState.pageAiProfileError = '';
void pageAiLoadGatewayHealth();
} catch (error) {
pageUiState.pageAiProfileError = error instanceof Error ? error.message : String(error);
if (!pageUiState.pageAiProfiles.length) pageUiState.pageAiProfiles = [{ name: 'default', active: true }];
if (!pageUiState.pageAiProfiles.length) pageUiState.pageAiProfiles = [{ name: 'mnoteai', active: true }];
pageAiSetActiveProfile(pageAiCurrentProfile());
}
renderPageAiProviderButtons();
@@ -4520,6 +4659,7 @@ const SIDEBAR_TREE_JS: &str = r##"
await pageAiEnsureHermesSession(true);
await pageAiLoadProfileMemory();
await pageAiLoadSkills();
await pageAiLoadTools();
await pageAiLoadGatewayHealth();
renderPageAiControls();
} catch (error) {
@@ -4627,6 +4767,45 @@ const SIDEBAR_TREE_JS: &str = r##"
renderPageAiConversation();
}
async function pageAiToggleTool(toolName, enabled) {
var name = String(toolName || '').trim();
if (!name) return;
var previous = null;
pageAiNormalizeArray(pageUiState.pageAiTools).forEach(function(tool) {
if (tool.name === name && previous == null) previous = tool.enabled !== false;
});
try {
var response = await fetch('/api/hermes/client/tools/toggle', {
method: 'PUT',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
profile: pageAiCurrentProfile(),
name: name,
enabled: Boolean(enabled)
})
});
var payload = await response.json().catch(function(){ return null; });
if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'tool_toggle_failed_' + response.status));
pageAiNormalizeArray(pageUiState.pageAiTools).forEach(function(tool) {
if (tool.name === name) {
tool.enabled = Boolean(enabled);
tool.status = Boolean(enabled) ? 'available' : 'disabled';
tool.unavailableReason = Boolean(enabled) ? '' : ' Hermes profile mnote tool';
}
});
document.documentElement.setAttribute('data-mnote-page-ai-tool-toggled', name);
pageUiState.pageAiToolsError = '';
} catch (error) {
pageUiState.pageAiToolsError = error instanceof Error ? error.message : String(error);
if (previous != null) {
pageAiNormalizeArray(pageUiState.pageAiTools).forEach(function(tool) {
if (tool.name === name) tool.enabled = previous;
});
}
}
renderPageAiControls();
}
function renderPageAiProviderButtons() {
var drawer = ensurePageAiDrawer();
drawer.querySelectorAll('[data-page-ai-provider]').forEach(function(button) {
@@ -4785,9 +4964,14 @@ const SIDEBAR_TREE_JS: &str = r##"
toolsList.innerHTML = tools.map(function(tool) {
return '' +
'<div class="wolai-page-ai-tool-row">' +
'<div class="wolai-page-ai-tool-name">' + escapeHtml(tool.name) + '</div>' +
'<div class="wolai-page-ai-tool-meta">' + escapeHtml(tool.scope || tool.kind || 'mnote') + ' · ' + escapeHtml(tool.status || 'available') + '</div>' +
(tool.unavailableReason ? '<div class="wolai-page-ai-tool-meta">' + escapeHtml(tool.unavailableReason) + '</div>' : '') +
'<div class="wolai-page-ai-skill-copy">' +
'<div class="wolai-page-ai-tool-name">' + escapeHtml(tool.name) + '</div>' +
'<div class="wolai-page-ai-tool-meta">' + escapeHtml(tool.scope || tool.kind || 'mnote') + ' · ' + escapeHtml(tool.status || 'available') + '</div>' +
(tool.unavailableReason ? '<div class="wolai-page-ai-tool-meta">' + escapeHtml(tool.unavailableReason) + '</div>' : '') +
'</div>' +
'<button type="button" class="wolai-page-ai-skill-switch' + (tool.enabled !== false ? ' is-on' : '') + '" data-page-ai-tool-toggle="' + escapeHtml(tool.name) + '" aria-pressed="' + (tool.enabled !== false ? 'true' : 'false') + '">' +
'<span></span>' +
'</button>' +
'</div>';
}).join('');
}
@@ -5161,6 +5345,88 @@ const SIDEBAR_TREE_JS: &str = r##"
}
}
function pageAiLooksLikeBlockEdit(prompt) {
var text = searchText(prompt);
return ['', '', '', '', '', '', '', '', '', '', 'move', 'replace', 'delete', 'insert'].some(function(word) {
return text.indexOf(word) >= 0;
});
}
async function pageAiTryBlockEditWorkflow(prompt, scopedContext) {
if (!pageAiLooksLikeBlockEdit(prompt)) return false;
var runId = 'page-ai-fast-' + Date.now().toString(36);
var traceId = 'page-ai-fast-' + Date.now().toString(36);
pageAiSetRunStatus('running', runId);
renderPageAiControls();
var started = Date.now();
var response = await fetch('/api/page-ai/block-edit-workflow', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
workspaceId: resolveWorkspaceId(document.body),
documentId: currentDocumentId(),
sessionId: pageUiState.pageAiActiveSessionId,
runId: runId,
profile: pageAiCurrentProfile(),
model: pageAiMnoteToolModel(),
message: prompt,
pageContext: scopedContext.pageContext,
selectedBlockId: scopedContext.selectedBlockId,
selectedText: scopedContext.selectedText,
traceId: traceId
})
});
var payload = await response.json().catch(function() { return null; });
if (!response.ok || !payload || payload.ok !== true) {
var code = payload && payload.code ? String(payload.code) : '';
if (code === 'page_ai_workflow_not_block_edit') return false;
pageUiState.pageAiMessages.push({
role: 'tool',
toolName: 'mnote.page_ai.block_edit_workflow',
status: 'failed',
toolCallId: runId,
resultSummary: pageAiErrorMessage(payload, 'fast_path_failed_' + response.status)
});
renderPageAiConversation();
pageAiSetRunStatus('failed', runId);
renderPageAiControls();
return true;
}
pageUiState.pageAiMessages.push({
role: 'tool',
toolName: 'mnote.page_ai.block_edit_workflow',
status: 'completed',
toolCallId: runId,
resultSummary: 'operations=' + String(Array.isArray(payload.operations) ? payload.operations.length : 0) + ' · ' + String(Date.now() - started) + 'ms'
});
pageUiState.pageAiMessages.push({
role: 'assistant',
content: payload.message || ''
});
pageAiSetRunStatus('completed', runId);
try {
window.dispatchEvent(new CustomEvent('mnote:page-ai-tool-write-completed', {
detail: {
toolName: 'mnote.doc.apply_block_ops',
normalizedToolName: 'mnote.doc.apply.block.ops',
documentId: currentDocumentId(),
workspaceId: resolveWorkspaceId(document.body),
runId: runId,
traceId: traceId,
toolCallId: runId
}
}));
} catch (_) {}
var currentSession = pageAiCurrentSession();
if (currentSession) {
currentSession.messages = pageUiState.pageAiMessages.slice();
currentSession.updatedAt = Date.now();
}
renderPageAiConversation();
renderPageAiControls();
return true;
}
async function sendPageAiMessage(text) {
var allowQueue = ['running', 'tool_calling'].indexOf(pageUiState.pageAiRunStatus) >= 0;
if (pageUiState.pageAiBusy && !allowQueue) return;
@@ -5184,6 +5450,9 @@ const SIDEBAR_TREE_JS: &str = r##"
currentSession.updatedAt = Date.now();
}
renderPageAiConversation();
if (!allowQueue && await pageAiTryBlockEditWorkflow(prompt, scopedContext)) {
return;
}
var response = await fetch('/api/hermes/client/runs', {
method: 'POST',
headers: { 'content-type': 'application/json' },
@@ -5194,7 +5463,7 @@ const SIDEBAR_TREE_JS: &str = r##"
profile: pageAiCurrentProfile(),
contextScope: pageUiState.pageAiContextScope,
message: prompt,
model: 'hermes-agent',
model: pageAiMnoteToolModel(),
pageContext: scopedContext.pageContext,
selectedBlockId: scopedContext.selectedBlockId,
selectedText: scopedContext.selectedText,
@@ -5911,6 +6180,15 @@ const SIDEBAR_TREE_JS: &str = r##"
return;
}
var pageAiToolToggle = closestAction(e.target, '[data-page-ai-tool-toggle]');
if (pageAiToolToggle) {
e.preventDefault();
var toolName = pageAiToolToggle.getAttribute('data-page-ai-tool-toggle') || '';
var nextToolEnabled = pageAiToolToggle.getAttribute('aria-pressed') !== 'true';
void pageAiToggleTool(toolName, nextToolEnabled);
return;
}
var pageAiSession = closestAction(e.target, '[data-page-ai-session]');
if (pageAiSession) {
e.preventDefault();
@@ -6269,7 +6547,16 @@ const SIDEBAR_TREE_JS: &str = r##"
window.__mnoteRecordPageHistorySnapshot = recordPageHistorySnapshot;
window.__mnoteApplyPageOptionsToShell = applyPageOptionsToShell;
setTimeout(initializePageUiSurfaces, 0);
function scheduleInitializePageUiSurfaces() {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
setTimeout(initializePageUiSurfaces, 0);
}, { once: true });
return;
}
setTimeout(initializePageUiSurfaces, 0);
}
scheduleInitializePageUiSurfaces();
function readPageDragNodeId(event) {
var fromTransfer = event.dataTransfer ? event.dataTransfer.getData(PAGE_DRAG_MIME) || event.dataTransfer.getData('text/plain') : '';
@@ -6441,7 +6728,7 @@ const SIDEBAR_TREE_JS: &str = r##"
return;
}
if (action === 'move' && body.documentId) {
if (applyMoveDocumentDelta({ documentId: body.documentId, parentId: body.parentId || null })) {
if (applyMoveDocumentDelta({ documentId: body.documentId, parentId: body.parentId || null, sortOrder: body.sortOrder })) {
document.documentElement.setAttribute('data-mnote-tree-local-command-applied', 'move');
}
return;
@@ -6799,6 +7086,11 @@ mod tests {
assert!(SIDEBAR_TREE_JS.contains("data-mnote-tree-local-command-applied', 'remove"));
assert!(SIDEBAR_TREE_JS.contains("data-mnote-tree-local-command-applied', 'rename"));
assert!(SIDEBAR_TREE_JS.contains("data-mnote-tree-local-command-applied', 'move"));
assert!(SIDEBAR_TREE_JS.contains("function sortOrderFromDelta(data)"));
assert!(SIDEBAR_TREE_JS.contains("data.sortOrder ?? data.sort_order"));
assert!(SIDEBAR_TREE_JS.contains("function insertTreeNodeAtSortOrder"));
assert!(SIDEBAR_TREE_JS
.contains("applyMoveDocumentDelta({ documentId: body.documentId, parentId: body.parentId || null, sortOrder: body.sortOrder })"));
assert!(SIDEBAR_TREE_JS.contains("wolai:assets-changed"));
assert!(SIDEBAR_TREE_JS.contains("applyAssetsChangedToFileTree"));
assert!(SIDEBAR_TREE_JS.contains("installMindmapAssetFetchObserver"));
@@ -6880,6 +7172,12 @@ mod tests {
assert!(SIDEBAR_TREE_JS.contains(
r#".tree-row[data-shell-mode="filetree"][data-row-id="' + escapedDocRowId + '"] > .tree-link > .tree-link-title"#
));
assert!(SIDEBAR_TREE_JS.contains(
r#"[data-page-title-input="true"][data-document-id="' + escaped + '"]"#
));
assert!(SIDEBAR_TREE_JS.contains(
r#".wolai-breadcrumb-current [data-page-title-current]"#
));
assert!(!SIDEBAR_TREE_JS.contains(
r#".tree-row[data-document-id="' + escaped + '"] > .tree-link > .tree-link-title"#
));
+5 -1
View File
@@ -3154,11 +3154,15 @@ body {
.wolai-page-ai-tool-list {
display: grid;
max-height: 96px;
max-height: 140px;
gap: 6px;
overflow: auto;
}
[data-page-ai-tool-list].wolai-page-ai-tool-list {
max-height: min(420px, calc(100vh - 330px));
}
.wolai-page-ai-tool-row {
display: grid;
gap: 2px;