---
name: 修改脑图样式
description: 弹出脑图样式与排版配置面板,支持修改当前选中脑图或全局默认配置。
author: ymjr
version: 1.1.1
license: MIT
usage: 选中脑图的根节点运行,修改当前脑图的样式;不选任何元素运行,则修改全局新建脑图的默认样式配置。
features:
- 提供可视化的脑图主题与排版编辑器UI
- 支持修改展开方向、连线样式、节点间距和连线长度
- 提供多套预设主题并支持自定义颜色与层级样式
dependencies:
- 依赖 Feature-Mindmap_Engine 提供后台渲染与自动排版支持
autorun: false
---
/*
```javascript
*/
var locales = {
zh: {
theme_rainbow: "🌈 彩虹分枝",
theme_morandi: "🎨 莫兰迪色",
theme_business: "💼 商务蓝调",
theme_custom: "⚙️ 自定义色",
ui_title_global: "🧠 脑图全局默认设置",
ui_title_current: "🧠 修改当前脑图样式",
ui_group_layout: "排版与连线",
ui_dir_lr: "向右展开 (LR)",
ui_dir_rl: "向左展开 (RL)",
ui_dir_td: "向下展开 (TD)",
ui_dir_bu: "向上展开 (BU)",
ui_direction: "展开方向",
ui_arrow_normal: "平滑曲线 (Normal)",
ui_arrow_elbow: "折线 (Elbow)",
ui_arrow_curve: "S型曲线 (Curve)",
ui_arrow_style: "连线样式",
ui_fixed_center: "固定在中点",
ui_auto_slide: "自动滑动",
ui_fixed_point: "箭头固定中点",
ui_node_gap: "节点间距",
ui_line_length: "连线长度",
ui_group_theme: "主题配置",
ui_new_theme_name: "新建主题",
ui_prompt_theme_name: "请输入新主题名称:",
ui_rename_title: "重命名",
ui_delete_title: "删除",
notice_at_least_one: "至少保留一个主题!",
confirm_delete_theme: "确定删除主题 \"{name}\" 吗?",
ui_root_bg: "根节点背景色",
ui_branch_colors: "分支循环配色",
ui_level_styles: "层级样式 (文字/边框/粗细/字号)",
ui_text_color: "文字颜色",
ui_stroke_color: "边框颜色",
ui_stroke_width: "边框粗细",
ui_font_size: "字号",
ui_btn_save_global: "💾 保存为全局配置",
setting_desc: "⚠️ 请不要在此直接修改 JSON 代码。请运行 'Change Mindmap Style' 样式脚本,通过可视化 UI 面板修改脑图全局默认设置。",
notice_saved_global: "✅ 已保存脑图全局配置",
ui_btn_apply: "✨ 应用并重排",
notice_applied: "🎨 样式已应用,自动排版完成!",
notice_no_engine: "⚠️ 设置已更新,但未检测到后台引擎,请开启 Engine 后调整排版。",
notice_global_mode: "💡 当前未选中脑图节点,进入【全局设置模式】。您在这里所做的修改将作为以后新建脑图的默认值。"
},
en: {
theme_rainbow: "🌈 Rainbow",
theme_morandi: "🎨 Morandi",
theme_business: "💼 Business Blue",
theme_custom: "⚙️ Custom",
ui_title_global: "🧠 Mindmap Global Settings",
ui_title_current: "🧠 Change Mindmap Style",
ui_group_layout: "Layout & Connectors",
ui_dir_lr: "Expand Right (LR)",
ui_dir_rl: "Expand Left (RL)",
ui_dir_td: "Expand Down (TD)",
ui_dir_bu: "Expand Up (BU)",
ui_direction: "Direction",
ui_arrow_normal: "Normal Curve",
ui_arrow_elbow: "Elbow Connector",
ui_arrow_curve: "S-Curve",
ui_arrow_style: "Connector Style",
ui_fixed_center: "Fixed at Center",
ui_auto_slide: "Auto Sliding",
ui_fixed_point: "Fixed Point Arrow",
ui_node_gap: "Node Gap",
ui_line_length: "Line Length",
ui_group_theme: "Theme Configuration",
ui_new_theme_name: "New Theme",
ui_prompt_theme_name: "Enter new theme name:",
ui_rename_title: "Rename",
ui_delete_title: "Delete",
notice_at_least_one: "At least one theme must be kept!",
confirm_delete_theme: "Are you sure to delete theme \"{name}\"?",
ui_root_bg: "Root Background Color",
ui_branch_colors: "Branch Color Cycle",
ui_level_styles: "Level Styles (Text/Stroke/Width/Size)",
ui_text_color: "Text Color",
ui_stroke_color: "Stroke Color",
ui_stroke_width: "Stroke Width",
ui_font_size: "Font Size",
ui_btn_save_global: "💾 Save Global Config",
setting_desc: "⚠️ Do not modify JSON directly. Use 'Change Mindmap Style' script UI to update global defaults.",
notice_saved_global: "✅ Global configuration saved",
ui_btn_apply: "✨ Apply and Re-layout",
notice_applied: "🎨 Style applied and layout refreshed!",
notice_no_engine: "⚠️ Settings updated, but engine not detected. Enable Engine to re-layout.",
notice_global_mode: "💡 No mindmap node selected. Entering 【Global Settings Mode】. Changes here will be defaults for new mindmaps."
}
};
locales["zh-cn"] = locales.zh;
locales["zh-tw"] = locales.zh;
const { Notice } = ea.obsidian;
const DEFAULT_LEVELS = [
{ level: 1, strokeColor: "black", textColor: "#ffffff", fontSize: 24, strokeWidth: 2 },
{ level: 2, strokeColor: "black", textColor: "black", fontSize: 20, strokeWidth: 1 },
{ level: 3, strokeColor: "black", textColor: "black", fontSize: 16, strokeWidth: 1 },
{ level: 4, strokeColor: "black", textColor: "black", fontSize: 14, strokeWidth: 1 }
];
const FALLBACK_THEMES = {
"rainbow": { name: t("theme_rainbow"), rootBg: "#343a40", colors: ["#e03131", "#1971c2", "#099268", "#e8590c", "#9c36b5", "#fcc419", "#3bc9db"], levels: JSON.parse(JSON.stringify(DEFAULT_LEVELS)) },
"morandi": { name: t("theme_morandi"), rootBg: "#495057", colors: ["#5c7a82", "#b38269", "#788560", "#a68894", "#7a6c5d"], levels: JSON.parse(JSON.stringify(DEFAULT_LEVELS)) },
"business": { name: t("theme_business"), rootBg: "#0b2545", colors: ["#1864ab", "#0b7285", "#087f5b", "#5f3dc4", "#c92a2a"], levels: JSON.parse(JSON.stringify(DEFAULT_LEVELS)) },
"custom": { name: t("theme_custom"), rootBg: "#1e1e1e", colors: ["#e03131", "#1971c2", "#099268", "#e8590c", "#9c36b5", "#fcc419"], levels: JSON.parse(JSON.stringify(DEFAULT_LEVELS)) }
};
const FALLBACK_SETTINGS = {
direction: "LR", arrowType: "normal", defaultGap: 15, curveLength: 40, lengthBetweenElAndLine: 100, enableFixedPoint: true, theme: "rainbow"
};
let engineSettings = ExcalidrawAutomate.plugin.settings.scriptEngineSettings["Mindmap_Engine"] || {};
let mindmapConfig;
try {
if (engineSettings["Mindmap Configuration"] && engineSettings["Mindmap Configuration"].value) {
mindmapConfig = JSON.parse(engineSettings["Mindmap Configuration"].value);
} else { mindmapConfig = { themes: FALLBACK_THEMES, defaultSettings: FALLBACK_SETTINGS }; }
} catch (e) { mindmapConfig = { themes: FALLBACK_THEMES, defaultSettings: FALLBACK_SETTINGS }; }
let localConfig = JSON.parse(JSON.stringify(mindmapConfig));
if (!localConfig.themes) localConfig.themes = JSON.parse(JSON.stringify(FALLBACK_THEMES));
if (!localConfig.defaultSettings) localConfig.defaultSettings = JSON.parse(JSON.stringify(FALLBACK_SETTINGS));
function lightenHex(hex, percent) {
if (!hex || hex === "transparent") return hex;
let num = parseInt(hex.replace("#",""), 16); if (isNaN(num)) return hex;
let amt = Math.round(2.55 * percent);
let R = (num >> 16) + amt; let G = (num >> 8 & 0x00FF) + amt; let B = (num & 0x0000FF) + amt;
return "#" + (0x1000000 + (R<255?R<1?0:R:255)*0x10000 + (G<255?G<1?0:G:255)*0x100 + (B<255?B<1?0:B:255)).toString(16).slice(1);
}
function resolveStyle(level, branchIndex, settings, themesObj) {
const themeKey = settings?.theme || "rainbow";
let theme = themesObj[themeKey] || themesObj["rainbow"];
const levels = theme.levels || DEFAULT_LEVELS;
const lvlConfig = level <= levels.length ? levels[level - 1] : levels[levels.length - 1];
let bg = "transparent";
if (level === 1) { bg = theme.rootBg; }
else {
const colors = theme.colors && theme.colors.length > 0 ? theme.colors : ["#cccccc"];
const baseColor = colors[branchIndex % colors.length];
bg = level > 2 ? lightenHex(baseColor, Math.min((level - 2) * 15, 60)) : baseColor;
}
return { backgroundColor: bg, textColor: lvlConfig.textColor, strokeColor: lvlConfig.strokeColor, fontSize: lvlConfig.fontSize, strokeWidth: lvlConfig.strokeWidth };
}
const selectedEls = ea.getViewSelectedElements();
let targetRoot = null;
if (selectedEls.length > 0 && selectedEls[0]?.customData?.mindmap) {
const elements = ea.getViewElements();
const rootId = selectedEls[0].customData.mindmap.root;
targetRoot = elements.find(e => e.id === rootId);
}
const isGlobalMode = !targetRoot;
let activeThemeKey = isGlobalMode ? localConfig.defaultSettings.theme : (targetRoot.customData.mindmap.settings?.theme || "rainbow");
let activeLayoutSettings = isGlobalMode ? localConfig.defaultSettings : JSON.parse(JSON.stringify(targetRoot.customData.mindmap.settings || localConfig.defaultSettings));
if (typeof activeLayoutSettings.enableFixedPoint === 'undefined') {
activeLayoutSettings.enableFixedPoint = true;
}
function createInput(type, value, onChange, width="auto", title="") {
const inp = document.createElement('input'); inp.type = type; inp.value = value; inp.title = title;
if (type === "color") {
inp.style.cssText = `width:24px; height:24px; padding:0; border:1px solid var(--background-modifier-border); border-radius:4px; cursor:pointer; flex-shrink:0;`;
} else if (type === "number") {
inp.style.cssText = `width:${width}; text-align:center; padding:2px; background:var(--background-modifier-form-field); border:1px solid var(--background-modifier-border); border-radius:4px; font-size:12px;`;
} else {
inp.style.cssText = `width:${width}; padding:4px 8px; background:var(--background-modifier-form-field); border:1px solid var(--background-modifier-border); border-radius:4px; font-size:12px; color:var(--text-normal);`;
}
inp.onchange = e => onChange(type === "number" ? Number(e.target.value) : e.target.value);
return inp;
}
function createIconButton(text, onClick, title="") {
const btn = document.createElement('button'); btn.innerHTML = text; btn.title = title;
btn.style.cssText = `background:var(--interactive-normal); border:1px solid var(--background-modifier-border); color:var(--text-normal); border-radius:4px; cursor:pointer; padding:2px 8px; font-size:12px; display:flex; align-items:center; justify-content:center;`;
btn.onmouseover = () => btn.style.background = "var(--interactive-hover)";
btn.onmouseout = () => btn.style.background = "var(--interactive-normal)";
btn.onclick = onClick; return btn;
}
function createStylePanel() {
if (document.getElementById("ea-mindmap-style-panel")) return;
const panel = document.createElement('div');
panel.id = "ea-mindmap-style-panel";
const isSmallScreen = window.innerWidth <= 600 || (ea?.obsidian?.Platform?.isMobile ?? false);
panel.style.cssText = isSmallScreen
? `position:fixed; top:50%; left:50%; transform:translate(-50%, -50%); width:min(380px, 94vw); max-height:85vh; background:var(--background-primary); border:1px solid var(--background-modifier-border); box-shadow:0 10px 30px rgba(0,0,0,0.3); border-radius:8px; z-index:9999; display:flex; flex-direction:column; overflow:hidden;`
: `position:fixed; top:80px; right:40px; width:380px; max-height:85vh; background:var(--background-primary); border:1px solid var(--background-modifier-border); box-shadow:0 10px 30px rgba(0,0,0,0.3); border-radius:8px; z-index:9999; display:flex; flex-direction:column; overflow:hidden;`;
const header = document.createElement('div');
header.style.cssText = `padding:12px 16px; background:var(--background-secondary); cursor:move; border-bottom:1px solid var(--background-modifier-border); display:flex; justify-content:space-between; align-items:center; user-select:none;`;
header.innerHTML = `${isGlobalMode ? t("ui_title_global") : t("ui_title_current")}`;
panel.appendChild(header);
const content = document.createElement('div');
content.style.cssText = `padding:16px; display:flex; flex-direction:column; gap:20px; overflow-y:auto;`;
const createRow = (label, element) => {
const div = document.createElement('div'); div.style.cssText = "display:flex; justify-content:space-between; align-items:center; margin-bottom:10px;";
const lbl = document.createElement('label'); lbl.innerText = label; lbl.style.cssText = "font-size:13px; color:var(--text-normal);";
div.appendChild(lbl); div.appendChild(element); return div;
};
const layoutContainer = document.createElement('div');
const themeSelectorContainer = document.createElement('div');
const themeEditorContainer = document.createElement('div');
content.appendChild(layoutContainer);
content.appendChild(themeSelectorContainer);
if (isGlobalMode) content.appendChild(themeEditorContainer);
const renderLayout = () => {
layoutContainer.innerHTML = `
${t("ui_group_layout")}
`;
const dirSelect = document.createElement('select'); dirSelect.style.cssText = "width:120px; background:var(--background-modifier-form-field); border-radius:4px; padding:4px; color:var(--text-normal); border:1px solid var(--background-modifier-border);";
const dirs = { "LR": t("ui_dir_lr"), "RL": t("ui_dir_rl"), "TD": t("ui_dir_td"), "BU": t("ui_dir_bu") };
Object.entries(dirs).forEach(([val, text]) => {
const o = document.createElement('option'); o.value = val; o.innerText = text;
if(activeLayoutSettings.direction === val) o.selected = true; dirSelect.appendChild(o);
});
dirSelect.onchange = e => activeLayoutSettings.direction = e.target.value;
layoutContainer.appendChild(createRow(t("ui_direction"), dirSelect));
const arrowSelect = document.createElement('select'); arrowSelect.style.cssText = dirSelect.style.cssText;
const arrowOptions = { "normal": t("ui_arrow_normal"), "elbow": t("ui_arrow_elbow"), "curve": t("ui_arrow_curve") };
Object.entries(arrowOptions).forEach(([val, text]) => {
const o = document.createElement('option'); o.value = val; o.innerText = text;
if(activeLayoutSettings.arrowType === val) o.selected = true; arrowSelect.appendChild(o);
});
arrowSelect.onchange = e => activeLayoutSettings.arrowType = e.target.value;
layoutContainer.appendChild(createRow(t("ui_arrow_style"), arrowSelect));
const fixedPointSelect = document.createElement('select'); fixedPointSelect.style.cssText = dirSelect.style.cssText;
fixedPointSelect.innerHTML = ``;
fixedPointSelect.value = activeLayoutSettings.enableFixedPoint ? "true" : "false";
fixedPointSelect.onchange = e => activeLayoutSettings.enableFixedPoint = (e.target.value === "true");
layoutContainer.appendChild(createRow(t("ui_fixed_point"), fixedPointSelect));
layoutContainer.appendChild(createRow(t("ui_node_gap"), createInput("number", activeLayoutSettings.defaultGap, val => activeLayoutSettings.defaultGap = val, "60px")));
layoutContainer.appendChild(createRow(t("ui_line_length"), createInput("number", activeLayoutSettings.lengthBetweenElAndLine, val => activeLayoutSettings.lengthBetweenElAndLine = val, "60px")));
};
const renderThemeSelector = () => {
themeSelectorContainer.innerHTML = `${t("ui_group_theme")}
`;
const topRow = document.createElement('div'); topRow.style.cssText = "display:flex; justify-content:space-between; align-items:center; margin-bottom:10px;";
const themeSelect = document.createElement('select'); themeSelect.style.cssText = "flex-grow:1; background:var(--background-modifier-form-field); border-radius:4px; padding:6px; color:var(--text-normal); border:1px solid var(--background-modifier-border);";
Object.keys(localConfig.themes).forEach(k => {
const o = document.createElement('option'); o.value = k; o.innerText = localConfig.themes[k].name;
if (activeThemeKey === k) o.selected = true; themeSelect.appendChild(o);
});
themeSelect.onchange = (e) => { activeThemeKey = e.target.value; activeLayoutSettings.theme = activeThemeKey; if(isGlobalMode) renderThemeEditor(); };
topRow.appendChild(themeSelect);
if (isGlobalMode) {
const tools = document.createElement('div'); tools.style.cssText = "display:flex; gap:6px; margin-left:10px;";
tools.appendChild(createIconButton("➕", () => {
const newId = 'theme_' + Date.now();
localConfig.themes[newId] = { name: t("ui_new_theme_name"), rootBg: "#343a40", colors: ["#1971c2", "#e03131", "#099268"], levels: JSON.parse(JSON.stringify(DEFAULT_LEVELS)) };
activeThemeKey = newId; activeLayoutSettings.theme = activeThemeKey; renderThemeSelector(); renderThemeEditor();
}, t("ui_new_theme_name")));
tools.appendChild(createIconButton("✏️", async () => {
let newName = null;
if (utils && typeof utils.inputPrompt === "function") {
newName = await utils.inputPrompt(t("ui_prompt_theme_name"), localConfig.themes[activeThemeKey].name, localConfig.themes[activeThemeKey].name);
} else {
newName = prompt(t("ui_prompt_theme_name"), localConfig.themes[activeThemeKey].name);
}
if (newName && newName.trim()) { localConfig.themes[activeThemeKey].name = newName.trim(); renderThemeSelector(); }
}, t("ui_rename_title")));
tools.appendChild(createIconButton("🗑️", async () => {
if (Object.keys(localConfig.themes).length <= 1) return new Notice(t("notice_at_least_one"));
let confirmed = false;
if (utils && typeof utils.suggester === "function") {
confirmed = await utils.suggester(["Yes (删除)", "No (取消)"], [true, false], t("confirm_delete_theme", { name: localConfig.themes[activeThemeKey].name }));
} else {
confirmed = confirm(t("confirm_delete_theme", { name: localConfig.themes[activeThemeKey].name }));
}
if (confirmed) {
delete localConfig.themes[activeThemeKey]; activeThemeKey = Object.keys(localConfig.themes)[0]; activeLayoutSettings.theme = activeThemeKey;
renderThemeSelector(); renderThemeEditor();
}
}, t("ui_delete_title")));
topRow.appendChild(tools);
}
themeSelectorContainer.appendChild(topRow);
};
const renderThemeEditor = () => {
themeEditorContainer.innerHTML = "";
if (!isGlobalMode || !localConfig.themes[activeThemeKey]) return;
const currentTheme = localConfig.themes[activeThemeKey];
const panelWrap = document.createElement('div'); panelWrap.style.cssText = `background:var(--background-secondary-alt); padding:12px; border-radius:6px; border:1px solid var(--background-modifier-border); display:flex; flex-direction:column; gap:12px;`;
panelWrap.appendChild(createRow(t("ui_root_bg"), createInput("color", currentTheme.rootBg, val => currentTheme.rootBg = val)));
const branchDiv = document.createElement('div');
const branchHeader = document.createElement('div'); branchHeader.style.cssText = "display:flex; justify-content:space-between; align-items:center; margin-bottom:8px;";
branchHeader.innerHTML = `${t("ui_branch_colors")}`;
const branchTools = document.createElement('div'); branchTools.style.cssText = "display:flex; gap:4px;";
branchTools.appendChild(createIconButton("+", () => { currentTheme.colors.push("#cccccc"); renderThemeEditor(); }, t("ui_add_color")));
branchTools.appendChild(createIconButton("-", () => { if(currentTheme.colors.length > 1) { currentTheme.colors.pop(); renderThemeEditor(); } }, t("ui_remove_last")));
branchHeader.appendChild(branchTools); branchDiv.appendChild(branchHeader);
const branchColorsWrap = document.createElement('div'); branchColorsWrap.style.cssText = "display:flex; flex-wrap:wrap; gap:6px;";
currentTheme.colors.forEach((c, i) => { branchColorsWrap.appendChild(createInput("color", c, val => currentTheme.colors[i] = val)); });
branchDiv.appendChild(branchColorsWrap); panelWrap.appendChild(branchDiv);
const lvlDiv = document.createElement('div');
lvlDiv.innerHTML = `${t("ui_level_styles")}
`;
currentTheme.levels.forEach((lvl) => {
const row = document.createElement('div'); row.style.cssText = `display:flex; justify-content:space-between; align-items:center; background:var(--background-primary); padding:6px 8px; border-radius:6px; border:1px solid var(--background-modifier-border); margin-bottom:6px;`;
row.innerHTML = `L${lvl.level}`;
const wrap = document.createElement('div'); wrap.style.cssText = `display:flex; gap: 6px; align-items:center;`;
wrap.appendChild(createInput("color", lvl.textColor, val => lvl.textColor = val, "auto", t("ui_text_color")));
wrap.appendChild(createInput("color", lvl.strokeColor, val => lvl.strokeColor = val, "auto", t("ui_stroke_color")));
wrap.appendChild(createInput("number", lvl.strokeWidth, val => lvl.strokeWidth = val, "35px", t("ui_stroke_width")));
wrap.appendChild(createInput("number", lvl.fontSize, val => lvl.fontSize = val, "40px", t("ui_font_size")));
row.appendChild(wrap); lvlDiv.appendChild(row);
});
panelWrap.appendChild(lvlDiv);
themeEditorContainer.appendChild(panelWrap);
};
renderLayout(); renderThemeSelector(); if(isGlobalMode) renderThemeEditor();
panel.appendChild(content);
const footer = document.createElement('div');
footer.style.cssText = `padding:12px 16px; border-top:1px solid var(--background-modifier-border); display:flex; justify-content:flex-end; gap:10px; background:var(--background-secondary); border-radius: 0 0 8px 8px;`;
if (isGlobalMode) {
const btnSave = document.createElement('button'); btnSave.innerText = t("ui_btn_save_global"); btnSave.className = "mod-cta";
btnSave.onclick = async () => {
localConfig.defaultSettings = activeLayoutSettings;
let targetEngineSettings = ExcalidrawAutomate.plugin.settings.scriptEngineSettings["Mindmap_Engine"] || {};
targetEngineSettings["Mindmap Configuration"] = {
value: JSON.stringify(localConfig, null, 2),
description: t("setting_desc")
};
ExcalidrawAutomate.plugin.settings.scriptEngineSettings["Mindmap_Engine"] = targetEngineSettings;
await ExcalidrawAutomate.plugin.saveSettings();
new Notice(t("notice_saved_global")); panel.remove();
};
footer.appendChild(btnSave);
} else {
const btnApply = document.createElement('button'); btnApply.innerText = t("ui_btn_apply"); btnApply.className = "mod-cta";
btnApply.onclick = async () => {
ea.copyViewElementsToEAforEditing([targetRoot]);
const rootForSetting = ea.getElement(targetRoot.id);
if (rootForSetting) rootForSetting.customData.mindmap.settings = JSON.parse(JSON.stringify(activeLayoutSettings));
await ea.addElementsToView(false, false, false);
if (window.MindmapAPI && window.MindmapAPI.runLayout) {
const sceneElements = ea.getViewElements();
const treeElements = sceneElements.filter(el => {
if (el.customData?.mindmap?.root === targetRoot.id) return true;
if (el.type === "arrow" && el.startBinding) {
const startEl = sceneElements.find(e => e.id === el.startBinding.elementId);
return startEl?.customData?.mindmap?.root === targetRoot.id;
} return false;
});
const elementsToLoad = [...treeElements];
treeElements.forEach(el => {
if (el.boundElements) {
el.boundElements.forEach(b => {
if (b.type === "text") { const textEl = sceneElements.find(e => e.id === b.id); if (textEl) elementsToLoad.push(textEl); }
});
}
});
ea.clear(); ea.copyViewElementsToEAforEditing(elementsToLoad);
const rootStyle = resolveStyle(1, 0, activeLayoutSettings, localConfig.themes);
const rEl = ea.getElement(targetRoot.id);
if (rEl && rootStyle) {
rEl.backgroundColor = rootStyle.backgroundColor; rEl.strokeColor = rootStyle.strokeColor || "black"; rEl.strokeWidth = rootStyle.strokeWidth || 1;
if (rEl.type === "text") rEl.fontSize = rootStyle.fontSize;
if (rEl.boundElements) {
rEl.boundElements.forEach(b => { if (b.type === "text") { const text = ea.getElement(b.id); if (text) { text.strokeColor = rootStyle.textColor || "black"; text.fontSize = rootStyle.fontSize; } } });
}
}
const level2Nodes = treeElements.filter(el => el.customData?.mindmap?.parent === targetRoot.id && el.id !== targetRoot.id);
level2Nodes.sort((a,b) => a.y - b.y);
level2Nodes.forEach((l2Node, index) => {
const queue = [{ el: l2Node, branchIndex: index }];
while(queue.length > 0) {
const { el, branchIndex } = queue.shift();
const level = el.customData.mindmap.level;
const style = resolveStyle(level, branchIndex, activeLayoutSettings, localConfig.themes);
const eEl = ea.getElement(el.id);
if (eEl && style) {
eEl.backgroundColor = style.backgroundColor; eEl.strokeColor = style.strokeColor || "black"; eEl.strokeWidth = style.strokeWidth || 1;
if (eEl.type === "text") eEl.fontSize = style.fontSize;
if (eEl.boundElements) {
eEl.boundElements.forEach(b => { if (b.type === "text") { const text = ea.getElement(b.id); if (text) { text.strokeColor = style.textColor || "black"; text.fontSize = style.fontSize; } } });
}
const incomingArrowId = treeElements.find(a => a.type === "arrow" && a.endBinding && a.endBinding.elementId === el.id)?.id;
if(incomingArrowId) {
const arrEl = ea.getElement(incomingArrowId);
if(arrEl) {
arrEl.strokeColor = style.strokeColor || "black";
arrEl.strokeWidth = style.strokeWidth || 1;
arrEl.customData = arrEl.customData || {};
if (activeLayoutSettings.arrowType === "curve") {
arrEl.customData.curveArrow = true;
} else {
delete arrEl.customData.curveArrow;
}
// 注意:不再这里直接修改 elbowed 属性,而是让后续的引擎 runLayout 来计算和接管!
if (activeLayoutSettings.enableFixedPoint) {
if (arrEl.startBinding) arrEl.startBinding.mode = "inside";
if (arrEl.endBinding) arrEl.endBinding.mode = "inside";
} else {
if (arrEl.startBinding) { delete arrEl.startBinding.mode; delete arrEl.startBinding.fixedPoint; }
if (arrEl.endBinding) { delete arrEl.endBinding.mode; delete arrEl.endBinding.fixedPoint; }
}
}
}
}
const children = treeElements.filter(c => c.customData?.mindmap?.parent === el.id && c.id !== el.id);
children.forEach(c => queue.push({ el: c, branchIndex }));
}
});
await ea.addElementsToView(false, false, false);
// 将重新排版以及计算箭头 elbowed / points 的工作交给引擎处理
setTimeout(async () => {
const currentSceneElements = ea.getViewElements();
const updatedElementsMap = new Map(currentSceneElements.map(el => [el.id, el]));
const updatedTreeElements = elementsToLoad.map(el => updatedElementsMap.get(el.id)).filter(Boolean);
await window.MindmapAPI.runLayout(updatedTreeElements, true, ea);
new Notice(t("notice_applied"));
}, 150);
} else { new Notice(t("notice_no_engine")); }
panel.remove();
};
footer.appendChild(btnApply);
}
panel.appendChild(footer); document.body.appendChild(panel);
header.querySelector('#close-btn').onclick = () => panel.remove();
let isDragging = false, startX, startY, initLeft, initTop;
const startDrag = (clientX, clientY, target) => {
if(target && (target.tagName === 'BUTTON' || target.tagName === 'INPUT' || target.tagName === 'SELECT')) return;
isDragging = true; startX = clientX; startY = clientY;
const rect = panel.getBoundingClientRect(); initLeft = rect.left; initTop = rect.top;
panel.style.transform = 'none';
panel.style.left = `${initLeft}px`;
panel.style.top = `${initTop}px`;
};
const doDrag = (clientX, clientY) => {
if(!isDragging) return;
const newLeft = Math.max(10, Math.min(window.innerWidth - panel.offsetWidth - 10, initLeft + clientX - startX));
const newTop = Math.max(10, Math.min(window.innerHeight - panel.offsetHeight - 10, initTop + clientY - startY));
panel.style.left = `${newLeft}px`;
panel.style.top = `${newTop}px`;
};
const endDrag = () => { isDragging = false; };
header.onmousedown = (e) => {
startDrag(e.clientX, e.clientY, e.target);
e.preventDefault();
document.onmousemove = (ev) => doDrag(ev.clientX, ev.clientY);
document.onmouseup = () => { endDrag(); document.onmousemove = null; document.onmouseup = null; };
};
header.ontouchstart = (e) => {
if (e.touches && e.touches.length === 1) {
startDrag(e.touches[0].clientX, e.touches[0].clientY, e.target);
}
};
header.ontouchmove = (e) => {
if (isDragging && e.touches && e.touches.length === 1) {
doDrag(e.touches[0].clientX, e.touches[0].clientY);
e.preventDefault();
}
};
header.ontouchend = () => endDrag();
header.ontouchcancel = () => endDrag();
}
createStylePanel();
if (isGlobalMode) { new Notice(t("notice_global_mode")); }