Maintainers
Pricing
Enterprise
More
More
Documentation
Try Live
Rankings
Install
Blog
Docs7
Ask
New
Add Docs
Obsidian Excalidraw Plugin
https://github.com/zsviczian/obsidian-excalidraw-plugin
Admin
A plugin to edit and view Excalidraw drawings in Obsidian
Tokens:
1,122,270
Snippets:
5,424
Trust Score:
8.3
License:
AGPL-3.0
Update:
1 month ago
Context
Chat
Benchmark
73.48
Latest
Show doc for...
Code
Info
Show Results
Tokens:
Raw
Copy
Link
Tokens:
Raw
Copy
Link
### Install Pack Logic Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Comic Strip Director.md Main function to install a .strippack into the bundle, validating format and existing indices before modification. ```javascript async function installPack(pack, onProgress) { const appRef = _vaultApp(); const adapter = appRef && appRef.vault && appRef.vault.adapter; if (!adapter) throw new Error("No vault adapter available."); if (!pack || pack.format !== PACK_FORMAT) { const fmt = pack && String(pack.format || ""); if (fmt && fmt.startsWith("strippack/")) throw new Error("This pack uses a newer format (" + fmt + ") — update the Comic Strip Director script (you have v" + SCRIPT_VERSION + "), then import again."); throw new Error("Not a Strip Director pack (expected " + PACK_FORMAT + ")."); } const dir = _aiDir(); // 0) Read (and validate) BOTH index files before any file is written — a corrupt // index aborts the import with zero side effects (no orphaned images). const rosterPath = await _resolveIndexPath(adapter, [dir + "/" + ROSTER_FILE, ROSTER_FILE, "AI Figures/" + ROSTER_FILE]); const roster = (await _readIndexOrThrow(adapter, rosterPath, "roster.json")) || { characters: [], functions: [], actions: [] }; roster.characters = (roster.characters || []).filter(Boolean); roster.functions = (roster.functions || []).filter(Boolean); roster.actions = (roster.actions || []).filter(Boolean); const manifestPath = await _resolveIndexPath(adapter, [dir + "/" + AI_MANIFEST, AI_MANIFEST, "AI Figures/" + AI_MANIFEST]); const manifest = (await _readIndexOrThrow(adapter, manifestPath, "ai-figures.json")) || { figures: [] }; manifest.figures = (manifest.figures || []).filter(Boolean); await _ensureDir(adapter, dir); ``` -------------------------------- ### Experimental Dynamic Styling Script Example Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/docs/Release-notes.md An example script for dynamically styling the Excalidraw Toolbar, recommended for users of the experimental feature. ```javascript /* * This script is an example of how to dynamically style the Excalidraw Toolbar. * It is based on the gist provided in the release notes. * You can modify this script to customize the appearance of the toolbar. */ // Example styling for the toolbar const toolbarStyles = { backgroundColor: '#f0f0f0', border: '1px solid #ccc', borderRadius: '5px', padding: '10px', }; // Example styling for toolbar buttons const buttonStyles = { backgroundColor: '#e0e0e0', border: 'none', borderRadius: '3px', padding: '5px 10px', margin: '0 5px', cursor: 'pointer', }; // Apply styles to the toolbar and buttons (implementation depends on Excalidraw API) // This is a conceptual example and may require specific Excalidraw API calls to function. console.log('Applying toolbar styles:', toolbarStyles); console.log('Applying button styles:', buttonStyles); // In a real scenario, you would interact with Excalidraw's internal components or APIs // to apply these styles. For instance, if Excalidraw exposes a way to // access and modify its UI elements, you would use that here. // Example (hypothetical): // ExcalidrawAPI.setToolbarStyles(toolbarStyles); // ExcalidrawAPI.setButtonStyles(buttonStyles); ``` -------------------------------- ### Fountain Pen Configuration Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Archive/Alternative Pens.md Example configuration for a fountain pen utilizing 'easeInQuad' easing and specific tapering and capping options for start and end points. ```json { "options": { "smoothing": 0.22, "thinning": 0.8, "streamline": 0.22, "easing": "easeInQuad", "start": { "taper": true, "cap": true, }, "end": { "taper": 1, "cap": true, }, } } ``` -------------------------------- ### Initialize Starter Pack Installation Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Comic Strip Director.md Displays a UI prompt for users with no installed characters to download the free starter pack. Handles the download process and UI updates upon success. ```javascript // Empty library — the FIRST thing a fresh two-file install sees. Put the // one-click starter right here at the top instead of a stale warning. statusRow.style.color = "var(--text-muted)"; statusRow.setText("No characters installed yet — grab the free starter cast:"); const getRow = contentEl.createDiv(); getRow.style.cssText = "display:flex;align-items:center;gap:8px;margin:6px 0 0;flex-wrap:wrap"; const freeBtn = getRow.createEl("button", { text: "⭐ Get the free starter pack" }); styleActionBtn(freeBtn, { accent: true }); freeBtn.title = "One click: downloads the free Core Cast + FX packs and imports them"; freeBtn.onclick = async () => { try { // getRow first: the progress box appears right HERE where the user // clicked (top of the panel), plus over the sections further down. if (await downloadFreeStarterPacks(() => createImportProgressMulti([getRow, tab && tab.__csdCharSection, tab && tab.__csdFxSection]))) { await reloadPackCaches(); await buildPanel(tab, ctx); } } catch (e) { console.error("Strip Director: starter pack install failed", e); new Notice("Starter pack install failed — see console."); } }; const hint = getRow.createEl("span", { text: "8 characters + FX, free — or drop a .strippack in your scripts folder and use ⬇ Import pack… below." }); hint.style.cssText = "font-size:0.72em;color:var(--text-faint)"; ``` -------------------------------- ### Get Next Element Following Arrow Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Excalidraw Writing Machine.md Finds the element connected to a given element via an arrow. It checks both the start and end bindings of the arrow. ```javascript function getNextElementFollowingArrow(el, arrow) { if (arrow.startBinding?.elementId === el.id) { return ea.getViewElements().find(x => x.id === arrow.endBinding?.elementId); } if (arrow.endBinding?.elementId === el.id) { return ea.getViewElements().find(x => x.id === arrow.startBinding?.elementId); } return null; } ``` -------------------------------- ### Initialize and Manage Icon Library Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/docs/AITrainingData/ExcalidrawStartupExample.md Sets up the library folder, configures Obsidian Sync to ignore it, and creates the base template file if it does not exist. ```javascript const IMAGE_LIBRARY_FOLDER = ea.obsidian.normalizePath("Assets/nosync"); const IMAGE_LIBRARY_FILENAME = "Image Library.base" const ICONTYPES = [ {name: "Icon", pattern: "icon"}, {name: "Stickfigure", pattern: "stickfigure"}, {name: "Logo", pattern: "logo"} ]; const IMAGE_LIBRARY_PATH = ea.obsidian.normalizePath(IMAGE_LIBRARY_FOLDER + "/" + IMAGE_LIBRARY_FILENAME); async function initializeImageLibrary() { await ea.checkAndCreateFolder(IMAGE_LIBRARY_FOLDER); const syncPlugin = app.internalPlugins.plugins["sync"]?.instance; if(syncPlugin && !syncPlugin.ignoreFolders.includes(IMAGE_LIBRARY_FOLDER)) { syncPlugin.setIgnoreFolders(syncPlugin.ignoreFolders.concat(IMAGE_LIBRARY_FOLDER)); } const imgLibFile = app.vault.getFileByPath(IMAGE_LIBRARY_PATH); if(!imgLibFile) { //The bases file is very sensitive to spaces, indents, and formatting //take care when modifying this const baseTemplate = `formulas:\n` + ` Icon: image(file.path)\n` + ` keywords: file.name.split(" - ")[1]\n` + ` icon-path: link(if(file.ext == "md", "Assets/" + file.name.split(" - ")[0] + "s/" + file.name + ".svg", file.path))\n` + `views:\n` + ` - type: cards\n` + ` name: View\n` + ` filters:\n` + ` and:\n` + ` - /^(icon|stickfigure|logo) \\- /i.matches(file.name.lower())\n` + ` - '!file.path.startsWith("Assets/")'\n` + ` - /./i.matches(formula.keywords)\n` + ` order:\n` + ` - formula.keywords\n` + ` sort:\n` + ` - property: formula.keywords\n` + ` direction: ASC\n` + ` cardSize: 130\n` + ` imageFit: contain\n` + ` image: formula.icon-path\n` + ` imageAspectRatio: 0.8\n`; await app.vault.create(IMAGE_LIBRARY_PATH, baseTemplate); } } initializeImageLibrary(); ``` -------------------------------- ### Install Normalize Selected Arrows Script Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/docs/AITrainingData/ExcalidrawAutomate full library for LLM training.md Use this script to reset arrow start and end positions to point to the center of connected boxes with an 8px gap. ```excalidraw-script-install https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Normalize%20Selected%20Arrows.md ``` -------------------------------- ### Execute Main Initialization Sequence Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Comicbook Callout Editor.md Handles the core startup logic, including checking for existing sidepanel tabs and initializing state. ```javascript async function main() { const existingTab = ea.checkForActiveSidepanelTabForScript(); if (existingTab) { const hostEA = existingTab.getHostEA(); if (hostEA && hostEA !== ea) { // Prevent setting an invalid view during startup if (ea.targetView) hostEA.setView(ea.targetView); existingTab.open(); return; } } initializeStateAndPresets(); detectEditTarget(); const tab = await ea.createSidepanelTab("Comic Bubbles", true, true); if (!tab) return; ``` -------------------------------- ### Initialize Slideshow Environment Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/docs/AITrainingData/excalidraw-automate/references/scripts/Slideshow.md Sets up the owner window context and retrieves the Excalidraw API and current view state. ```javascript const ownerWindow = ea.targetView.ownerWindow; const excalidrawAPI = ea.getExcalidrawAPI(); const frameRenderingOriginalState = excalidrawAPI.getAppState().frameRendering; const contentEl = ea.targetView.contentEl; const sleep = async (ms) => new Promise((resolve) => ownerWindow.setTimeout(resolve, ms)); const getFrameName = (name, index) => name ?? `Frame ${(index+1).toString().padStart(2, '0')}`; ``` -------------------------------- ### Quick Start: API Initialization and Method Discovery Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/docs/ea-script-docs/MindMapBuilderAPI.md Check if the API is ready, list available methods, and get the current view. Throws an error if the API is not ready. ```javascript const mmb = window.MindMapBuilderAPI; if (!mmb?.ready()) throw new Error("MindMapBuilderAPI not ready"); const methods = mmb.listMethods(); console.log(methods); const view = mmb.getView(); console.log(view); ``` -------------------------------- ### Install Comicbook Callout Editor Script Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/index-new.md Use this URL to install the Comicbook Callout Editor script via the Excalidraw script installer. ```excalidraw-script-install https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Comicbook%20Callout%20Editor.md ``` -------------------------------- ### Download Free Starter Packs Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Comic Strip Director.md Fetches and imports free .strippack files from GitHub. Requires an active vault adapter and internet connectivity. ```javascript const FREE_PACK_URLS = [ "https://raw.githubusercontent.com/iwanhoogendoorn/obsidian-excalidraw-plugin/main/ea-scripts/comic-strip-director/free/core-free.strippack", "https://raw.githubusercontent.com/iwanhoogendoorn/obsidian-excalidraw-plugin/main/ea-scripts/comic-strip-director/free/comic-fx-free.strippack", ]; const _FREE_MANUAL_HINT = "Get the packs manually instead: download them from the GitHub repo's free/ folder (or comicstripdirector.com), drop them in your Excalidraw scripts folder, then use ⬇ Import pack…"; function _freeTierInstalled() { try { const s = (ea.getScriptSettings && ea.getScriptSettings()) || {}; return (s.installedPacks || []).some((r) => r && (r.product === "core-free" || r.packId === "core-free")); } catch (e) { return false; } } async function downloadFreeStarterPacks(makeProgress) { if (_importActive) { new Notice("An import is already running — wait for it to finish (or cancel it) first."); return false; } _importActive = true; const prog = makeProgress ? makeProgress() : null; try { const ad = _vaultApp() && _vaultApp().vault && _vaultApp().vault.adapter; if (!ad) { new Notice("No vault adapter available."); return false; } const req = ea.obsidian && ea.obsidian.requestUrl; const root = _scriptsRoot(); const local = []; for (let i = 0; i < FREE_PACK_URLS.length; i++) { if (prog && prog.cancelled) return false; const url = FREE_PACK_URLS[i]; const name = url.split("/").pop(); const dest = root + "/" + name; if (prog) prog.pack(i + 1, FREE_PACK_URLS.length, name + " — downloading…"); if (!(await ad.exists(dest))) { // already downloaded earlier → reuse if (!req) { new Notice("This app version can't download files. " + _FREE_MANUAL_HINT, 12000); return false; } try { const res = await req({ url, throw: true }); await ad.writeBinary(dest, res.arrayBuffer); } catch (e) { console.error("Strip Director: free pack download failed", url, e); new Notice(name + ": download failed — check your internet connection. " + _FREE_MANUAL_HINT, 12000); return false; } } local.push(dest); if (prog) prog.packDone(i + 1, FREE_PACK_URLS.length); } if (prog && prog.cancelled) return false; return (await importPackFiles(local, prog)) > 0; } finally { _importActive = false; if (prog) prog.done(); } } ``` -------------------------------- ### Install Excalidraw Plugin Beta Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/docs/Release-notes.md Instructions for installing beta versions of the Excalidraw plugin. Always back up your vault before installing beta releases due to potential file format changes. ```text - Exit Obsidian. - Copy the 3 files `main.js`, `manifest.json`, `styles.css` to the `vault/.obsidian/plugins/obsidian-excalidraw-plugin/` folder. - Restart Obsidian. ``` -------------------------------- ### Define Script Configuration and UI Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/docs/AITrainingData/excalidraw-automate/references/scripts/Printable Layout Wizard.md Sets up the help text, modal interface, and frame rendering settings required for the layout wizard. ```javascript const HELP_TEXT = ` **Easily split your Excalidraw drawing into printable pages!** If you find this script helpful, consider [buying me a coffee](https://ko-fi.com/zsolt). Thank you. --- ### How it works - **Define Pages:** Use frames to mark out each page area in your drawing. You can create the first frame with this script (choose a standard size or orientation), or draw your own frame for a custom page size. - **Add More Pages:** Select a frame, then use the arrow buttons to add new frames next to it. All new frames will match the size of the selected one. - **Rename Frames:** You can rename frames as you like. When exporting to PDF, pages will be ordered alphabetically by frame name. --- ### Important Notes - **Same Size & Orientation:** All frames must have the same size and orientation (e.g., all A4 Portrait) to export to PDF. Excalidraw currently does not support PDFs with different-sized pages. - **Custom Sizes:** If you draw your own frame, the PDF will use that exact size—great for custom page layouts! - **Margins:** If you set a margin, the page size stays the same, but your content will shrink to fit inside the printable area. - **No Frame Borders/Titles in Print:** Frame borders and frame titles will *not* appear in the PDF. - **No Frame Clipping:** The script disables frame clipping for this drawing. - **Templates:** You can save a template document with prearranged frames (even locked ones) for reuse. - **Lock Frames:** Frames only define print areas—they don't "contain" elements. Locking frames is recommended to prevent accidental movement. - **Outside Content:** Anything outside the frames will *not* appear in the PDF. --- ### Printing - **Export to PDF:** Click the printer button to export each frame as a separate page in a PDF. - **Order:** Pages are exported in alphabetical order of frame names. --- ### Settings You can also access script settings at the bottom of Excalidraw Plugin settings. The script stores your preferences for: - Locking new frames after creation - Zooming to new frames - Closing the dialog after adding a frame - Default page size and orientation - Print margin --- **Tip:** For more on templates, see [Mastering Excalidraw Templates](https://youtu.be/jgUpYznHP9A). For referencing pages in markdown, see [Image Fragments](https://youtu.be/sjZfdqpxqsg) and [Image Block References](https://youtu.be/yZQoJg2RCKI).   `; async function run() { modal = new ea.FloatingModal(ea.plugin.app); window.excalidrawPrintableLayoutWizardModal = modal; modal.contentEl.empty(); let shouldRestart = false; // Enable frame rendering const st = ea.getExcalidrawAPI().getAppState(); let {enabled, clip, name, outline, markerName, markerEnabled} = st.frameRendering; if(!enabled || !name || !outline || !markerEnabled || !markerName) { ea.viewUpdateScene({ appState: { frameRendering: { enabled: true, clip: clip, name: true, outline: true, markerName: true, markerEnabled: true } } }); } // Page size options (using standard sizes from ExcalidrawAutomate) const PAGE_SIZES = [ "A0", "A1", "A2", "A3", "A4", "A5", "A6", "Letter", "Legal", "Tabloid", "Ledger" ]; const PAGE_ORIENTATIONS = ["portrait", "landscape"]; // Margin sizes in points const MARGINS = { "none": 0, "tiny": 10, "normal": 60, }; // Initialize settings let settings = ea.getScriptSettings(); let dirty = false; ``` -------------------------------- ### Install Invert Colors Script Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/index-new.md Installs the script to invert colors on the canvas and in Element Properties. ```excalidraw-script-install https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Invert%20colors.md ``` -------------------------------- ### Initialize Printable Layout Wizard Settings Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/docs/AITrainingData/ExcalidrawAutomate full library for LLM training.md Sets up default configuration keys and values for the layout wizard, including handling legacy casing corrections. ```javascript // Define setting keys const PAGE_SIZE = "Page size"; const ORIENTATION = "Page orientation"; const MARGIN = "Print-margin"; const LOCK_FRAME = "Lock frame after it is created"; const SHOULD_ZOOM = "Should zoom after adding page"; const SHOULD_CLOSE = "Should close after adding page"; const PRINT_EMPTY = "Print empty pages"; const PRINT_MARKERS_ONLY = "Print only marker frames"; // Set default values on first run if (!settings[PAGE_SIZE]) { settings = {}; settings[PAGE_SIZE] = { value: "A4", valueset: PAGE_SIZES }; settings[ORIENTATION] = { value: "portrait", valueset: PAGE_ORIENTATIONS }; settings[MARGIN] = { value: "none", valueset: Object.keys(MARGINS)}; settings[SHOULD_ZOOM] = { value: false }; settings[SHOULD_CLOSE] = { value: false }; settings[LOCK_FRAME] = { value: true }; settings[PRINT_EMPTY] = { value: false }; settings[PRINT_MARKERS_ONLY] = { value: true }; dirty = true; } //once off correction. In the first version I incorrectly used valueSet with wrong casing. if(settings[PAGE_SIZE].valueSet) { settings[PAGE_SIZE].valueset = settings[PAGE_SIZE].valueSet; delete settings[PAGE_SIZE].valueSet; settings[ORIENTATION].valueset = settings[ORIENTATION].valueSet; delete settings[ORIENTATION].valueSet; settings[MARGIN].valueset = settings[MARGIN].valueSet; delete settings[MARGIN].valueSet; dirty = true; } if(!settings[LOCK_FRAME]) { settings[LOCK_FRAME] = { value: true }; dirty = true; } if(!settings[PRINT_EMPTY]) { settings[PRINT_EMPTY] = { value: false }; dirty = true; } if(!settings[PRINT_MARKERS_ONLY]) { settings[PRINT_MARKERS_ONLY] = { value: true }; dirty = true; } let lockFrame = settings[LOCK_FRAME].value; let shouldClose = settings[SHOULD_CLOSE].value; let shouldZoom = settings[SHOULD_ZOOM].value; let printEmptyPages = settings[PRINT_EMPTY].value; let printMarkersOnly = settings[PRINT_MARKERS_ONLY].value; ``` -------------------------------- ### Install Expand Rectangles Horizontally Script Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/index-new.md Installs a script to standardize the width of selected rectangles. ```excalidraw-script-install https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Expand%20rectangles%20horizontally.md ``` -------------------------------- ### Run Layout Wizard Logic Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/docs/AITrainingData/ExcalidrawAutomate full library for LLM training.md Configures the environment, enables frame rendering, and sets up the UI components for the layout wizard. ```javascript async function run() { modal = new ea.FloatingModal(ea.plugin.app); window.excalidrawPrintableLayoutWizardModal = modal; modal.contentEl.empty(); let shouldRestart = false; // Enable frame rendering const st = ea.getExcalidrawAPI().getAppState(); let {enabled, clip, name, outline, markerName, markerEnabled} = st.frameRendering; if(!enabled || !name || !outline || !markerEnabled || !markerName) { ea.viewUpdateScene({ appState: { frameRendering: { enabled: true, clip: clip, name: true, outline: true, markerName: true, markerEnabled: true } } }); } // Page size options (using standard sizes from ExcalidrawAutomate) const PAGE_SIZES = [ "A0", "A1", "A2", "A3", "A4", "A5", "A6", "Letter", "Legal", "Tabloid", "Ledger" ]; const PAGE_ORIENTATIONS = ["portrait", "landscape"]; // Margin sizes in points const MARGINS = { "none": 0, "tiny": 10, "normal": 60, }; // Initialize settings let settings = ea.getScriptSettings(); let dirty = false; ``` -------------------------------- ### Generate UI Instructions Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/docs/AITrainingData/ExcalidrawAutomate full library for LLM training.md Constructs an HTML string containing instructions and promotional links. ```javascript const getInstructions = () => ` <br> ${t("INSTRUCTIONS")} <div class="ex-coffee-div"><a href="https://ko-fi.com/zsolt"><img src="https://storage.ko-fi.com/cdn/kofi6.png?v=6" border="0" alt="Buy Me a Coffee at ko-fi.com" height=45></a></div> <a href="YouTube: 5G9QF-u9w0Q" target="_blank"><img src ="https://i.ytimg.com/vi/5G9QF-u9w0Q/maxresdefault.jpg" style="max-width:560px; width:100%"></a> `; ``` -------------------------------- ### Recalculate Line Start Point Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/docs/AITrainingData/Excalidraw Script Library.md Adjusts the start point of a line to intersect with the boundary of a target element. ```javascript function recalculateStartPointOfLine(line, el) { const aX = el.x + el.width/2; const bX = line.x + line.points[1][0]; const aY = el.y + el.height/2; const bY = line.y + line.points[1][1]; line.startBinding.gap = 8; line.startBinding.focus = 0; const intersectA = ea.intersectElementWithLine( el, [bX, bY], [aX, aY], line.startBinding.gap ); if(intersectA.length > 0) { line.points[0] = [0, 0]; for(var i = 1; i<line.points.length; i++) { line.points[i][0] -= intersectA[0][0] - line.x; line.points[i][1] -= intersectA[0][1] - line.y; } line.x = intersectA[0][0]; line.y = intersectA[0][1]; } } ``` -------------------------------- ### Recalculate Line Start Point Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Expand rectangles vertically keep text centered.md Helper function to recalculate the start point of a line connected to an element. ```javascript function recalculateStartPointOfLine(line, el) { const aX = el.x + el.width/2; const bX = line.x + line.points[1][0]; const aY = el.y + el.height/2; const bY = line.y + line.points[1][1]; line.startBinding.gap = 8; line.startBinding.focus = 0; const intersectA = ea.intersectElementWithLine( el, [bX, bY], [aX, aY], line.startBinding.gap ); if(intersectA.length > 0) { line.points[0] = [0, 0]; for(var i = 1; i<line.points.length; i++) { line.points[i][0] -= intersectA[0][0] - line.x; line.points[i][1] -= intersectA[0][1] - line.y; } line.x = intersectA[0][0]; line.y = intersectA[0][1]; } } ``` -------------------------------- ### Download Sample Import File Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Color Scheme Manager.md Downloads a sample text file for color scheme imports to the user's Downloads folder. ```javascript function downloadSampleImport() { try { const blob = new Blob([SAMPLE_IMPORT], { type: "text/plain" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "color-scheme-import-sample.txt"; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url); new Notice("Saved color-scheme-import-sample.txt to your Downloads folder."); } catch (e) { console.error("Color Scheme Manager: sample download failed", e); new Notice("Could not download the sample — see console."); } } ``` -------------------------------- ### Install Organic Line Legacy Script Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/docs/AITrainingData/ExcalidrawAutomate full library for LLM training.md Installs the legacy version of the Organic Line script for backward compatibility. ```excalidraw-script-install https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Organic%20Line%20Legacy.md ``` -------------------------------- ### Install Mindmap Builder Script Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/index-new.md Use this URL to install the Mindmap Builder script via the Excalidraw plugin. ```excalidraw-script-install https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Mindmap%20Builder.md ``` -------------------------------- ### Scribble Helper Initialization and Setup Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Scribble Helper.md Initializes the Scribble Helper script, verifies the Excalidraw plugin version, and sets up global variables and default settings for text element creation. ```javascript if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.19.0")) { new Notice("This script requires a newer version of Excalidraw. Please install the latest version."); return; } // ------------------------------ // Constants and initialization // ------------------------------ const helpLINK = "https://youtu.be/BvYkOaly-QM"; const DBLCLICKTIMEOUT = 300; const maxWidth = 600; const padding = 6; const api = ea.getExcalidrawAPI(); const win = ea.targetView.ownerWindow; // Initialize global variables if(!win.ExcalidrawScribbleHelper) win.ExcalidrawScribbleHelper = {}; if(typeof win.ExcalidrawScribbleHelper.penOnly === "undefined") { win.ExcalidrawScribbleHelper.penOnly = false; } let windowOpen = false; //to prevent the modal window to open again while writing with scribble let prevZoomValue = api.getAppState().zoom.value; //used to avoid trigger on pinch zoom // ------------- // Load settings // ------------- let settings = ea.getScriptSettings(); //set default values on first-ever run of the script if(!settings["Default action"]) { settings = { "Default action" : { value: "Text", valueset: ["Text","Sticky","Wrap"], description: "What type of element should CTRL/CMD+ENTER create. TEXT: A regular text element. " + "STICKY: A sticky note with border color and background color " + "(using the current setting of the canvas). STICKY: A sticky note with transparent " + "border and background color." }, }; await ea.setScriptSettings(settings); } if(typeof win.ExcalidrawScribbleHelper.action === "undefined") { win.ExcalidrawScribbleHelper.action = settings["Default action"].value; } ``` -------------------------------- ### Render Help Documentation Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/docs/AITrainingData/excalidraw-automate/references/scripts/Mindmap Builder.js.md Creates a collapsible details element containing help instructions and version information. ```javascript const renderHelp = (container) => { helpContainer = container.createDiv(); detailsEl = helpContainer.createEl("details"); const summary = detailsEl.createEl("summary", { attr: { style: "cursor: pointer;" } }); // Title summary.createSpan({ text: t("HELP_SUMMARY"), attr: { style: "font-weight: bold;" } }); // Version Number summary.createSpan({ text: VERSION, attr: { style: "float: right; color: var(--text-muted); font-size: 0.8em;" } }); ea.obsidian.MarkdownRenderer.render(app, getInstructions(), detailsEl.createDiv(), "", ea.plugin); }; ``` -------------------------------- ### Initialize Image Library with Bases Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/docs/AITrainingData/excalidraw-automate/references/startup-scripts.md Sets up a folder and a .base file for managing image libraries within Obsidian. ```javascript const IMAGE_LIBRARY_FOLDER = ea.obsidian.normalizePath("Assets/nosync"); const IMAGE_LIBRARY_FILENAME = "Image Library.base" const ICONTYPES = [ {name: "Icon", pattern: "icon"}, {name: "Stickfigure", pattern: "stickfigure"}, {name: "Logo", pattern: "logo"} ]; const IMAGE_LIBRARY_PATH = ea.obsidian.normalizePath(IMAGE_LIBRARY_FOLDER + "/" + IMAGE_LIBRARY_FILENAME); async function initializeImageLibrary() { await ea.checkAndCreateFolder(IMAGE_LIBRARY_FOLDER); const syncPlugin = app.internalPlugins.plugins["sync"]?.instance; if(syncPlugin && !syncPlugin.ignoreFolders.includes(IMAGE_LIBRARY_FOLDER)) { syncPlugin.setIgnoreFolders(syncPlugin.ignoreFolders.concat(IMAGE_LIBRARY_FOLDER)); } const imgLibFile = app.vault.getFileByPath(IMAGE_LIBRARY_PATH); if(!imgLibFile) { //The bases file is very sensitive to spaces, indents, and formatting //take care when modifying this const baseTemplate = `formulas:\n` + ` Icon: image(file.path)\n` + ` keywords: file.name.split(" - ")[1]\n` + ` icon-path: link(if(file.ext == "md", "Assets/" + file.name.split(" - ")[0] + "s/" + file.name + ".svg", file.path))\n` + `views:\n` + ` - type: cards\n` + ` name: View\n` + ` filters:\n` + ` and:\n` + ` - /^(icon|stickfigure|logo) \\- /i.matches(file.name.lower())\n` + ` - '!file.path.startsWith("Assets/")'\n` + ` - /./i.matches(formula.keywords)\n` + ` order:\n` + ` - formula.keywords\n` + ` sort:\n` + ` - property: formula.keywords\n` + ` direction: ASC\n` + ` cardSize: 130\n` + ` imageFit: contain\n` + ` image: formula.icon-path\n` + ` imageAspectRatio: 0.8\n`; await app.vault.create(IMAGE_LIBRARY_PATH, baseTemplate); } } initializeImageLibrary(); async function revealIconLibrary() { const file = app.vault.getFileByPath(IMAGE_LIBRARY_PATH); if(!file) return; let leaf; app.workspace.iterateAllLeaves(l=>{ if(leaf) return; if(l.view?.getViewType() === "bases" && l.view.getState().file === file.path) leaf = l; }); if(leaf) { app.workspace.revealLeaf(leaf); return file; } leaf = app.workspace.getRightLeaf(); await leaf.openFile(file); app.workspace.revealLeaf(leaf); return file; } if(ea.verifyMinimumPluginVersion("2.13.2")) { ea.plugin.addCommand({ id: "base-filter-keywords", name: "Icon Library", icon: "images", callback: async () => { // Check if the active file is a .base file const file = await revealIconLibrary(); if(!file) return false; let baseContent = await app.vault.read(file); // Check if the file has the specific patterns for filtering if (!baseContent.includes(".matches(formula.keywords)")) return; // Create a modal using Obsidian's Modal class const Modal = ea.FloatingModal; const modal = new Modal(app); const { contentEl } = modal; contentEl.createEl("style", { text: ` ``` -------------------------------- ### Install Set Link Alias Script Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/docs/AITrainingData/ExcalidrawAutomate full library for LLM training.md Installs the script to iterate through links in selected TextElements and prompt for alias modifications. ```excalidraw-script-install https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Link%20Alias.md ``` -------------------------------- ### Install Reset LaTeX Size Script Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/docs/AITrainingData/ExcalidrawAutomate full library for LLM training.md Installs the script to reset embedded LaTeX equations to default or scaled sizes. ```excalidraw-script-install https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Reset%20LaTeX%20Size.md ``` -------------------------------- ### Initialize Card Generation Process Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/docs/AITrainingData/excalidraw-automate/references/scripts/Image Occlusion.md Handles the initial setup for the card generation process, including template selection and validation for specific modes. ```javascript let counter = 1; let templateFile = null; // Move templateFile declaration to outer scope if(mode === "hideAll") { // Get template selection from user for Hide All mode const templates = getTemplates(); // Only try to get template if templates exist if (templates) { // Get template file based on settings or user selection templateFile = await getTemplateFile(templates); } // Check if we should proceed without template const generateImagesNoMatterWhat = settings["Generate Images No Matter What"]?.value === "yes"; if (!templateFile && !generateImagesNoMatterWhat) { new Notice("Operation cancelled - no template selected"); return; } ``` -------------------------------- ### Install Organic Line Script Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/docs/AITrainingData/ExcalidrawAutomate full library for LLM training.md Installs the Organic Line script to convert freedraw lines with variable pencil pressure. ```excalidraw-script-install https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Organic%20Line.md ``` -------------------------------- ### Initialize Folders and Batch Markers Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/docs/AITrainingData/excalidraw-automate/references/scripts/Image Occlusion.md Sets up the directory structure for images and cards, and initializes a batch marker file to track generated content. ```javascript // Create necessary folders for storing images and cards const imageName = getSourceFileName(); const imageFolder = getImageFolder(imageName, timestamp); const cardFolder = await askForCardLocation(imageFolder); // Exit if user cancelled location selection if (cardFolder === null) { new Notice("Operation cancelled"); return; } // Create image folder with all parent directories await app.vault.adapter.mkdir(imageFolder, { recursive: true }); // Create card folder if different from image folder if(cardFolder !== imageFolder) { await app.vault.adapter.mkdir(cardFolder, { recursive: true }); } // Create initial batch marker file const createBatchMarker = async (sourceFile) => { const content = `Source: [[${sourceFile}|find edit source]]\n\nGenerated Cards:\n`; const fileName = `${imageFolder}/batch-marker.md`; await app.vault.create(fileName, content); return fileName; }; // Add card to batch marker const addCardToBatchMarker = async (cardPath) => { const markerPath = `${imageFolder}/batch-marker.md`; const currentContent = await app.vault.read(app.vault.getAbstractFileByPath(markerPath)); // Use full path in batch-marker const newContent = currentContent + `[[${cardPath}]]\n`; await app.vault.modify(app.vault.getAbstractFileByPath(markerPath), newContent); }; // Create batch marker file after folders are created const sourceFile = getCurrentFilePath(); const batchMarkerFile = await createBatchMarker(sourceFile); ``` -------------------------------- ### Initialize ExcaliAI Modal UI Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/docs/AITrainingData/excalidraw-automate/references/scripts/ExcaliAI.md Sets up the modal's CSS styles and DOM structure, including header, main container columns, and warning elements. ```javascript configModal.onOpen = async () => { const contentEl = configModal.contentEl; // --- CSS --- contentEl.createEl("style", { text: ` .excali-ai-floating-modal { width: min(1000px, 95vw) !important; max-height: 90vh !important; border-radius: 8px; } .excali-ai-header { display: flex; align-items: center; gap: 10px; margin-bottom: 15px; border-bottom: 1px solid var(--background-modifier-border); padding-bottom: 10px; } .excali-ai-header h2 { margin: 0; font-weight: 600; } .excali-ai-header svg { width: 28px; height: 28px; color: var(--interactive-accent); } .excali-ai-main-container { display: flex; flex-direction: column; gap: 20px; } .excali-ai-left-col { flex: 1 1 55%; min-width: 0; display: flex; flex-direction: column; } .excali-ai-right-col { flex: 1 1 45%; min-width: 0; background: var(--background-secondary); padding: 15px; border-radius: 8px; border: 1px solid var(--background-modifier-border); } @media (min-width: 768px) { .excali-ai-main-container { flex-direction: row; } } .excali-ai-warning { display: flex; align-items: center; gap: 8px; padding: 10px; background: var(--background-modifier-error); color: var(--text-error); border-radius: 6px; margin-bottom: 15px; font-size: 0.9em; } .excali-ai-preview-img { max-width: 100%; max-height: 250px; object-fit: contain; border: 1px solid var(--background-modifier-border); border-radius: 4px; margin-top: 10px; background: var(--background-primary); } .excali-ai-help-text { font-size: 0.9em; color: var(--text-muted); margin-top: 5px; margin-bottom: 15px; } .excali-ai-validation-text { font-size: 0.9em; color: var(--text-error); margin-top: 5px; margin-bottom: 15px; font-weight: 500; } .excali-ai-advanced-details { margin-top: 20px; border-top: 1px solid var(--background-modifier-border); padding-top: 15px; } .excali-ai-advanced-summary { cursor: pointer; color: var(--text-muted); font-size: 0.95em; font-weight: 500; user-select: none; display: flex; align-items: center; gap: 5px; margin-bottom: 10px; } .excali-ai-advanced-summary:hover { color: var(--text-normal); } .excali-ai-advanced-content { border-left: 2px solid var(--interactive-accent); padding-left: 15px; margin-bottom: 15px; margin-top: 10px; } .excali-ai-run-container { margin-top: auto; padding-top: 20px; display: flex; justify-content: flex-end; } .excali-ai-task-setting { flex-wrap: wrap; gap: 10px; } .excali-ai-task-setting .setting-item-info { min-width: 150px; flex: 1 1 auto; } .excali-ai-task-setting .setting-item-control { flex: 1 1 auto; justify-content: flex-start; } .excali-ai-advanced-summary > svg { transition: transform 0.15s ease-in-out; width: 16px; height: 16px; } .excali-ai-advanced-details[open] > .excali-ai-advanced-summary > svg { transform: rotate(90deg); } .excali-ai-advanced-summary::-webkit-details-marker, .excali-ai-advanced-summary::marker { display: none; /* Hide native marker */ } ` }); const headerContainer = contentEl.createDiv({ cls: "excali-ai-header" }); headerContainer.innerHTML = `${ea.obsidian.getIcon("bot").outerHTML} <h2>ExcaliAI</h2>`; const mainContainer = contentEl.createDiv({ cls: "excali-ai-main-container" }); const leftCol = mainContainer.createDiv({ cls: "excali-ai-left-col" }); const rightCol = mainContainer.createDiv({ cls: "excali-ai-right-col" }); // --- WARNINGS --- const mmbWarning = leftCol.createDiv({ cls: "excali-ai-warning" }); mmbWarning.innerHTML = `${ea.obsidian.getIcon("alert-triangle").outerHTML} <span><b>MindMap Builder API is not active.</b> The "Create Mindmap" task requires it to be running.</span>`; mmbWarning.style.display = "none"; ``` -------------------------------- ### Install Fixed Horizontal Distance script Source: https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/docs/AITrainingData/Excalidraw Script Library.md Installs a script to arrange selected elements with a uniform horizontal distance between their centers. ```excalidraw-script-install https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20horizontal%20distance%20between%20centers.md ```