vault backup: 2026-09-15 18:32:01

This commit is contained in:
2026-09-15 18:32:01 -05:00
commit 5c1933aaa9
171 changed files with 276502 additions and 0 deletions
@@ -0,0 +1,72 @@
---
excalidraw-plugin: parsed
tags: [excalidraw]
---
==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== You can decompress Drawing data with the command palette: 'Decompress current Excalidraw file'. For more info check in plugin settings under 'Saving'
# Excalidraw Data
## Text Elements
%%
## Drawing
```compressed-json
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=
```
%%
@@ -0,0 +1,106 @@
---
excalidraw-plugin: parsed
tags: [excalidraw]
---
==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== You can decompress Drawing data with the command palette: 'Decompress current Excalidraw file'. For more info check in plugin settings under 'Saving'
# Excalidraw Data
## Text Elements
%%
## Drawing
```compressed-json
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
ioKBsAErAgBKwQAA
```
%%
@@ -0,0 +1,34 @@
---
excalidraw-plugin: parsed
tags: [excalidraw]
---
==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== You can decompress Drawing data with the command palette: 'Decompress current Excalidraw file'. For more info check in plugin settings under 'Saving'
# Excalidraw Data
## Text Elements
%%
## Drawing
```compressed-json
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
```
%%
@@ -0,0 +1,14 @@
---
excalidraw-plugin: parsed
tags: [excalidraw]
---
==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== You can decompress Drawing data with the command palette: 'Decompress current Excalidraw file'. For more info check in plugin settings under 'Saving'
## Drawing
```compressed-json
N4IgLgngDgpiBcIYA8DGBDANgSwCYCd0B3EAGhADcZ8BnbAewDsEAmcm+gV31TkQAswYKDXgB6MQHNsYfpwBGAOlT0AtmIBeNCtlQbs6RmPry6uA4wC0KDDgLFLUTJ2lH8MTDHQ0YNMWHRJMRZFFhCABjIkT1UYRjAaBABtAF1ydCgoAGUAsD5QSXw8LOwNPkZOTExyHRgiACF0VABrQq5GXABhekx6fAQQAGIAM1GxkABfCaA==
```
%%
@@ -0,0 +1,14 @@
---
excalidraw-plugin: parsed
tags: [excalidraw]
---
==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== You can decompress Drawing data with the command palette: 'Decompress current Excalidraw file'. For more info check in plugin settings under 'Saving'
## Drawing
```compressed-json
N4IgLgngDgpiBcIYA8DGBDANgSwCYCd0B3EAGhADcZ8BnbAewDsEAmcm+gV31TkQAswYKDXgB6MQHNsYfpwBGAOlT0AtmIBeNCtlQbs6RmPry6uA4wC0KDDgLFLUTJ2lH8MTDHQ0YNMWHRJMRZFFhCABjIkT1UYRjAaBABtAF1ydCgoAGUAsD5QSXw8LOwNPkZOTExyHRgiACF0VABrQq5GXABhekx6fAQQAGIAM1GxkABfCaA==
```
%%
@@ -0,0 +1,132 @@
---
excalidraw-plugin: parsed
tags: [excalidraw]
---
==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== You can decompress Drawing data with the command palette: 'Decompress current Excalidraw file'. For more info check in plugin settings under 'Saving'
# Excalidraw Data
## Text Elements
%%
## Drawing
```compressed-json
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=
```
%%
@@ -0,0 +1,38 @@
---
excalidraw-plugin: parsed
tags: [excalidraw]
---
==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== You can decompress Drawing data with the command palette: 'Decompress current Excalidraw file'. For more info check in plugin settings under 'Saving'
# Excalidraw Data
## Text Elements
%%
## Drawing
```compressed-json
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
```
%%
@@ -0,0 +1,28 @@
---
excalidraw-plugin: parsed
tags: [excalidraw]
---
==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== You can decompress Drawing data with the command palette: 'Decompress current Excalidraw file'. For more info check in plugin settings under 'Saving'
# Excalidraw Data
## Text Elements
%%
## Drawing
```compressed-json
N4KAkARALgngDgUwgLgAQQQDwMYEMA2AlgCYBOuA7hADTgQBuCpAzoQPYB2KqATLZMzYBXUtiRoIACyhQ4zZAHoFAc0JRJQgEYA6bGwC2CgF7N6hbEcK4OCtptbErHALRY8RMpWdx8Q1TdIEfARcZgRmBShcZQUebR44gAYaOiCEfQQOKGZuAG1wMFAwYogSbggAdQANegqoAHYAMQBxFOLIWERywOwojmVgtpLMbmcAZgBGCf4SmFGAFgBWMZnI
ChJ1bjH6+dWpBEJlaW4JngntRb3rAfFURL3mKFI2AGsEAGE2fDZScoBiCYIQGAoaQTS4bAvZTPIQcYifb6/CRPazMOC4QJZUEQABmhHw+AAyrBBhJBB5sY9nm8KhtJNw+AUBE9XghiTBSehyWU9jCjhxwjk0NMmRA2OjsGo5sLEvdRdDhHAAJLEIWoXIAXT2OPIGRV3A4QgJe0IcKw5VwyV5wjhAuYaqK7Wg8FuYyZAF8HggEMQtjwABxjf2JeqJ
S6ixgsdhcNAANkZTsjrE4ADlOGITrHZfUpgBOWO5k3MAAiaSgPu4TyECD2mhtxAAosEMlk1YbjaKhHBiLhy77hfV/YPtvMg0GRU6iBwXgajfg9t9IRW0DiCGECu7wFq6Lg4HBib3bo7oJJ0rcIEQjlAhgxCAgKAAhCFQmFwhE/f44r/fm/YESYqAlXLfRiWpD4vg/CQASBGDf3/TJAOAp9IQVWF4QgpF0BRDg0QxBC4NIACgPSRp8SJElz25X0Zg
gP9CIQ4iQJZGk6QZGi6KI4DQNZdlOQgKiCM49IACVhH5QUTnY+CskYgB5CUpROWUpPomTgMaTgoEaXB9HxaVUHDEoOIY9TNMJQgjFuHg5SM6TEPSAAVLAoAAQUvGN0GCHFrxUoSmIxVzCLYChT1wftUHbecClouzGIbOEXKCkKQnCiAMWeKhfJM9JEoyhyXW6etBOy/RGl1BBRM5SKaOYbBngJKpuHmAMarqr58AATQZBILhoow2AMbhHUgegCGr
E4NyytSRPrO01QgV9qOi6ESHMyyGRsyAVuIYkEDgbhDK20gSAAWTYYgEHi3BNGCcLV3wMIaO299MOGiAHy+VLSGUcEAAoznqaheAmQHgdBxILgASmxYSEGUI0MW6H7cH+sZ7l4NGgZ4TG7ihiBJui4ysm4t55KgaM2znGidR0irMgu47+iG0VMmu27K1Ias9mwIh9rQKsa1FDhaY5rnRWEKAp1uAW9n0DE3lIFMRf5znBadOXSAVq6buXVABYJko
7AAKwQXpmEJYW4DOi7tfZlc1zVyAIXJxgHIG/BmadTpKLSXpo2xP9HgMfKujQarRUXN47od7VnhA33yc4bh7seiPQlcv3Xfd2cCQNyBHGYNnwKyZyTsyIRk5j6LNFNRx+gABUCHEmEyDMJFZnWlpKQhmAfWvTWUW3dZl6Ke5Okg4DYU0oAt3c4AHofwpHko60wQkE+ja3zz0LJcFNfGwE9Og8WCB1N3dIA==
```
%%
@@ -0,0 +1,38 @@
---
excalidraw-plugin: parsed
tags: [excalidraw]
---
==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== You can decompress Drawing data with the command palette: 'Decompress current Excalidraw file'. For more info check in plugin settings under 'Saving'
# Excalidraw Data
## Text Elements
%%
## Drawing
```compressed-json
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=
```
%%
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-map-plus"><path d="m11 19-1.106-.552a2 2 0 0 0-1.788 0l-3.659 1.83A1 1 0 0 1 3 19.381V6.618a1 1 0 0 1 .553-.894l4.553-2.277a2 2 0 0 1 1.788 0l4.212 2.106a2 2 0 0 0 1.788 0l3.659-1.83A1 1 0 0 1 21 4.619V12"/><path d="M15 5.764V12"/><path d="M18 15v6"/><path d="M21 18h-6"/><path d="M9 3.236v15"/></svg>

After

Width:  |  Height:  |  Size: 505 B

@@ -0,0 +1,113 @@
/*
![](https://github.com/xllowl/obsidian-excalidraw-plugin/blob/master/images/mindmap%20connector.png)
![](https://github.com/xllowl/obsidian-excalidraw-plugin/blob/master/images/Mindmap%20connector1.png)
This script creates mindmap like lines(only right and down side are available). The line will starts according to the creation time of the elements. So you may need to create the header element first.
```javascript
*/
const elements = ea.getViewSelectedElements();
ea.copyViewElementsToEAforEditing(elements);
groups = ea.getMaximumGroups(elements);
els=[];
elsx=[];
elsy=[];
for (i = 0, len =groups.length; i < len; i++) {
els.push(ea.getLargestElement(groups[i]));
elsx.push(ea.getLargestElement(groups[i]).x);
elsy.push(ea.getLargestElement(groups[i]).y);
}
//line style setting
ea.style.strokeColor = els[0].strokeColor;
ea.style.strokeWidth = els[0].strokeWidth;
ea.style.strokeStyle = els[0].strokeStyle;
ea.style.strokeSharpness = els[0].strokeSharpness;
//all min max x y
let maxy = Math.max.apply(null, elsy);
let indexmaxy=elsy.indexOf(maxy);
let miny = Math.min.apply(null, elsy);
let indexminy = elsy.indexOf(miny);
let maxx = Math.max.apply(null, elsx);
let indexmaxx = elsx.indexOf(maxx);
let minx = Math.min.apply(null, elsx);
let indexminx = elsx.indexOf(minx);
//child max min x y
let gmaxy = Math.max.apply(null, elsy.slice(1));
let gindexmaxy=elsy.indexOf(gmaxy);
let gminy = Math.min.apply(null, elsy.slice(1));
let gindexminy = elsy.indexOf(gminy);
let gmaxx = Math.max.apply(null, elsx.slice(1));
let gindexmaxx = elsx.indexOf(gmaxx);
let gminx = Math.min.apply(null, elsx.slice(1));
let gindexminx = elsx.indexOf(gminx);
let s=0;//Set line direction down as default
if (indexminx==0 && els[0].x + els[0].width<=gminx) {
s=1;
}
else if (indexminy == 0) {
s=0;
}
var length_left;
if(els[0].x + els[0].width * 2<=gminx){length_left=els[0].x + els[0].width * 1.5;}
else {length_left=(els[0].x + els[0].width+gminx)/2;}
var length_down;
if(els[0].y + els[0].height* 2.5<=gminy){length_down=els[0].y + els[0].height * 2;}
else {length_down=(els[0].y + els[0].height+gminy)/2;}
if(s) {
ea.addLine(
[[length_left,
maxy + els[indexmaxy].height / 2],
[length_left,
miny + els[indexminy].height / 2]]
);
for (i = 1, len = groups.length; i < len; i++) {
ea.addLine(
[[els[i].x,
els[i].y + els[i].height/2],
[length_left,
els[i].y + els[i].height/2]]
);
}
ea.addArrow(
[[els[0].x+els[0].width,
els[0].y + els[0].height / 2],
[length_left,
els[0].y + els[0].height / 2]],
{
startArrowHead: "none",
endArrowHead: "dot"
}
)
}
else {
ea.addLine(
[[maxx + els[indexmaxx].width / 2,
length_down],
[minx + els[indexminx].width / 2,
length_down]]
);
for (i = 1, len = groups.length; i < len; i++) {
ea.addLine(
[[els[i].x + els[i].width / 2,
els[i].y],
[els[i].x + els[i].width / 2,
length_down]]
);
}
ea.addArrow(
[[els[0].x + els[0].width / 2,
els[0].y + els[0].height],
[els[0].x + els[0].width / 2,
length_down]],
{
startArrowHead: "none",
endArrowHead: "dot"
}
);
}
await ea.addElementsToView(false,false,true);
@@ -0,0 +1,12 @@
<svg width="607" height="541" viewBox="0 0 607 541" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M280 43.355V253.355H140V206.687L0 206.691V346.691H140V300.023H280V510.023C280 522.925 290.453 533.355 303.332 533.355H490.002C502.904 533.355 513.334 522.925 513.334 510.023C513.334 497.144 502.904 486.691 490.002 486.691H326.672V300.021H490.002C502.904 300.021 513.334 289.568 513.334 276.689C513.334 263.81 502.904 253.357 490.002 253.357H326.672V66.6869H490.002C502.904 66.6869 513.334 56.2569 513.334 43.3549C513.334 30.4529 502.904 20.0229 490.002 20.0229H303.332C290.453 20.019 280 30.4489 280 43.3509V43.355ZM46.67 300.025V253.357H93.338V300.025H46.67Z" fill="black"/>
<rect x="540" y="23" width="39" height="39" fill="#D9D9D9"/>
<rect x="503" width="104" height="95" fill="#D9D9D9"/>
<rect x="503.5" y="0.5" width="103" height="94" fill="black" stroke="black"/>
<rect x="503" y="223" width="104" height="95" fill="black"/>
<rect x="503.5" y="446.5" width="103" height="94" fill="black" stroke="black"/>
<path d="M532 243H580V291H532V243Z" fill="white"/>
<path d="M532 475H580V523H532V475Z" fill="white"/>
<path d="M532 243H580V291H532V243Z" fill="white"/>
<path d="M532 23H580V71H532V23Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,370 @@
/*
format **the left to right** mind map
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-mindmap-format-1.png)
# tree
Mind map is actually a tree, so you must have a **root node**. The script will determine **the leftmost element** of the selected element as the root element (node is excalidraw element, e.g. rectangle, diamond, ellipse, text, image, but it can't be arrow, line, freedraw, **group**)
The element connecting node and node must be an **arrow** and have the correct direction, e.g. **parent node -> children node**
# sort
The order of nodes in the Y axis or vertical direction is determined by **the creation time** of the arrow connecting it
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-mindmap-format-2.png)
So if you want to readjust the order, you can **delete arrows and reconnect them**
# setting
Script provides options to adjust the style of mind map, The option is at the bottom of the option of the exalidraw plugin(e.g. Settings -> Community plugins -> Excalidraw -> drag to bottom)
# problem
1. since the start bingding and end bingding of the arrow are easily disconnected from the node, so if there are unformatted parts, please **check the connection** and use the script to **reformat**
```javascript
*/
let settings = ea.getScriptSettings();
//set default values on first run
if (!settings["MindMap Format"]) {
settings = {
"MindMap Format": {
value: "Excalidraw/MindMap Format",
description:
"This is prepared for the namespace of MindMap Format and does not need to be modified",
},
"default gap": {
value: 10,
description: "Interval size of element",
},
"curve length": {
value: 40,
description: "The length of the curve part in the mind map line",
},
"length between element and line": {
value: 50,
description:
"The distance between the tail of the connection and the connecting elements of the mind map",
},
};
ea.setScriptSettings(settings);
}
const sceneElements = ea.getExcalidrawAPI().getSceneElements();
// default X coordinate of the middle point of the arc
const defaultDotX = Number(settings["curve length"].value);
// The default length from the middle point of the arc on the X axis
const defaultLengthWithCenterDot = Number(
settings["length between element and line"].value
);
// Initial trimming distance of the end point on the Y axis
const initAdjLength = 4;
// default gap
const defaultGap = Number(settings["default gap"].value);
const setCenter = (parent, line) => {
// Focus and gap need the api calculation of excalidraw
// e.g. determineFocusDistance, but they are not available now
// so they are uniformly set to 0/1
line.startBinding.focus = 0;
line.startBinding.gap = 1;
line.endBinding.focus = 0;
line.endBinding.gap = 1;
line.x = parent.x + parent.width;
line.y = parent.y + parent.height / 2;
};
/**
* set the middle point of curve
* @param {any} lineEl the line element of excalidraw
* @param {number} height height of dot on Y axis
* @param {number} [ratio=1] coefficient of the initial trimming distance of the end point on the Y axis, default is 1
*/
const setTopCurveDotOnLine = (lineEl, height, ratio = 1) => {
if (lineEl.points.length < 3) {
lineEl.points.splice(1, 0, [defaultDotX, lineEl.points[0][1] - height]);
} else if (lineEl.points.length === 3) {
lineEl.points[1] = [defaultDotX, lineEl.points[0][1] - height];
} else {
lineEl.points.splice(2, lineEl.points.length - 3);
lineEl.points[1] = [defaultDotX, lineEl.points[0][1] - height];
}
lineEl.points[2][0] = lineEl.points[1][0] + defaultLengthWithCenterDot;
// adjust the curvature of the second line segment
lineEl.points[2][1] = lineEl.points[1][1] - initAdjLength * ratio * 0.8;
};
const setMidCurveDotOnLine = (lineEl) => {
if (lineEl.points.length < 3) {
lineEl.points.splice(1, 0, [defaultDotX, lineEl.points[0][1]]);
} else if (lineEl.points.length === 3) {
lineEl.points[1] = [defaultDotX, lineEl.points[0][1]];
} else {
lineEl.points.splice(2, lineEl.points.length - 3);
lineEl.points[1] = [defaultDotX, lineEl.points[0][1]];
}
lineEl.points[2][0] = lineEl.points[1][0] + defaultLengthWithCenterDot;
lineEl.points[2][1] = lineEl.points[1][1];
};
/**
* set the middle point of curve
* @param {any} lineEl the line element of excalidraw
* @param {number} height height of dot on Y axis
* @param {number} [ratio=1] coefficient of the initial trimming distance of the end point on the Y axis, default is 1
*/
const setBottomCurveDotOnLine = (lineEl, height, ratio = 1) => {
if (lineEl.points.length < 3) {
lineEl.points.splice(1, 0, [defaultDotX, lineEl.points[0][1] + height]);
} else if (lineEl.points.length === 3) {
lineEl.points[1] = [defaultDotX, lineEl.points[0][1] + height];
} else {
lineEl.points.splice(2, lineEl.points.length - 3);
lineEl.points[1] = [defaultDotX, lineEl.points[0][1] + height];
}
lineEl.points[2][0] = lineEl.points[1][0] + defaultLengthWithCenterDot;
// adjust the curvature of the second line segment
lineEl.points[2][1] = lineEl.points[1][1] + initAdjLength * ratio * 0.8;
};
const setTextXY = (rect, text) => {
text.x = rect.x + (rect.width - text.width) / 2;
text.y = rect.y + (rect.height - text.height) / 2;
};
const setChildrenXY = (parent, children, line, elementsMap) => {
x = parent.x + parent.width + line.points[2][0];
y = parent.y + parent.height / 2 + line.points[2][1] - children.height / 2;
distX = children.x - x;
distY = children.y - y;
ea.getElementsInTheSameGroupWithElement(children, sceneElements).forEach((el) => {
el.x = el.x - distX;
el.y = el.y - distY;
});
if (
["rectangle", "diamond", "ellipse"].includes(children.type) &&
![null, undefined].includes(children.boundElements)
) {
const textDesc = children.boundElements.filter(
(el) => el.type === "text"
)[0];
if (textDesc !== undefined) {
const textEl = elementsMap.get(textDesc.id);
setTextXY(children, textEl);
}
}
};
/**
* returns the height of the upper part of all child nodes
* and the height of the lower part of all child nodes
* @param {Number[]} childrenTotalHeightArr
* @returns {Number[]} [topHeight, bottomHeight]
*/
const getNodeCurrentHeight = (childrenTotalHeightArr) => {
if (childrenTotalHeightArr.length <= 0) return [0, 0];
else if (childrenTotalHeightArr.length === 1)
return [childrenTotalHeightArr[0] / 2, childrenTotalHeightArr[0] / 2];
const heightArr = childrenTotalHeightArr;
let topHeight = 0,
bottomHeight = 0;
const isEven = heightArr.length % 2 === 0;
const mid = Math.floor(heightArr.length / 2);
const topI = mid - 1;
const bottomI = isEven ? mid : mid + 1;
topHeight = isEven ? 0 : heightArr[mid] / 2;
for (let i = topI; i >= 0; i--) {
topHeight += heightArr[i];
}
bottomHeight = isEven ? 0 : heightArr[mid] / 2;
for (let i = bottomI; i < heightArr.length; i++) {
bottomHeight += heightArr[i];
}
return [topHeight, bottomHeight];
};
/**
* handle the height of each point in the single-level tree
* @param {Array} lines
* @param {Map} elementsMap
* @param {Boolean} isEven
* @param {Number} mid 'lines' array midpoint index
* @returns {Array} height array corresponding to 'lines'
*/
const handleDotYValue = (lines, elementsMap, isEven, mid) => {
const getTotalHeight = (line, elementsMap) => {
return elementsMap.get(line.endBinding.elementId).totalHeight;
};
const getTopHeight = (line, elementsMap) => {
return elementsMap.get(line.endBinding.elementId).topHeight;
};
const getBottomHeight = (line, elementsMap) => {
return elementsMap.get(line.endBinding.elementId).bottomHeight;
};
const heightArr = new Array(lines.length).fill(0);
const upI = mid === 0 ? 0 : mid - 1;
const bottomI = isEven ? mid : mid + 1;
let initHeight = isEven ? 0 : getTopHeight(lines[mid], elementsMap);
for (let i = upI; i >= 0; i--) {
heightArr[i] = initHeight + getBottomHeight(lines[i], elementsMap);
initHeight += getTotalHeight(lines[i], elementsMap);
}
initHeight = isEven ? 0 : getBottomHeight(lines[mid], elementsMap);
for (let i = bottomI; i < lines.length; i++) {
heightArr[i] = initHeight + getTopHeight(lines[i], elementsMap);
initHeight += getTotalHeight(lines[i], elementsMap);
}
return heightArr;
};
/**
* format single-level tree
* @param {any} parent
* @param {Array} lines
* @param {Map} childrenDescMap
* @param {Map} elementsMap
*/
const formatTree = (parent, lines, childrenDescMap, elementsMap) => {
lines.forEach((item) => setCenter(parent, item));
const isEven = lines.length % 2 === 0;
const mid = Math.floor(lines.length / 2);
const heightArr = handleDotYValue(lines, childrenDescMap, isEven, mid);
lines.forEach((item, index) => {
if (isEven) {
if (index < mid) setTopCurveDotOnLine(item, heightArr[index], index + 1);
else setBottomCurveDotOnLine(item, heightArr[index], index - mid + 1);
} else {
if (index < mid) setTopCurveDotOnLine(item, heightArr[index], index + 1);
else if (index === mid) setMidCurveDotOnLine(item);
else setBottomCurveDotOnLine(item, heightArr[index], index - mid);
}
});
lines.forEach((item) => {
if (item.endBinding !== null) {
setChildrenXY(
parent,
elementsMap.get(item.endBinding.elementId),
item,
elementsMap
);
}
});
};
const generateTree = (elements) => {
const elIdMap = new Map([[elements[0].id, elements[0]]]);
let minXEl = elements[0];
for (let i = 1; i < elements.length; i++) {
elIdMap.set(elements[i].id, elements[i]);
if (
!(elements[i].type === "arrow" || elements[i].type === "line") &&
elements[i].x < minXEl.x
) {
minXEl = elements[i];
}
}
const root = {
el: minXEl,
totalHeight: minXEl.height,
topHeight: 0,
bottomHeight: 0,
linkChildrensLines: [],
isLeafNode: false,
children: [],
};
const preIdSet = new Set(); // The id_set of Elements that is already in the tree, avoid a dead cycle
const dfsForTreeData = (root) => {
if (preIdSet.has(root.el.id)) {
return 0;
}
preIdSet.add(root.el.id);
let lines = root.el.boundElements.filter(
(el) =>
el.type === "arrow" &&
!preIdSet.has(el.id) &&
elIdMap.get(el.id)?.startBinding?.elementId === root.el.id
);
if (lines.length === 0) {
root.isLeafNode = true;
root.totalHeight = root.el.height + 2 * defaultGap;
[root.topHeight, root.bottomHeight] = [
root.totalHeight / 2,
root.totalHeight / 2,
];
return root.totalHeight;
} else {
lines = lines.map((elementDesc) => {
preIdSet.add(elementDesc.id);
return elIdMap.get(elementDesc.id);
});
}
const linkChildrensLines = [];
lines.forEach((el) => {
const line = el;
if (
line &&
line.endBinding !== null &&
line.endBinding !== undefined &&
!preIdSet.has(elIdMap.get(line.endBinding.elementId).id)
) {
const children = elIdMap.get(line.endBinding.elementId);
linkChildrensLines.push(line);
root.children.push({
el: children,
totalHeight: 0,
topHeight: 0,
bottomHeight: 0,
linkChildrensLines: [],
isLeafNode: false,
children: [],
});
}
});
let totalHeight = 0;
root.children.forEach((el) => (totalHeight += dfsForTreeData(el)));
root.linkChildrensLines = linkChildrensLines;
if (root.children.length === 0) {
root.isLeafNode = true;
root.totalHeight = root.el.height + 2 * defaultGap;
[root.topHeight, root.bottomHeight] = [
root.totalHeight / 2,
root.totalHeight / 2,
];
} else if (root.children.length > 0) {
root.totalHeight = Math.max(root.el.height + 2 * defaultGap, totalHeight);
[root.topHeight, root.bottomHeight] = getNodeCurrentHeight(
root.children.map((item) => item.totalHeight)
);
}
return totalHeight;
};
dfsForTreeData(root);
const dfsForFormat = (root) => {
if (root.isLeafNode) return;
const childrenDescMap = new Map(
root.children.map((item) => [item.el.id, item])
);
formatTree(root.el, root.linkChildrensLines, childrenDescMap, elIdMap);
root.children.forEach((el) => dfsForFormat(el));
};
dfsForFormat(root);
};
const elements = ea.getViewSelectedElements();
generateTree(elements);
ea.copyViewElementsToEAforEditing(elements);
await ea.addElementsToView(false, false);
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1673428425027" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1642" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24"><path d="M388.7 542.88c-16.57 0-30-13.43-30-30s13.43-30 30-30c52.3 0 94.85-42.55 94.85-94.85v-67.81c0-40.96 15.84-79.58 44.6-108.74 28.76-29.16 67.16-45.53 108.12-46.1l3.43-0.05c16.57-0.22 30.18 13.02 30.41 29.58 0.23 16.57-13.02 30.18-29.58 30.41l-3.43 0.05c-51.58 0.71-93.55 43.25-93.55 94.84v67.81c0 85.4-69.47 154.86-154.85 154.86z" fill="#000000" p-id="1643"></path><path d="M640.12 860.42h-0.42l-3.43-0.05c-40.96-0.56-79.36-16.93-108.12-46.09s-44.6-67.78-44.6-108.74v-67.8c0-52.3-42.55-94.85-94.85-94.85-16.57 0-30-13.43-30-30s13.43-30 30-30c85.38 0 154.85 69.47 154.85 154.85v67.8c0 51.59 41.96 94.13 93.55 94.84l3.43 0.05c16.57 0.23 29.81 13.84 29.59 30.41-0.24 16.42-13.62 29.58-30 29.58z" fill="#000000" p-id="1644"></path><path d="M640.11 542.88H388.7c-16.57 0-30-13.43-30-30s13.43-30 30-30h251.42c16.57 0 30 13.43 30 30-0.01 16.57-13.44 30-30.01 30z" fill="#000000" p-id="1645"></path><path d="M343.89 638.95H137.78c-38.6 0-70-31.4-70-70V456.81c0-38.6 31.4-70 70-70h206.11c38.6 0 70 31.4 70 70v112.13c0 38.6-31.4 70.01-70 70.01zM137.78 446.81c-5.51 0-10 4.49-10 10v112.13c0 5.51 4.49 10 10 10h206.11c5.51 0 10-4.49 10-10V456.81c0-5.51-4.49-10-10-10H137.78zM830.16 316.96h-93.98c-69.51 0-126.07-56.55-126.07-126.07S666.66 64.83 736.18 64.83h93.98c69.51 0 126.07 56.55 126.07 126.07-0.01 69.5-56.56 126.06-126.07 126.06z m-93.98-192.13c-36.43 0-66.07 29.64-66.07 66.07s29.64 66.07 66.07 66.07h93.98c36.43 0 66.07-29.64 66.07-66.07s-29.64-66.07-66.07-66.07h-93.98zM830.16 638.95h-93.98c-69.51 0-126.07-56.55-126.07-126.07 0-69.51 56.55-126.07 126.07-126.07h93.98c69.51 0 126.07 56.55 126.07 126.07-0.01 69.51-56.56 126.07-126.07 126.07z m-93.98-192.14c-36.43 0-66.07 29.64-66.07 66.07 0 36.43 29.64 66.07 66.07 66.07h93.98c36.43 0 66.07-29.64 66.07-66.07 0-36.43-29.64-66.07-66.07-66.07h-93.98z" fill="#000000" p-id="1646"></path><path d="M830.16 959.17h-93.98c-69.51 0-126.07-56.55-126.07-126.07s56.55-126.07 126.07-126.07h93.98c69.51 0 126.07 56.55 126.07 126.07s-56.56 126.07-126.07 126.07z m-93.98-192.13c-36.43 0-66.07 29.64-66.07 66.07s29.64 66.07 66.07 66.07h93.98c36.43 0 66.07-29.64 66.07-66.07s-29.64-66.07-66.07-66.07h-93.98z" fill="#000000" p-id="1647"></path></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

@@ -0,0 +1,36 @@
/*
Copies the text from the selected PDF page on the Excalidraw canvas to the clipboard.
<a href="https://www.youtube.com/watch?v=Kwt_8WdOUT4" target="_blank"><img src ="https://i.ytimg.com/vi/Kwt_8WdOUT4/maxresdefault.jpg" style="width:560px;"></a>
```js*/
const el = ea.getViewSelectedElements().filter(el=>el.type==="image")[0];
if(!el) {
new Notice("Select a PDF page");
return;
}
const f = ea.getViewFileForImageElement(el);
if(f.extension.toLowerCase() !== "pdf") {
new Notice("Select a PDF page");
return;
}
const pageNum = parseInt(ea.targetView.excalidrawData.getFile(el.fileId).linkParts.ref.replace(/\D/g, ""));
if(isNaN(pageNum)) {
new Notice("Can't find page number");
return;
}
const pdfDoc = await window.pdfjsLib.getDocument(app.vault.getResourcePath(f)).promise;
const page = await pdfDoc.getPage(pageNum);
const text = await page.getTextContent();
if(!text) {
new Notice("Could not get text");
return;
}
pdfDoc.destroy();
window.navigator.clipboard.writeText(
text.items.reduce((acc, cur) => acc + cur.str.replace(/\x00/ug, '') + (cur.hasEOL ? "\n" : ""),"")
);
new Notice("Page text is available on the clipboard");
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-file-text"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/><path d="M14 2v5a1 1 0 0 0 1 1h5"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/></svg>

After

Width:  |  Height:  |  Size: 437 B

+244
View File
@@ -0,0 +1,244 @@
/*
Palm Guard: A mobile-friendly drawing mode for Excalidraw that prevents accidental palm touches by hiding UI controls and entering fullscreen mode. Perfect for drawing with a stylus on tablets.
Features:
- Enters fullscreen to maximize drawing space (configurable in plugin script settings)
- Hides all UI controls to prevent accidental taps
- Provides a minimal floating toolbar with toggle visibility button
- Enables a completely distraction-free canvas even on desktop devices by hiding the main toolbar and all chrome while keeping a tiny movable toggle control (addresses immersive canvas / beyond Zen Mode request)
- Draggable toolbar can be positioned anywhere on screen
- Exit Palm Guard mode with a single tap
- Press the hotkey you configured for this script in Obsidian's Hotkey settings (e.g., ALT+X) to toggle UI visibility; if no hotkey is set, use the on-screen toggle button.
![Palm Guard Script](https://youtu.be/A_udjVjgWN0)
```js
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.14.2")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
function requestFullscreen() {
const el = ea.targetView.ownerDocument.body;
if (el.requestFullscreen) {
el.requestFullscreen();
} else if (el.webkitRequestFullscreen) {
el.webkitRequestFullscreen();
}
ea.targetView.gotoFullscreen();
}
function exitFullscreen() {
const doc = ea.targetView.ownerDocument;
if (doc.exitFullscreen) {
doc.exitFullscreen();
} else if (doc.webkitExitFullscreen) {
doc.webkitExitFullscreen();
}
ea.targetView.exitFullscreen();
}
async function run() {
if(window.excalidrawPalmGuard) {
window.excalidrawPalmGuard()
return;
}
const modal = new ea.FloatingModal(ea.plugin.app);
if (modal.bgEl) {
modal.containerEl.removeChild(modal.bgEl);
}
modal.modalEl.style.borderRadius = "6px";
modal.contentEl.style.padding = "4px";
const FULLSCREEN = "Goto fullscreen?";
let settings = ea.getScriptSettings() || {};
if(!settings[FULLSCREEN]) {
settings[FULLSCREEN] = { value: true };
await ea.setScriptSettings(settings);
}
//added only to clean up settings if someone installed the initial version of the script
const HOTKEY_MODIFIERS = "PalmGuard Toggle UI Hotkey Modifiers";
const HOTKEY_KEY = "PalmGuard Toggle UI Hotkey Key";
if(settings[HOTKEY_MODIFIERS] || settings[HOTKEY_KEY]) {
delete settings[HOTKEY_MODIFIERS];
delete settings[HOTKEY_KEY];
await ea.setScriptSettings(settings);
}
const enableFullscreen = settings[FULLSCREEN].value;
// Initialize state
let uiHidden = true;
let currentIcon = "eye";
let layerUIWrapper = ea.targetView.contentEl.querySelector(".excalidraw.excalidraw-container > .layer-ui__wrapper");
const toolbar = ea.targetView.contentEl.querySelector(".excalidraw > .Island");
let toolbarActive = toolbar?.style.display === "block";
let prevHiddenState = false;
// Function to toggle UI visibility
const toggleUIVisibility = (hidden) => {
if(hidden === prevHiddenState) return hidden;
prevHiddenState = hidden;
if (!!layerUIWrapper) {
try {
if(hidden) {
layerUIWrapper.style.display = "none";
} else {
layerUIWrapper.style.display = "block";
}
} catch {};
} else {
try{
const topBar = ea.targetView.containerEl.querySelector(".App-top-bar");
const bottomBar = ea.targetView.containerEl.querySelector(".App-bottom-bar");
const sidebarToggle = ea.targetView.containerEl.querySelector(".sidebar-toggle");
const plugins = ea.targetView.containerEl.querySelector(".plugins-container");
if(hidden) {
if (toolbarActive && (toolbar?.style.display === "none")) {
toolbarActive = false;
}
if (toolbarActive = toolbar?.style.display === "block") {
toolbarActive = true;
};
}
const display = hidden ? "none" : "";
if (topBar) topBar.style.display = display;
if (bottomBar) bottomBar.style.display = display;
if (sidebarToggle) sidebarToggle.style.display = display;
if (plugins) plugins.style.display = display;
if (toolbarActive) toolbar.style.display = hidden ? "none" : "block";
modal.modalEl.style.opacity = hidden ? "0.4" : "0.8";
} catch {};
};
return hidden;
};
// Enter fullscreen view mode
if(enableFullscreen) {
requestFullscreen ();
}
setTimeout(()=>toggleUIVisibility(true),100);
// Create floating toolbar modal
Object.assign(modal.modalEl.style, {
width: "fit-content",
minWidth: "fit-content",
height: "fit-content",
minHeight: "fit-content",
paddingBottom: "4px",
paddingTop: "16px",
paddingRight: "4px",
paddingLeft: "4px"
});
modal.headerEl.style.display = "none";
// Configure modal
modal.titleEl.setText(""); // No title for minimal UI
// Create modal content
modal.contentEl.createDiv({ cls: "palm-guard-toolbar" }, div => {
const container = div.createDiv({
attr: {
style: "display: flex; flex-direction: column; background-color: var(--background-secondary); border-radius: 4px;"
}
});
// Button container
const buttonContainer = container.createDiv({
attr: {
style: "display: flex; flex-wrap: wrap; gap: 4px; justify-content: center;"
}
});
// Toggle UI visibility button
const toggleButton = buttonContainer.createEl("button", {
cls: "palm-guard-btn clickable-icon",
attr: {
style: "background-color: var(--interactive-accent); color: var(--text-on-accent);"
}
});
toggleButton.innerHTML = ea.obsidian.getIcon("eye").outerHTML;
// Keyboard hotkey listener (only acts if hotkey configured)
window.excalidrawPalmGuard = () => toggleButton.click();
toggleButton.addEventListener("click", () => {
uiHidden = !uiHidden;
toggleUIVisibility(uiHidden);
// Toggle icon
currentIcon = uiHidden ? "eye" : "eye-off";
toggleButton.innerHTML = ea.obsidian.getIcon(currentIcon).outerHTML;
});
// Exit button
const exitButton = buttonContainer.createEl("button", {
cls: "palm-guard-btn clickable-icon",
attr: {
style: "background-color: var(--background-secondary-alt); color: var(--text-normal);"
}
});
exitButton.innerHTML = ea.obsidian.getIcon("cross").outerHTML;
exitButton.addEventListener("click", () => {
modal.close();
});
// Add CSS
div.createEl("style", {
text: `
.palm-guard-btn:hover {
filter: brightness(1.1);
}
.modal-close-button {
display: none;
}
.palm-guard-btn {
display: flex;
justify-content: center;
align-items: center;
padding: 4px;
border-radius: 10%;
width: 2em;
height: 2em;
cursor: pointer;
}
`
});
});
const autocloseTimer = setInterval(()=>{
if(!ea.targetView) modal.close();
},1000);
// Handle modal close (exit Palm Guard mode)
modal.onClose = () => {
// Show all UI elements
toggleUIVisibility(false);
// Exit fullscreen
if(ea.targetView && enableFullscreen) {
exitFullscreen();
}
clearInterval(autocloseTimer);
delete window.excalidrawPalmGuard;
};
// Open the modal
modal.open();
// Position the modal in the top left initially
setTimeout(() => {
const modalEl = modal.modalEl;
const rect = ea.targetView.contentEl.getBoundingClientRect();
if (modalEl) {
modalEl.style.left = `${rect.left+10}px`;
modalEl.style.top = `${rect.top+10}px`;
}
}, 100);
}
run();
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-hand"><path d="M18 11V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2"/><path d="M14 10V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2v2"/><path d="M10 10.5V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2v8"/><path d="M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15"/></svg>

After

Width:  |  Height:  |  Size: 475 B

@@ -0,0 +1,864 @@
/*
Export Excalidraw to PDF Pages: Define printable page areas using frames, then export each frame as a separate page in a multi-page PDF. Perfect for turning your Excalidraw drawings into printable notes, handouts, or booklets. Supports standard and custom page sizes, margins, and easy frame arrangement.
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-layout-wizard-01.png)
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-layout-wizard-02.png)
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-layout-wizard-03.png)
![Marker Frames](https://youtu.be/DqDnzCOoYMc)
![Printable Layout Wizard](https://youtu.be/29EWeglRm7s)
```js
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.15.0")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
if(window.excalidrawPrintableLayoutWizardModal) {
window.excalidrawPrintableLayoutWizardModal.open();
return;
}
// Help text for the script
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).
![Marker Frames](https://youtu.be/DqDnzCOoYMc)
![Printable Layout Wizard](https://youtu.be/29EWeglRm7s)
`;
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;
// 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;
const getSortedFrames = () => {
return ea.getViewElements()
.filter(el => isEligibleFrame(el))
.sort((a, b) => {
const nameA = a.name || "";
const nameB = b.name || "";
return nameA.localeCompare(nameB);
});
};
// Find existing page frames and determine next page number
const findExistingPages = (selectLastFrame = false) => {
const frameElements = getSortedFrames();
// Extract page numbers from frame names
const pageNumbers = frameElements
.map(frame => {
const match = frame.name?.match(/(?:Page\s+)?(\d+)/i);
return match ? parseInt(match[1]) : 0;
})
.filter(num => !isNaN(num));
// Find the highest page number
const nextPageNumber = pageNumbers.length > 0
? Math.max(...pageNumbers) + 1
: 1;
if(selectLastFrame && frameElements.length > 0) {
ea.selectElementsInView([frameElements[frameElements.length-1]]);
}
return {
frames: frameElements,
nextPageNumber: nextPageNumber
};
};
const isEligibleFrame = (el) => el.type === "frame" && (printMarkersOnly ? el.frameRole === "marker" : true);
// Check if there are frames in the scene and if a frame is selected
let existingFrames = ea.getViewElements().filter(el => isEligibleFrame(el));
let selectedFrame = ea.getViewSelectedElements().find(el => isEligibleFrame(el));
const hasFrames = existingFrames.length > 0;
if(hasFrames && !selectedFrame) {
if(st.activeLockedId && existingFrames.find(f=>f.id === st.activeLockedId)) {
selectedFrame = existingFrames.find(f=>f.id === st.activeLockedId);
ea.viewUpdateScene({ appState: { activeLockedId: null }});
ea.selectElementsInView([selectedFrame]);
} else {
findExistingPages(true);
selectedFrame = ea.getViewSelectedElements().find(el => isEligibleFrame(el));
}
}
const hasSelectedFrame = !!selectedFrame;
// rotation is now a temporary UI state controlled by the center button
let rotateOnAdd = false;
let centerRotateBtn = null;
const setRotateBtnActive = (active) => {
if (!centerRotateBtn) return;
centerRotateBtn.classList.toggle("is-accent", active);
centerRotateBtn.setAttribute("aria-pressed", active ? "true" : "false");
};
// Show notice if there are frames but none selected
if (hasFrames && !hasSelectedFrame) {
new Notice("Select a frame before running the script", 7000);
return;
}
// Create the first frame
const createFirstFrame = async (pageSize, orientation) => {
// Use ExcalidrawAutomate's built-in function to get page dimensions
const dimensions = ea.getPagePDFDimensions(pageSize, orientation);
if (!dimensions) {
new Notice("Invalid page size selected");
return;
}
// Save settings when creating first frame
if (settings[PAGE_SIZE].value !== pageSize) {
settings[PAGE_SIZE].value = pageSize;
dirty = true;
}
if (settings[ORIENTATION].value !== orientation) {
settings[ORIENTATION].value = orientation;
dirty = true;
}
// Format page number with leading zero
const pageName = "01";
// Calculate position to center the frame
const appState = ea.getExcalidrawAPI().getAppState();
const x = (appState.width - dimensions.width) / 2;
const y = (appState.height - dimensions.height) / 2;
return await addFrameElement(x, y, dimensions.width, dimensions.height, pageName, true);
};
// Add new page frame
const addPage = async (direction, pageSize, orientation) => {
selectedFrame = ea.getViewSelectedElements().find(el => isEligibleFrame(el));
if (!selectedFrame) {
const { activeLockedId } = ea.getExcalidrawAPI().getAppState();
if(activeLockedId) {
selectedFrame = ea.getViewElements().find(el=>el.id === activeLockedId && isEligibleFrame(el));
}
if (!selectedFrame) return;
}
ea.viewUpdateScene({appState: {activeLockedId: null}});
const { frames, nextPageNumber } = findExistingPages();
// Get dimensions from selected frame, support optional rotation
const dimensions = {
width: rotateOnAdd ? selectedFrame.height : selectedFrame.width,
height: rotateOnAdd ? selectedFrame.width : selectedFrame.height
};
// Format page number with leading zero
const pageName = `${nextPageNumber.toString().padStart(2, '0')}`;
// Calculate position based on direction and selected frame
let x = 0;
let y = 0;
switch (direction) {
case "right":
x = selectedFrame.x + selectedFrame.width;
y = selectedFrame.y;
break;
case "left":
x = selectedFrame.x - dimensions.width;
y = selectedFrame.y;
break;
case "down":
x = selectedFrame.x;
y = selectedFrame.y + selectedFrame.height;
break;
case "up":
x = selectedFrame.x;
y = selectedFrame.y - dimensions.height;
break;
}
const added = await addFrameElement(x, y, dimensions.width, dimensions.height, pageName);
// reset the rotate toggle after adding the frame
rotateOnAdd = false;
setRotateBtnActive(false);
return added;
};
addFrameElement = async (x, y, width, height, pageName, repositionToCursor = false) => {
const frameId = ea.addFrame(x, y, width, height, pageName);
ea.getElement(frameId).frameRole = "marker";
if(lockFrame) {
ea.getElement(frameId).locked = true;
}
await ea.addElementsToView(repositionToCursor);
const addedFrame = ea.getViewElements().find(el => el.id === frameId);
if(shouldZoom) {
ea.viewZoomToElements(true, [addedFrame]);
} else {
ea.selectElementsInView([addedFrame]);
}
//ready for the next frame
ea.clear();
selectedFrame = addedFrame;
if(shouldClose) {
modal.close();
}
return addedFrame;
}
const translateToZero = ({ x, y, width, height }, padding=0) => {
const top = y, left = x, right = x + width, bottom = y + height;
const {topX, topY, width:w, height:h} = ea.getBoundingBox(ea.getViewElements());
const newTop = top - (topY - padding);
const newLeft = left - (topX - padding);
const newBottom = bottom - (topY - padding);
const newRight = right - (topX - padding);
return {
top: newTop,
left: newLeft,
bottom: newBottom,
right: newRight,
};
}
// NEW: detect if any non-frame element overlaps the given area
const hasElementsInArea = (area) => ea.getElementsInArea(ea.getViewElements(), area).length>0;
const checkFrameSizes = (frames) => {
if (frames.length <= 1) return true;
const referenceWidth = frames[0].width;
const referenceHeight = frames[0].height;
return frames.every(frame =>
Math.abs(frame.width - referenceWidth) < 1 &&
Math.abs(frame.height - referenceHeight) < 1
);
};
const printToPDF = async (marginSize) => {
const margin = MARGINS[marginSize] || 0;
// Save margin setting
if (settings[MARGIN].value !== marginSize) {
settings[MARGIN].value = marginSize;
dirty = true;
}
// Get all frame elements and sort by name
const frames = getSortedFrames();
if (frames.length === 0) {
new Notice("No frames found to print");
return;
}
// Create a notice during processing
const notice = new Notice("Preparing PDF, please wait...", 0);
// Create SVGs for each frame
const svgPages = [];
let placeholderRects = [];
ea.clear();
for (const frame of frames) {
ea.style.opacity = 0;
ea.style.roughness = 0;
ea.style.fillStyle = "solid";
ea.style.backgroundColor = "black"
ea.style.strokeWidth = 0.01;
ea.addRect(frame.x, frame.y, frame.width, frame.height);
}
const svgScene = await ea.createViewSVG({
withBackground: true,
theme: st.theme,
//frameRendering: { enabled: false, name: false, outline: false, clip: false },
padding: 0,
selectedOnly: false,
skipInliningFonts: false,
embedScene: false,
elementsOverride: ea.getViewElements().concat(ea.getElements()),
});
ea.clear();
for (const frame of frames) {
// NEW: skip empty frames unless user opted to print them
if(!printEmptyPages && !hasElementsInArea(frame)) continue;
const { top, left, bottom, right } = translateToZero(frame);
//always create the new SVG in the main Obsidian workspace (not the popout window, if present)
const host = window.createDiv();
host.innerHTML = svgScene.outerHTML;
const clonedSVG = host.firstElementChild;
const width = Math.abs(left-right);
const height = Math.abs(top-bottom);
clonedSVG.setAttribute("viewBox", `${left} ${top} ${width} ${height}`);
clonedSVG.setAttribute("width", `${width}`);
clonedSVG.setAttribute("height", `${height}`);
svgPages.push(clonedSVG);
}
// NEW: abort if nothing to print
if(svgPages.length === 0) {
notice.hide();
new Notice("No pages to print (all selected frames are empty)");
notice.hide();
return;
}
// Use dimensions from the first frame
const width = frames[0].width;
const height = frames[0].height;
// Create PDF
await ea.createPDF({
SVG: svgPages,
scale: { fitToPage: true },
pageProps: {
dimensions: {},
//dimensions: { width, height },
backgroundColor: "#ffffff",
margin: {
left: margin,
right: margin,
top: margin,
bottom: margin
},
alignment: "center"
},
filename: ea.targetView.file.basename + "-pages.pdf"
});
notice.hide();
};
// -----------------------
// Create a floating modal
// -----------------------
modal.titleEl.setText("Page Management");
modal.titleEl.style.textAlign = "center";
modal.onClose = async () => {
delete window.excalidrawPrintableLayoutWizardModal;
if (dirty) {
await ea.setScriptSettings(settings);
}
ea.viewUpdateScene({
appState: {
frameRendering: {enabled, clip, name, outline, markerName, markerEnabled}
}
});
if(shouldRestart) setTimeout(()=>run());
};
// Create modal content
modal.contentEl.createDiv({ cls: "excalidraw-page-manager" }, div => {
const container = div.createDiv({
attr: {
style: "display: flex; flex-direction: column; gap: 15px; padding: 10px;"
}
});
// Help section
const helpDiv = container.createDiv({
attr: {
style: "margin-bottom: 10px;"
}
});
helpDiv.createEl("details", {}, (details) => {
details.createEl("summary", {
text: "Help & Information",
attr: {
style: "cursor: pointer; font-weight: bold; margin-bottom: 10px;"
}
});
details.createEl("div", {
attr: {
style: "padding: 10px; border: 1px solid var(--background-modifier-border); border-radius: 4px; margin-top: 8px; font-size: 0.9em; max-height: 300px; overflow-y: auto;"
}
}, div => {
ea.obsidian.MarkdownRenderer.render(ea.plugin.app, HELP_TEXT, div, "", ea.plugin)
});
});
// Tabs (show only when frames exist)
let framesTabEl, printingTabEl, tabsHeaderEl, marginDropdown;
if (hasFrames) {
tabsHeaderEl = container.createDiv({
attr: { style: "display:flex; gap:8px; border-bottom:1px solid var(--background-modifier-border); padding-bottom:0;" }
});
tabsHeaderEl.addClass("tabs-header"); // NEW
const framesTabBtn = tabsHeaderEl.createEl("button", {
text: "Frames",
attr: { style: "padding:8px 12px; cursor:pointer;" }
});
framesTabBtn.addClass("tab-btn"); // NEW
const printingTabBtn = tabsHeaderEl.createEl("button", {
text: "Printing",
attr: { style: "padding:8px 12px; cursor:pointer;" }
});
printingTabBtn.addClass("tab-btn"); // NEW
const tabsBody = container.createDiv();
tabsBody.addClass("tab-panels"); // NEW
framesTabEl = tabsBody.createDiv({ attr: { style: "display:block;" } });
framesTabEl.addClass("tab-panel"); // NEW
printingTabEl = tabsBody.createDiv({ attr: { style: "display:none;" } });
printingTabEl.addClass("tab-panel"); // NEW
const activate = (tab) => {
if (tab === "frames") {
framesTabEl.style.display = "";
printingTabEl.style.display = "none";
framesTabBtn.classList.add("is-active");
printingTabBtn.classList.remove("is-active");
} else {
framesTabEl.style.display = "none";
printingTabEl.style.display = "";
framesTabBtn.classList.remove("is-active");
printingTabBtn.classList.add("is-active");
}
};
framesTabBtn.addEventListener("click", () => {
window.excalidrawPrintLayoutWizard = "frames";
activate("frames")
});
printingTabBtn.addEventListener("click", () => {
window.excalidrawPrintLayoutWizard = "printing";
activate("printing")
});
activate(window.excalidrawPrintLayoutWizard ?? "frames");
} else {
// No frames yet, only frames tab content
framesTabEl = container.createDiv();
}
const createOptionsContainerCommonControls = (optionsContainer) => {
new ea.obsidian.Setting(optionsContainer)
.setName("Lock")
.setDesc("Lock the new frame element after it is created.")
.addToggle(toggle => {
toggle.setValue(lockFrame).onChange(value => {
lockFrame = value;
if (settings[LOCK_FRAME].value !== value) {
settings[LOCK_FRAME].value = value; dirty = true;
}
});
});
new ea.obsidian.Setting(optionsContainer)
.setName("Zoom to new frame")
.setDesc("Automatically zoom to the newly created frame")
.addToggle(toggle => {
toggle.setValue(shouldZoom).onChange(value => {
shouldZoom = value;
if (settings[SHOULD_ZOOM].value !== value) {
settings[SHOULD_ZOOM].value = value; dirty = true;
}
});
});
new ea.obsidian.Setting(optionsContainer)
.setName("Close after adding")
.setDesc("Close this dialog after adding a new frame")
.addToggle(toggle => {
toggle.setValue(shouldClose).onChange(value => {
shouldClose = value;
if (settings[SHOULD_CLOSE].value !== value) {
settings[SHOULD_CLOSE].value = value; dirty = true;
}
});
});
new ea.obsidian.Setting(optionsContainer)
.setName("Use only Marker Frames")
.setDesc("When off, all frames will be printed (not just marker frames)")
.addToggle(toggle => {
toggle.setValue(printMarkersOnly).onChange(value => {
printMarkersOnly = value;
if (settings[PRINT_MARKERS_ONLY].value !== value) {
settings[PRINT_MARKERS_ONLY].value = value;
dirty = true;
shouldRestart = true;
modal.close();
}
});
});
}
// FRAMES TAB CONTENT
// When no frames yet: initial size/orientation inputs and Create First Frame button
if (!hasFrames) {
const settingsContainer = framesTabEl.createDiv({
attr: {
// four columns: label + input, label + input
style: "display: grid; grid-template-columns: auto 1fr auto 1fr; gap: 10px; align-items: center;"
}
});
// Page Size
settingsContainer.createEl("label", { text: "Page Size:" });
const pageSizeDropdown = settingsContainer.createEl("select", {
cls: "dropdown",
attr: { style: "width: 100%;" }
});
PAGE_SIZES.forEach(size => pageSizeDropdown.createEl("option", { text: size, value: size }));
pageSizeDropdown.value = settings[PAGE_SIZE].value;
// Orientation
settingsContainer.createEl("label", { text: "Orientation:" });
const orientationDropdown = settingsContainer.createEl("select", {
cls: "dropdown",
attr: { style: "width: 100%;" }
});
PAGE_ORIENTATIONS.forEach(orientation => orientationDropdown.createEl("option", { text: orientation, value: orientation }));
orientationDropdown.value = settings[ORIENTATION].value;
const optionsContainer = framesTabEl.createDiv({ attr: { style: "margin-top: 10px;" } });
createOptionsContainerCommonControls(optionsContainer);
// Create First Frame button
const buttonContainer = framesTabEl.createDiv({
attr: { style: "display: grid; grid-template-columns: 1fr; gap: 10px; margin-top: 10px;" }
});
const createFirstBtn = buttonContainer.createEl("button", {
cls: "page-btn",
attr: { style: "height: 40px; background-color: var(--interactive-accent); color: var(--text-on-accent);" }
});
createFirstBtn.textContent = "Create First Frame";
createFirstBtn.addEventListener("click", async () => {
const tmpShouldClose = shouldClose;
shouldClose = true;
await createFirstFrame(pageSizeDropdown.value, orientationDropdown.value);
shouldClose = tmpShouldClose;
if(!shouldClose) {
shouldRestart = true;
modal.close()
}
});
} else {
// hasFrames: frame-management options + arrow buttons
const optionsContainer = framesTabEl.createDiv({ attr: { style: "margin-top: 10px;" } });
createOptionsContainerCommonControls(optionsContainer);
// Arrow buttons with center rotate toggle
const buttonContainer = framesTabEl.createDiv({
attr: {
style: "display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-top: 10px;"
}
});
const upBtn = buttonContainer.createEl("button", {
cls: "page-btn",
attr: { style: "grid-column: 2; grid-row: 1; height: 40px;" }
});
upBtn.innerHTML = ea.obsidian.getIcon("arrow-big-up").outerHTML;
upBtn.addEventListener("click", async () => { await addPage("up"); });
buttonContainer.createDiv({ attr: { style: "grid-column: 3; grid-row: 1;" } });
const leftBtn = buttonContainer.createEl("button", {
cls: "page-btn",
attr: { style: "grid-column: 1; grid-row: 2; height: 40px;" }
});
leftBtn.innerHTML = ea.obsidian.getIcon("arrow-big-left").outerHTML;
leftBtn.addEventListener("click", async () => { await addPage("left"); });
// Center toggle: Rotate next page
centerRotateBtn = buttonContainer.createEl("button", {
cls: "page-btn",
attr: { style: "grid-column: 2; grid-row: 2; height: 40px;" }
});
centerRotateBtn.textContent = "Rotate next page";
centerRotateBtn.addEventListener("click", () => {
rotateOnAdd = !rotateOnAdd;
setRotateBtnActive(rotateOnAdd);
});
setRotateBtnActive(rotateOnAdd);
const rightBtn = buttonContainer.createEl("button", {
cls: "page-btn",
attr: { style: "grid-column: 3; grid-row: 2; height: 40px;" }
});
rightBtn.innerHTML = ea.obsidian.getIcon("arrow-big-right").outerHTML;
rightBtn.addEventListener("click", async () => { await addPage("right"); });
const downBtn = buttonContainer.createEl("button", {
cls: "page-btn",
attr: { style: "grid-column: 2; grid-row: 3; height: 40px;" }
});
downBtn.innerHTML = ea.obsidian.getIcon("arrow-big-down").outerHTML;
downBtn.addEventListener("click", async () => { await addPage("down"); });
buttonContainer.createDiv({ attr: { style: "grid-column: 1; grid-row: 3;" } });
}
// PRINTING TAB CONTENT (only when hasFrames)
if (hasFrames && printingTabEl) {
const marginContainer = printingTabEl.createDiv({
attr: {
style: "display: grid; grid-template-columns: auto 1fr; gap: 10px; align-items: center; margin-top: 6px;"
}
});
marginContainer.createEl("label", { text: "Print Margin:" });
marginDropdown = marginContainer.createEl("select", { cls: "dropdown", attr: { style: "width: 100%;" } });
Object.keys(MARGINS).forEach(margin => marginDropdown.createEl("option", { text: margin, value: margin }));
marginDropdown.value = settings[MARGIN].value;
const printingOptions = printingTabEl.createDiv({ attr: { style: "margin-top: 10px;" } });
new ea.obsidian.Setting(printingOptions)
.setName(PRINT_EMPTY)
.setDesc("Include frames with no content in the PDF")
.addToggle(toggle => {
toggle.setValue(printEmptyPages).onChange(value => {
printEmptyPages = value;
if(settings[PRINT_EMPTY].value !== value) {
settings[PRINT_EMPTY].value = value;
dirty = true;
}
});
});
const printBtnRow = printingTabEl.createDiv({ attr: { style: "margin-top: 10px; display:flex; justify-content:flex-start;" } });
const printBtn = printBtnRow.createEl("button", {
cls: "page-btn",
attr: { style: "height: 40px; background-color: var(--interactive-accent);" }
});
printBtn.innerHTML = ea.obsidian.getIcon("printer").outerHTML;
printBtn.addEventListener("click", async () => { await printToPDF(marginDropdown.value); });
}
// CSS
div.createEl("style", {
text: `
.page-btn {
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
border-radius: 4px;
}
.page-btn:hover {
background-color: var(--interactive-hover);
}
.dropdown {
height: 30px;
background-color: var(--background-secondary);
color: var(--text-normal);
border-radius: 4px;
border: 1px solid var(--background-modifier-border);
padding: 0 10px;
}
.is-active {
background-color: var(--background-modifier-hover);
border-radius: 4px;
}
/* Tabs styling - NEW */
.tabs-header {
gap: 8px;
border-bottom: 1px solid var(--background-modifier-border);
}
.tabs-header .tab-btn {
background: var(--background-primary);
color: var(--text-normal);
border: 1px solid var(--background-modifier-border);
border-bottom: none;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
padding: 8px 12px;
margin-bottom: -1px; /* sit on top of the panel border */
}
.tabs-header .tab-btn:hover {
background: var(--background-modifier-hover);
}
.tabs-header .tab-btn.is-active {
background: var(--background-secondary);
color: var(--text-normal);
position: relative;
z-index: 2;
}
.tab-panels {
border: 1px solid var(--background-modifier-border);
border-radius: 0 6px 6px 6px; /* merge with active tab */
padding: 12px;
background: var(--background-primary);
}
/* accent styling for center rotate toggle when active */
.page-btn.is-accent {
background-color: var(--interactive-accent);
color: var(--text-on-accent);
}
.page-btn.is-accent:hover {
background-color: var(--interactive-accent-hover, var(--interactive-accent));
}
`
});
});
modal.open();
}
run();
@@ -0,0 +1 @@
<svg class="svg-icon lucide-printer" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><path d="M6 9V3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v6"/><rect x="6" y="14" width="12" height="8" rx="1"/></svg>

After

Width:  |  Height:  |  Size: 405 B