20260513 mindmap优化01

This commit is contained in:
lix-2026
2026-05-13 22:43:16 +08:00
parent 17c003976b
commit b4a452a8b7
89 changed files with 11557 additions and 707 deletions
+276 -5
View File
@@ -962,6 +962,25 @@ const SIDEBAR_TREE_JS: &str = r##"
return '';
}
function fileObjectIdentity(item) {
var meta = item && item.resourceMeta && typeof item.resourceMeta === 'object' ? item.resourceMeta : {};
if (meta.objectIdentity && typeof meta.objectIdentity === 'object') return meta.objectIdentity;
var rowKind = String(item && item.rowKind || '');
var documentId = fileDocumentId(item) || null;
var assetId = fileAssetId(item) || null;
var iconKind = iconKindOf(item);
var objectKind = rowKind === 'document' ? 'page' : rowKind === 'index' ? 'index' : iconKind === 'mindmap' ? 'mindmap' : 'attachment';
return { objectKind: objectKind, documentId: documentId, blockId: null, assetId: assetId };
}
function objectIdentityAttr(identity) {
try {
return JSON.stringify(identity || {});
} catch (_error) {
return '';
}
}
function iconKindOf(item) {
return String(item && (item.iconHint || item.rowKind || 'file') || 'file').trim() || 'file';
}
@@ -976,6 +995,7 @@ const SIDEBAR_TREE_JS: &str = r##"
var parent = parentIdOf(item);
var documentId = fileDocumentId(item);
var assetId = fileAssetId(item);
var objectIdentity = fileObjectIdentity(item);
var children = grouped.get(nodeId) || [];
var expandable = Boolean(item.expandable || item.childCount > 0 || children.length);
var expanded = expandable && item.expandedByDefault !== false;
@@ -990,7 +1010,7 @@ const SIDEBAR_TREE_JS: &str = r##"
var childHtml = expandable
? '<ul class="tree-children' + (expanded ? '' : ' tree-children--collapsed') + '">' + renderFileRows(nodeId, grouped, activeId) + '</ul>'
: '';
return '<li class="tree-node" data-node-id="' + escapeHtml(nodeId) + '"><div class="tree-row" role="treeitem" aria-level="' + (depth + 1) + '" aria-expanded="' + String(expandable && expanded) + '" data-rust-rendered-row="filetree" data-testid="' + testId + '" data-row-id="' + escapeHtml(rowId) + '" data-row-kind="' + escapeHtml(rowKind) + '" data-node-id="' + escapeHtml(nodeId) + '"' + (parent ? ' data-parent-id="' + escapeHtml(parent) + '"' : '') + ' data-document-id="' + escapeHtml(documentId) + '" data-doc-id="' + escapeHtml(documentId) + '" data-asset-id="' + escapeHtml(assetId) + '" data-shell-mode="filetree" data-selected="' + String(selected) + '" data-active="false" draggable="true">' + toggle + '<span class="tree-kind-badge" data-kind="' + escapeHtml(iconKindOf(item)) + '" aria-hidden="true"></span><button type="button" class="tree-link" data-rust-action="open" data-row-id="' + escapeHtml(rowId) + '" data-document-id="' + escapeHtml(documentId) + '" data-asset-id="' + escapeHtml(assetId) + '"><span class="tree-link-title">' + escapeHtml(title) + '</span></button><div class="tree-actions">' + createAction + '<button type="button" class="tree-action" data-testid="filetree-action-menu" data-rust-action="menu" data-row-id="' + escapeHtml(rowId) + '" aria-label="更多操作">…</button></div></div>' + childHtml + '</li>';
return '<li class="tree-node" data-node-id="' + escapeHtml(nodeId) + '"><div class="tree-row" role="treeitem" aria-level="' + (depth + 1) + '" aria-expanded="' + String(expandable && expanded) + '" data-rust-rendered-row="filetree" data-testid="' + testId + '" data-row-id="' + escapeHtml(rowId) + '" data-row-kind="' + escapeHtml(rowKind) + '" data-node-id="' + escapeHtml(nodeId) + '"' + (parent ? ' data-parent-id="' + escapeHtml(parent) + '"' : '') + ' data-document-id="' + escapeHtml(documentId) + '" data-doc-id="' + escapeHtml(documentId) + '" data-asset-id="' + escapeHtml(assetId) + '" data-object-identity="' + escapeHtml(objectIdentityAttr(objectIdentity)) + '" data-object-kind="' + escapeHtml(String(objectIdentity.objectKind || '')) + '" data-shell-mode="filetree" data-selected="' + String(selected) + '" data-active="false" draggable="true">' + toggle + '<span class="tree-kind-badge" data-kind="' + escapeHtml(iconKindOf(item)) + '" aria-hidden="true"></span><button type="button" class="tree-link" data-rust-action="open" data-row-id="' + escapeHtml(rowId) + '" data-document-id="' + escapeHtml(documentId) + '" data-asset-id="' + escapeHtml(assetId) + '"><span class="tree-link-title">' + escapeHtml(title) + '</span></button><div class="tree-actions">' + createAction + '<button type="button" class="tree-action" data-testid="filetree-action-menu" data-rust-action="menu" data-row-id="' + escapeHtml(rowId) + '" aria-label="更多操作">…</button></div></div>' + childHtml + '</li>';
}).join('');
}
@@ -1066,11 +1086,10 @@ const SIDEBAR_TREE_JS: &str = r##"
if (['doc', 'docx', 'odt', 'rtf'].indexOf(ext) >= 0) return ext;
if (['ppt', 'pptx', 'odp'].indexOf(ext) >= 0) return ext;
if (['xls', 'xlsx', 'ods', 'csv'].indexOf(ext) >= 0) return ext;
if (ext === 'pdf') return ext;
if (isNonOfficeAttachmentName(name, ext)) return '';
if (mt.indexOf('wordprocessingml') >= 0) return 'docx';
if (mt.indexOf('presentationml') >= 0) return 'pptx';
if (mt.indexOf('spreadsheetml') >= 0) return 'xlsx';
if (mt.indexOf('pdf') >= 0) return 'pdf';
return '';
}
@@ -1098,6 +1117,32 @@ const SIDEBAR_TREE_JS: &str = r##"
return '/onlyoffice?' + params.toString();
}
function buildMindmapOpenPath(documentId, assetId) {
var doc = String(documentId || '').trim();
var map = String(assetId || '').trim();
if (!doc || !map) return '';
return '/mindmap/' + encodeURIComponent(doc) + '/' + encodeURIComponent(map);
}
function isMindmapAssetDetail(detail) {
var assetId = String(detail && detail.assetId || '').trim();
var assetType = String(detail && detail.assetType || '').trim();
if (assetType === 'mindmap') return true;
return assetId.indexOf('mindmap_') === 0 || assetId.indexOf('mindmap-') === 0;
}
function readFileTreeObjectIdentity(row) {
if (!row) return null;
var raw = row.getAttribute('data-object-identity') || '';
if (!raw) return null;
try {
var parsed = JSON.parse(raw);
return parsed && typeof parsed === 'object' ? parsed : null;
} catch (_error) {
return null;
}
}
async function fetchCurrentOnlyOfficeUserId() {
try {
var response = await fetch('/api/auth/whoami', {
@@ -1115,6 +1160,16 @@ const SIDEBAR_TREE_JS: &str = r##"
async function openConvexAssetFromFileTree(detail) {
var assetId = String(detail && detail.assetId || '').trim();
if (!assetId) return;
var documentId = String(detail && detail.documentId || '').trim();
if (isMindmapAssetDetail(detail) && documentId) {
var mindmapPath = buildMindmapOpenPath(documentId, assetId);
if (mindmapPath) {
document.documentElement.setAttribute('data-mnote-last-mindmap-asset-open-mode', 'mindmap-object-shell');
document.documentElement.setAttribute('data-mnote-last-mindmap-asset-id', assetId);
window.location.assign(mindmapPath);
}
return;
}
try {
var response = await fetch('/api/media/sign?assetId=' + encodeURIComponent(assetId), {
method: 'GET',
@@ -1142,6 +1197,17 @@ const SIDEBAR_TREE_JS: &str = r##"
}), '_blank', 'noopener,noreferrer');
return;
}
if (isCodeAttachmentFileName(fileName) && String(asset.document_id || detail.documentId || '').trim() === currentDocumentId()) {
await openCodeEditorAttachment({
href: fileUrl,
fileUrl: fileUrl,
fileName: fileName,
assetId: assetId,
documentId: String(asset.document_id || detail.documentId || '').trim(),
fileSize: uploadedFileSize(asset)
});
return;
}
window.open(fileUrl, '_blank', 'noopener,noreferrer');
} catch (error) {
window.alert(error && error.message ? error.message : '打开附件失败');
@@ -1243,6 +1309,67 @@ const SIDEBAR_TREE_JS: &str = r##"
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() : '';
@@ -1250,6 +1377,9 @@ const SIDEBAR_TREE_JS: &str = r##"
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';
}
@@ -3050,9 +3180,99 @@ const SIDEBAR_TREE_JS: &str = r##"
function openEditorAttachmentDetail(detail) {
if (!detail || !detail.href) return;
if (isPdfAttachmentFileName(detail.fileName)) {
void openPdfEditorAttachment(detail);
return;
}
if (isCodeAttachmentFileName(detail.fileName)) {
void openCodeEditorAttachment(detail);
return;
}
window.open(detail.href, '_blank', 'noopener,noreferrer');
}
async function resolveEditorAttachmentUrl(detail) {
var assetId = String(detail && detail.assetId || '').trim();
if (assetId) {
var response = await fetch('/api/media/sign?assetId=' + encodeURIComponent(assetId), {
method: 'GET',
credentials: 'include',
cache: 'no-store'
});
var payload = await response.json().catch(function() { return null; });
if (!response.ok || !payload) {
throw new Error(payload && payload.error ? payload.error : '生成签名链接失败');
}
var signedUrl = String(payload && payload.signedUrl || '').trim();
if (!signedUrl) throw new Error('附件链接不可用');
return {
url: signedUrl,
asset: payload.asset && typeof payload.asset === 'object' ? payload.asset : {}
};
}
var url = String(detail && (detail.fileUrl || detail.href) || '').trim();
if (!url) throw new Error('附件链接不可用');
return { url: url, asset: {} };
}
async function openPdfEditorAttachment(detail) {
try {
var resolved = await resolveEditorAttachmentUrl(detail);
window.open(resolved.url, '_blank', 'noopener,noreferrer');
} catch (error) {
window.alert(error && error.message ? error.message : '打开 PDF 失败');
}
}
async function openCodeEditorAttachment(detail) {
var resolved = null;
try {
resolved = await resolveEditorAttachmentUrl(detail);
var size = Number(resolved.asset && (resolved.asset.file_size || resolved.asset.fileSize) || 0);
if (Number.isFinite(size) && size > 1024 * 1024) {
window.open(resolved.url, '_blank', 'noopener,noreferrer');
return;
}
var response = await fetch(resolved.url, {
method: 'GET',
credentials: 'include',
cache: 'no-store'
});
if (!response.ok) throw new Error('读取附件内容失败');
var text = await response.text();
if (text.length > 1024 * 1024) {
window.open(resolved.url, '_blank', 'noopener,noreferrer');
return;
}
var editorRoot = document.querySelector('.editor-surface .ProseMirror');
var editor = editorRoot && editorRoot.editor;
if (!editor || !editor.chain) {
window.open(resolved.url, '_blank', 'noopener,noreferrer');
return;
}
var title = String(detail.fileName || resolved.asset.file_name || '附件').trim() || '附件';
var language = inferCodeAttachmentLanguage(title);
editor.chain().focus().insertContent([
{
type: 'paragraph',
content: [{ type: 'text', text: title }]
},
{
type: 'codeBlock',
attrs: { language: language },
content: text ? [{ type: 'text', text: text.replace(/\r\n?/g, '\n') }] : []
}
]).run();
} catch (error) {
console.warn('[mnote attachment] open code attachment failed', error);
if (resolved && resolved.url) {
window.open(resolved.url, '_blank', 'noopener,noreferrer');
return;
}
window.alert(error && error.message ? error.message : '打开代码附件失败');
}
}
async function openEditorAttachmentDownload(detail) {
if (!detail) return;
if (detail.assetId) {
@@ -3330,6 +3550,11 @@ const SIDEBAR_TREE_JS: &str = r##"
var rowKind = fileRow.getAttribute('data-row-kind') || '';
var documentId = fileRow.getAttribute('data-document-id') || fileRow.getAttribute('data-doc-id') || '';
var assetId = fileRow.getAttribute('data-asset-id') || '';
var assetType = '';
var kindBadge = fileRow.querySelector('.tree-kind-badge');
if (kindBadge instanceof HTMLElement) {
assetType = kindBadge.getAttribute('data-kind') || '';
}
if (fileAction === 'toggle') {
e.preventDefault();
toggleChildren(fileRow, fileBtn);
@@ -3356,14 +3581,15 @@ const SIDEBAR_TREE_JS: &str = r##"
}
e.preventDefault();
selectSidebarFileTreeRow(fileRow, { ctrlKey: e.ctrlKey, metaKey: e.metaKey, shiftKey: e.shiftKey });
dispatchSidebarEvent('tree.filetree.open', { rowId: rowId, rowKind: rowKind, documentId: documentId || null, assetId: assetId || null });
var objectIdentity = readFileTreeObjectIdentity(fileRow);
dispatchSidebarEvent('tree.filetree.open', { rowId: rowId, rowKind: rowKind, documentId: documentId || null, assetId: assetId || null, assetType: assetType || null, objectIdentity: objectIdentity, workspaceId: resolveWorkspaceId(fileRow) });
if (e.shiftKey || e.ctrlKey || e.metaKey) {
return;
}
if ((rowKind === 'document' || rowKind === 'index' || rowKind === 'markdown') && documentId) {
navigateToDocument(documentId, resolveWorkspaceId(fileRow), { treeView: 'filetree' });
} else if (assetId) {
dispatchSidebarEvent('tree.asset.open', { rowId: rowId, rowKind: rowKind, documentId: documentId || null, assetId: assetId });
dispatchSidebarEvent('tree.asset.open', { rowId: rowId, rowKind: rowKind, documentId: documentId || null, assetId: assetId, assetType: assetType || null, objectIdentity: objectIdentity, workspaceId: resolveWorkspaceId(fileRow) });
}
return;
}
@@ -3988,6 +4214,17 @@ mod tests {
assert!(SIDEBAR_TREE_JS.contains("tree.filetree.open"));
assert!(SIDEBAR_TREE_JS.contains("tree.asset.open"));
assert!(SIDEBAR_TREE_JS.contains("openConvexAssetFromFileTree"));
assert!(SIDEBAR_TREE_JS.contains("buildMindmapOpenPath"));
assert!(SIDEBAR_TREE_JS.contains("isMindmapAssetDetail"));
assert!(!SIDEBAR_TREE_JS.contains("openMindmapAssetInDocumentShell"));
assert!(SIDEBAR_TREE_JS.contains("data-mnote-last-mindmap-asset-open-mode"));
assert!(SIDEBAR_TREE_JS.contains("mindmap-object-shell"));
assert!(SIDEBAR_TREE_JS.contains("window.location.assign(mindmapPath)"));
assert!(SIDEBAR_TREE_JS.contains("assetType: assetType || null"));
assert!(SIDEBAR_TREE_JS.contains("data-object-identity"));
assert!(SIDEBAR_TREE_JS.contains("readFileTreeObjectIdentity"));
assert!(SIDEBAR_TREE_JS.contains("objectIdentity: objectIdentity"));
assert!(SIDEBAR_TREE_JS.contains("workspaceId: resolveWorkspaceId(fileRow)"));
assert!(SIDEBAR_TREE_JS.contains("/api/media/sign?assetId="));
assert!(SIDEBAR_TREE_JS.contains("fetchCurrentOnlyOfficeUserId"));
assert!(SIDEBAR_TREE_JS.contains("/api/auth/whoami"));
@@ -4031,6 +4268,40 @@ mod tests {
assert!(SIDEBAR_TREE_JS.contains("/api/tree/projections/file"));
}
#[test]
fn sidebar_tree_runtime_keeps_pdf_and_code_assets_out_of_onlyoffice() {
assert!(SIDEBAR_TREE_JS.contains("function inferOnlyOfficeFileType"));
assert!(SIDEBAR_TREE_JS.contains("isNonOfficeAttachmentName(name, ext)"));
assert!(!SIDEBAR_TREE_JS.contains("if (ext === 'pdf') return ext;"));
assert!(!SIDEBAR_TREE_JS.contains("mt.indexOf('pdf') >= 0"));
assert!(SIDEBAR_TREE_JS.contains("mnote-uploaded-attachment-pdf"));
assert!(SIDEBAR_TREE_JS.contains("mnote-uploaded-attachment-code"));
assert!(SIDEBAR_TREE_JS.contains("'toml'"));
assert!(SIDEBAR_TREE_JS.contains("'json'"));
assert!(SIDEBAR_TREE_JS.contains("'yaml'"));
assert!(SIDEBAR_TREE_JS.contains("'md'"));
assert!(SIDEBAR_TREE_JS.contains("'vue'"));
assert!(SIDEBAR_TREE_JS.contains("'svelte'"));
assert!(SIDEBAR_TREE_JS.contains("'proto'"));
assert!(SIDEBAR_TREE_JS.contains("'dockerfile'"));
assert!(SIDEBAR_TREE_JS.contains("'.gitignore'"));
}
#[test]
fn sidebar_tree_runtime_opens_pdf_and_code_assets_with_builtin_tools() {
assert!(SIDEBAR_TREE_JS.contains("function openPdfEditorAttachment"));
assert!(SIDEBAR_TREE_JS.contains("function openCodeEditorAttachment"));
assert!(SIDEBAR_TREE_JS.contains("function resolveEditorAttachmentUrl"));
assert!(SIDEBAR_TREE_JS.contains("type: 'codeBlock'"));
assert!(SIDEBAR_TREE_JS.contains("attrs: { language: language }"));
assert!(SIDEBAR_TREE_JS.contains("inferCodeAttachmentLanguage"));
assert!(SIDEBAR_TREE_JS.contains("if (isPdfAttachmentFileName(detail.fileName))"));
assert!(SIDEBAR_TREE_JS.contains("if (isCodeAttachmentFileName(detail.fileName))"));
assert!(SIDEBAR_TREE_JS
.contains("if (isCodeAttachmentFileName(fileName) && String(asset.document_id"));
assert!(SIDEBAR_TREE_JS.contains("await openCodeEditorAttachment({"));
}
#[test]
fn tree_live_controller_marks_transport_and_closes_source_on_pagehide() {
assert!(TREE_LIVE_CONTROLLER_JS.contains("convex-command-log-sse"));
+22 -2
View File
@@ -18,17 +18,37 @@ pub fn MindmapPage(
document_id: String,
/// 思维导图 ID
mindmap_id: String,
/// 页面标题
title: String,
/// 侧栏页面树 HTML
#[prop(optional)]
sidebar_tree_html: Option<String>,
/// 工作区名称
#[prop(optional)]
workspace_name: Option<String>,
/// workspace shell 侧栏 sections HTML
#[prop(optional)]
workspace_sidebar_html: Option<String>,
) -> impl IntoView {
let object_identity = format!("resource:mindmap:{document_id}:{mindmap_id}");
view! {
<PageLayout current_nav="documents">
<PageLayout
current_nav="documents"
sidebar_tree_html={sidebar_tree_html.unwrap_or_default()}
workspace_name={workspace_name.unwrap_or_default()}
workspace_sidebar_html={workspace_sidebar_html.unwrap_or_default()}
topbar_title={title.clone()}
>
<main
id="mnote-mindmap-shell"
data-object-shell="mindmap"
data-mnote-object-editor="mindmap"
data-mnote-object-identity={object_identity}
data-document-id={document_id}
data-mindmap-id={mindmap_id}
>
<header>
<h1>{"思维导图"}</h1>
<h1>{title}</h1>
</header>
<section
id="mnote-mindmap-island"