--- name: 代码高亮样式设置 description: 悬浮面板:调整当前代码块主题、字体、字号,支持存为全局默认。 author: ymjr version: 1.0.0 license: MIT usage: 在画布上选中通过代码高亮插件生成的代码块图片后,运行此脚本打开样式配置面板。 features: - 悬浮面板 UI,支持拖拽和即时预览 - 设置高亮语言(language)、主题(theme)、嵌入字体以及字号 - 提供常用语言快速匹配列表,支持保存为全局默认配置 dependencies: - 依赖 CodeHighlight-Engine 底层引擎完成重新渲染 autorun: false --- /* ```javascript */ var locales = { zh: { notice_saved: "✅ 已保存为全局默认代码样式", panel_title: "🎨 代码高亮设置", row_lang: "代码语言", row_pool: "高频识别池", theme_dark: "🌙 暗色主题", theme_light: "☀️ 亮色主题", row_theme: "高亮主题", row_font: "字体/字号", row_embed: "SVG嵌入字体", btn_save_default: "设为全局默认", btn_done: "完成" }, en: { notice_saved: "✅ Saved as global default code style", panel_title: "🎨 Code Highlight Settings", row_lang: "Language", row_pool: "Detection Pool", theme_dark: "🌙 Dark Theme", theme_light: "☀️ Light Theme", row_theme: "Theme", row_font: "Font/Size", row_embed: "Embed SVG Font", btn_save_default: "Save as Default", btn_done: "Done" } }; const { Notice } = ea.obsidian; ExcalidrawAutomate.plugin.settings.scriptEngineSettings = ExcalidrawAutomate.plugin.settings.scriptEngineSettings || {}; let defaultSettings = ExcalidrawAutomate.plugin.settings.scriptEngineSettings["codeHighlight"] ?? { theme: "atom-one-dark", embedFont: true, fontFamily: 3, fontSize: 16, commonLangs: ["javascript", "typescript", "python", "java", "cpp", "sql", "xml", "json", "bash", "verilog", "gdscript"] }; // 抓取当前选中目标状态 const selectedEls = ea.getViewSelectedElements().filter(el => el.customData?.codeHighlight); let currentSettings = { ...defaultSettings }; if (selectedEls.length > 0) { const target = selectedEls[0]; currentSettings.theme = target.customData.codeHighlight.style || defaultSettings.theme; currentSettings.embedFont = target.customData.codeHighlight.embedFont ?? defaultSettings.embedFont; currentSettings.fontFamily = target.fontFamily; currentSettings.fontSize = target.fontSize; // 新增:读取当前代码块的语言 currentSettings.language = target.customData.codeHighlight.language || "plaintext"; } async function applyToSelected() { if (selectedEls.length === 0) return; const api = ea.getExcalidrawAPI(); selectedEls.forEach((el) => { el.fontFamily = currentSettings.fontFamily; el.fontSize = currentSettings.fontSize; api.ShapeCache?.cache?.delete(el); el.customData = { ...el.customData, codeHighlight: { ...el.customData?.codeHighlight, style: currentSettings.theme, embedFont: currentSettings.embedFont, language: currentSettings.language } }; }); ea.copyViewElementsToEAforEditing(selectedEls); await ea.addElementsToView(); await ea.viewUpdateScene({ appState: { zoom: { value: api.getAppState().zoom.value + 0.00001 } } }); } function saveAsDefault() { // 保存全局默认时不包含 language,因为每个代码块语言不同 const { language, ...settingsToSave } = currentSettings; ExcalidrawAutomate.plugin.settings.scriptEngineSettings["codeHighlight"] = settingsToSave; ExcalidrawAutomate.plugin.saveSettings(); new Notice(t("notice_saved")); } function createFloatingPanel() { if (document.getElementById("ea-code-style-panel")) return; const panel = document.createElement('div'); panel.id = "ea-code-style-panel"; panel.style.cssText = `position:fixed; top:120px; right:80px; width:290px; background:var(--background-primary); border:1px solid var(--background-modifier-border); box-shadow:0 8px 24px rgba(0,0,0,0.2); border-radius:8px; z-index:9999; display:flex; flex-direction:column;`; // 头部拖拽区 const header = document.createElement('div'); header.style.cssText = `padding:10px 15px; 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 = `${t("panel_title")}`; panel.appendChild(header); const content = document.createElement('div'); content.style.cssText = `padding:15px; display:flex; flex-direction:column; gap:12px;`; const createRow = (label, element) => { const div = document.createElement('div'); div.style.cssText = "display:flex; justify-content:space-between; align-items:center;"; 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 langSelect = document.createElement('select'); langSelect.style.cssText = "width:130px; background:var(--background-modifier-form-field); border-radius:4px; padding:4px;"; const langs = getLanguages(); // 提取所有支持的语言并按首字母排序 Object.keys(langs).sort().forEach(key => { const val = langs[key]; if (!val) return; // 过滤掉不支持的空字符串 const opt = document.createElement('option'); opt.value = val; opt.innerText = key; if (currentSettings.language === val) opt.selected = true; langSelect.appendChild(opt); }); langSelect.onchange = (e) => { currentSettings.language = e.target.value; window._ea_last_code_lang = e.target.value; // 修改面板也能记忆到内存中 applyToSelected(); }; content.appendChild(createRow(t("row_lang"), langSelect)); // ===================== const commonLangsInput = document.createElement('input'); commonLangsInput.type = "text"; commonLangsInput.style.cssText = "width:130px; background:var(--background-modifier-form-field); border-radius:4px; padding:4px; font-size:11px;"; commonLangsInput.value = currentSettings.commonLangs.join(","); commonLangsInput.title = "用逗号分隔的常用语言白名单"; commonLangsInput.onchange = (e) => { // 自动切分逗号并去除空格与空项 currentSettings.commonLangs = e.target.value.split(",").map(s => s.trim()).filter(Boolean); }; content.appendChild(createRow(t("row_pool"), commonLangsInput)); // 主题 const themeSelect = document.createElement('select'); themeSelect.style.cssText = "width:130px; background:var(--background-modifier-form-field); border-radius:4px; padding:4px;"; ['atom-one-dark', 'atom-one-light'].forEach(themeKey => { const opt = document.createElement('option'); opt.value = themeKey; opt.innerText = themeKey.includes('dark') ? t("theme_dark") : t("theme_light"); if (currentSettings.theme === themeKey) opt.selected = true; themeSelect.appendChild(opt); }); themeSelect.onchange = (e) => { currentSettings.theme = e.target.value; applyToSelected(); }; content.appendChild(createRow(t("row_theme"), themeSelect)); // 字体字号 const fontWrapper = document.createElement('div'); fontWrapper.style.cssText = "display:flex; gap:6px;"; const fontSelect = document.createElement('select'); fontSelect.style.cssText = "width:80px; background:var(--background-modifier-form-field); border-radius:4px;"; [{v:3, l:"Cascadia"}, {v:1, l:"Virgil"}, {v:2, l:"Helvetica"}].forEach(f => { const opt = document.createElement('option'); opt.value = f.v; opt.innerText = f.l; if (currentSettings.fontFamily === f.v) opt.selected = true; fontSelect.appendChild(opt); }); fontSelect.onchange = (e) => { currentSettings.fontFamily = Number(e.target.value); applyToSelected(); }; const sizeInput = document.createElement('input'); sizeInput.type = "number"; sizeInput.style.cssText = "width:45px; text-align:center;"; sizeInput.value = currentSettings.fontSize; sizeInput.onchange = (e) => { currentSettings.fontSize = Number(e.target.value); applyToSelected(); }; fontWrapper.appendChild(fontSelect); fontWrapper.appendChild(sizeInput); content.appendChild(createRow(t("row_font"), fontWrapper)); // 嵌入字体开关 const embedCheck = document.createElement('input'); embedCheck.type = "checkbox"; embedCheck.checked = currentSettings.embedFont; embedCheck.onchange = (e) => { currentSettings.embedFont = e.target.checked; applyToSelected(); }; content.appendChild(createRow(t("row_embed"), embedCheck)); panel.appendChild(content); // 底部按钮 const footer = document.createElement('div'); footer.style.cssText = `padding:10px 15px; border-top:1px solid var(--background-modifier-border); display:flex; justify-content:space-between;`; const btnSave = document.createElement('button'); btnSave.innerText = t("btn_save_default"); btnSave.onclick = saveAsDefault; const btnClose = document.createElement('button'); btnClose.innerText = t("btn_done"); btnClose.className = "mod-cta"; btnClose.onclick = () => panel.remove(); footer.appendChild(btnSave); footer.appendChild(btnClose); panel.appendChild(footer); document.body.appendChild(panel); header.querySelector('#close-btn').onclick = () => panel.remove(); // 拖拽逻辑 let isDragging = false, startX, startY, initLeft, initTop; header.onmousedown = (e) => { if(e.target.tagName === 'BUTTON' || e.target.tagName === 'INPUT') return; isDragging = true; startX = e.clientX; startY = e.clientY; const rect = panel.getBoundingClientRect(); initLeft = rect.left; initTop = rect.top; e.preventDefault(); document.onmousemove = (e) => { if(!isDragging) return; panel.style.left = (initLeft + e.clientX - startX) + 'px'; panel.style.top = (initTop + e.clientY - startY) + 'px'; }; document.onmouseup = () => { isDragging = false; document.onmousemove = null; document.onmouseup = null; }; }; } createFloatingPanel(); // --- 依赖数据 --- // 用于生成语言下拉列表的字典,已过滤不支持的空语言 function getLanguages() { return { "1C": "1c", "ABNF": "abnf", "Access logs": "accesslog", "Ada": "ada", "Arduino": "arduino", "ARM assembler": "armasm", "AVR assembler": "avrasm", "ActionScript": "actionscript", "AngelScript": "angelscript", "Apache": "apache", "AppleScript": "applescript", "Arcade": "arcade", "AsciiDoc": "asciidoc", "AspectJ": "aspectj", "AutoHotkey": "autohotkey", "AutoIt": "autoit", "Awk": "awk", "Bash": "bash", "Basic": "basic", "BNF": "bnf", "Brainfuck": "brainfuck", "C#": "cs", "C": "c", "C++": "cpp", "C/AL": "cal", "Cache Object Script": "cos", "CMake": "cmake", "COBOL": "cobol", "Coq": "coq", "CSP": "csp", "CSS": "css", "Cap’n Proto": "capnproto", "Clojure": "clojure", "CoffeeScript": "coffeescript", "Crmsh": "crmsh", "Crystal": "crystal", "D": "d", "Dart": "dart", "Delphi": "delphi", "Diff": "diff", "Django": "django", "DNS Zone file": "dns", "Dockerfile": "dockerfile", "DOS": "dos", "dsconfig": "dsconfig", "DTS (Device Tree)": "dts", "Dust": "dust", "EBNF": "ebnf", "Elixir": "elixir", "Elm": "elm", "Erlang": "erlang", "Excel": "excel", "F#": "fsharp", "FIX": "fix", "Flix": "flix", "Fortran": "fortran", "G-Code": "gcode", "Gams": "gams", "GAUSS": "gauss", "GDScript": "gdscript", "Gherkin": "gherkin", "Go": "go", "Golo": "golo", "Gradle": "gradle", "Groovy": "groovy", "HTML, XML": "xml", "HTTP": "http", "Haml": "haml", "Handlebars": "handlebars", "Haskell": "haskell", "Haxe": "haxe", "Hy": "hy", "Ini, TOML": "ini", "Inform7": "inform7", "IRPF90": "irpf90", "JSON": "json", "Java": "java", "JavaScript": "javascript", "Julia": "julia", "Julia REPL": "julia-repl", "Kotlin": "kotlin", "LaTeX": "tex", "Leaf": "leaf", "Lasso": "lasso", "Less": "less", "LDIF": "ldif", "Lisp": "lisp", "LiveCode Server": "livecodeserver", "LiveScript": "livescript", "Lua": "lua", "Makefile": "makefile", "Markdown": "markdown", "Mathematica": "mathematica", "Matlab": "matlab", "Maxima": "maxima", "Maya Embedded Language": "mel", "Mercury": "mercury", "MIPS Assembler": "mipsasm", "Mizar": "mizar", "Mojolicious": "mojolicious", "Monkey": "monkey", "Moonscript": "moonscript", "N1QL": "n1ql", "NSIS": "nsis", "Nginx": "nginx", "Nim": "nimrod", "Nix": "nix", "OCaml": "ocaml", "Objective C": "objectivec", "OpenGL Shading Language": "glsl", "OpenSCAD": "openscad", "Oracle Rules": "ruleslanguage", "Oxygene": "oxygene", "PF": "pf", "PHP": "php", "Parser3": "parser3", "Perl": "perl", "Plaintext": "plaintext", "Pony": "pony", "PostgreSQL": "pgsql", "PowerShell": "powershell", "Processing": "processing", "Prolog": "prolog", "Properties": "properties", "Protocol Buffers": "protobuf", "Puppet": "puppet", "Python": "python", "Python profiler": "profile", "Q": "q", "QML": "qml", "R": "r", "ReasonML": "reasonml", "RenderMan RIB": "rib", "RenderMan RSL": "rsl", "RISC-V Assembly": "riscv", "Roboconf": "roboconf", "Ruby": "ruby", "Rust": "rust", "SAS": "sas", "SCSS": "scss", "SQL": "sql", "STEP Part 21": "step21", "Scala": "scala", "Scheme": "scheme", "Scilab": "scilab", "Shell": "shell", "Smali": "smali", "Smalltalk": "smalltalk", "SML": "sml", "Stan": "stan", "Stata": "stata", "Stylus": "stylus", "SubUnit": "subunit", "Swift": "swift", "Tcl": "tcl", "Test Anything": "tap", "Thrift": "thrift", "TP": "tp", "Twig": "twig", "TypeScript": "typescript", "VB.Net": "vbnet", "VBScript": "vbscript", "VHDL": "vhdl", "Vala": "vala", "Verilog": "verilog", "Vim Script": "vim", "x86 Assembly": "x86asm", "XL": "xl", "XQuery": "xquery", "YAML": "yaml", "Zephir": "zephir" }; }