Compare commits

...

406 Commits
1.8.2 ... 2.2.5

Author SHA1 Message Date
zsviczian
dfbd385de7 2.2.5 2024-06-09 16:34:48 +02:00
zsviczian
94fbac38bf 2.2.4 2024-06-02 12:28:52 +02:00
zsviczian
73cf8e75d3 Merge pull request #1679 from evolutioned/patch-1
Update templater_mindmap.md grammar
2024-06-02 06:44:32 +02:00
zsviczian
395cbc104c Merge pull request #1805 from jmhammond/master
Remove autosaveInterval variable but preserve functionality
2024-06-02 06:43:31 +02:00
zsviczian
9a24db8379 Merge pull request #1810 from dmscode/master
Update zh-cn language to 2.2.3
2024-06-02 06:40:29 +02:00
zsviczian
934a5f4838 this to self 2024-06-02 06:39:55 +02:00
dmscode
9022500087 Update zh-cn language to 2.2.3 2024-06-01 12:03:23 +08:00
John Hammond
795807b6cf Remove autosaveInterval variable but preserve functionality 2024-05-29 17:15:14 -05:00
zsviczian
9eff79733c 2.2.3 2024-05-26 22:34:59 +02:00
zsviczian
c4e95d9207 replace commitToHistory with storeAction 2024-05-22 21:22:39 +02:00
zsviczian
32cd3a62b6 DEGUGGING in dev build with process.env.NODE_ENV, + this.lastSceneSnapshot 2024-05-22 20:08:49 +02:00
zsviczian
d05ccc0055 DEBUGGER improved 2024-05-20 19:28:33 +02:00
zsviczian
ff1d7b44b4 2.2.2 2024-05-20 12:25:53 +02:00
zsviczian
2b86ba2128 2.2.1 2024-05-20 09:37:14 +02:00
zsviczian
44a3b30e3b 2.2.0 2024-05-19 14:43:00 +02:00
zsviczian
bb9389c7dd cropping, fold everything, drag and drop fixes 2024-05-18 21:57:54 +02:00
zsviczian
ba4bfe9de7 embeddable theme 2024-05-18 14:33:25 +02:00
zsviczian
3a73b14ebb fixed select similar elements script 2024-05-18 09:35:42 +02:00
zsviczian
31f54db433 removed wrapAt, changed to refreshTextDimensions, updated onBeforeTextSubmit and Edit, replaced all != with !== plus additional type checks 2024-05-17 21:41:37 +02:00
zsviczian
26812dd297 force to open markdown command palette action, fixed decompress 2024-05-15 22:02:06 +02:00
zsviczian
ae4f4b4f08 2.1.8.1-beta-1 2024-05-14 21:07:42 +02:00
zsviczian
4ac0a4c565 2.1.8 2024-05-13 22:49:32 +02:00
zsviczian
69c9f824a0 Update README.md 2024-05-06 21:24:57 +02:00
zsviczian
6a2220c960 updated slideshow timestamp 2024-05-05 19:16:59 +02:00
zsviczian
aa501c2843 2.1.7 2024-05-05 19:13:30 +02:00
zsviczian
efce44f0a7 2.1.6.1-beta-1 2024-05-01 10:57:50 +02:00
zsviczian
491eb83d35 convert app.isMobile to DEVICE.isMobile, fix CropImage angle!==0 2024-04-30 19:31:00 +02:00
zsviczian
35cf0802d1 2.1.6 2024-04-23 22:19:49 +02:00
zsviczian
f768548f60 2.1.5 2024-04-22 20:10:21 +02:00
zsviczian
37789f9907 Update issue templates 2024-04-20 15:21:49 +02:00
zsviczian
131294464e 2.1.4 2024-04-13 09:44:15 +02:00
zsviczian
b7652a41f8 Update directory-info.json 2024-04-08 12:39:47 +02:00
zsviczian
91c5f85ec6 Update Deconstruct selected elements into new drawing.md 2024-04-08 12:38:40 +02:00
zsviczian
985983b31d updated slideshow script 2024-04-07 09:45:11 +02:00
zsviczian
9a27e38ce2 2.1.3 2024-04-06 15:52:34 +02:00
zsviczian
0017ed7c92 2.1.2 2024-04-06 13:32:56 +02:00
evolutioned
d5cf4ace21 Update templater_mindmap.md
Small grammar update
2024-04-02 06:40:55 +11:00
zsviczian
1c899746fd 2.1.1 2024-04-01 20:57:50 +02:00
zsviczian
9ea09c0fdd fixed excalidraw to PDF 2024-04-01 08:06:47 +02:00
zsviczian
cda674c289 2.1.0 2024-03-31 20:29:33 +02:00
zsviczian
ef7d7ccc91 2.0.26 2024-03-30 17:42:59 +01:00
zsviczian
21aa5eb2d6 wordsimthing... 2024-03-24 13:03:18 +01:00
zsviczian
e725fb9b65 improved description 2024-03-24 13:00:26 +01:00
zsviczian
fbd634bfce updated slideshow script 2024-03-24 12:53:53 +01:00
zsviczian
9a686f3827 corrected spelling in messages 2024-03-17 18:14:52 +01:00
zsviczian
999219a0c9 2.0.25 2024-03-17 18:01:18 +01:00
zsviczian
65fd370cc5 Revert "Merge pull request #1647 from blampewpew/feat-save-new-drawing-to-current-folder"
This reverts commit fa03968508, reversing
changes made to 78dace32d4.
2024-03-17 17:48:14 +01:00
zsviczian
232f0c38fa updated deconstruct script 2024-03-17 17:47:30 +01:00
zsviczian
fa03968508 Merge pull request #1647 from blampewpew/feat-save-new-drawing-to-current-folder
FR: Update Excalidraw Folder setting to allow saving drawings in the current folder or subfolder within current folder #1348
2024-03-17 17:14:12 +01:00
zsviczian
78dace32d4 support for templates folder 2024-03-17 16:57:10 +01:00
Bryan
a21e7aa0c5 Update helper text. 2024-03-16 15:13:57 -06:00
Bryan
9b7d828209 Set folder to current active folder if Excalidraw Folder name starts with "./" and embedUseExcalidrawFolder is true. 2024-03-16 15:13:45 -06:00
Bryan
a1071426c0 created getCurrentActiveDirectory helper function and updated reference in getAttachmentsFolderAndFilePath() 2024-03-16 15:09:53 -06:00
zsviczian
23baf21ef5 Merge pull request #1640 from zsviczian/custom-zoom
publish custom zoom script
2024-03-14 15:00:51 +01:00
zsviczian
d280b33073 publish custom zoom script 2024-03-14 14:58:39 +01:00
zsviczian
d1ab6bb7a1 2.0.24 2024-03-12 20:05:05 +01:00
zsviczian
146d04ea64 0.2.23 2024-02-25 16:15:32 +01:00
zsviczian
fd46a3f8ac Merge pull request #1602 from GColoy/Fix-Invert-Colors-Script
Fixed Issue with Invert Colors Script
2024-02-25 15:16:14 +01:00
zsviczian
9974ca1e2e 2.0.22 2024-02-21 18:49:19 +01:00
Felix
2c8a733359 Nested Arrays in colorPalette are taken into account 2024-02-20 19:54:30 +01:00
zsviczian
4c9eeb9a61 2.0.21 2024-02-19 19:32:44 +01:00
zsviczian
de23be8c2d Update directory-info.json 2024-02-19 08:03:22 +01:00
zsviczian
bb25a1fde9 Update ExcaliAI.md 2024-02-19 08:01:43 +01:00
zsviczian
9267c27bc5 Merge pull request #1578 from deining/fix-typos
Fix typos
2024-02-17 17:59:00 +01:00
zsviczian
591f073413 added match image fileId to Select Similar Elements 2024-02-12 17:53:04 +01:00
Andreas Deininger
9c28c1ee00 Fix typos 2024-02-05 01:27:36 +01:00
zsviczian
120d41ea2f 2.0.20 2024-02-04 10:46:16 +01:00
zsviczian
80b24a45ad fix getMaximumGroups 2024-02-04 10:36:24 +01:00
zsviczian
e8db9cbff6 2.0.19 2024-02-02 20:34:23 +01:00
zsviczian
5db4f8dd95 updated Crop Vintage Mask to ensure frame is at top 2024-01-29 22:55:27 +01:00
zsviczian
820b2ff6c4 2.0.18 2024-01-21 16:49:34 +01:00
zsviczian
596102fb68 publish crop vintage 2024-01-21 16:11:19 +01:00
zsviczian
fa5c63b224 publish crop script 2024-01-21 16:08:12 +01:00
zsviczian
aa66b8f716 Crop Vintage Mask Script 2024-01-21 15:41:57 +01:00
zsviczian
c06cdfcfa0 frogg image 2024-01-21 15:39:27 +01:00
zsviczian
e6943cf7f5 Merge pull request #1559 from BenMoon/patch-1
Fix typos
2024-01-21 11:44:26 +01:00
zsviczian
b3a2f067ab Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2024-01-21 11:43:28 +01:00
zsviczian
35407cf13d crop vintage mask image 2024-01-21 11:43:17 +01:00
BenMoon
d671faf712 Fix typos 2024-01-17 09:31:50 +01:00
zsviczian
815ad559ac updated deconstruct selected element into new drawing script 2024-01-16 15:06:12 +01:00
zsviczian
d2cf594756 Update Deconstruct selected elements into new drawing.md 2024-01-16 15:04:30 +01:00
zsviczian
6709f6cf87 updated text arch and split text by lines 2024-01-13 16:38:01 +01:00
zsviczian
f463f79222 updated split text by lines script 2024-01-11 21:15:25 +01:00
zsviczian
51cf3a9219 2.0.17 2024-01-10 22:12:02 +01:00
zsviczian
8700405af8 Merge pull request #1548 from tswwe/patch-4
Update zh-cn.ts
2024-01-10 20:51:15 +01:00
thxnder
d1d082b4f9 Update zh-cn.ts
keep up with en.ts
2024-01-10 20:20:04 +08:00
zsviczian
6dd9d1a056 2.0.16 2024-01-07 21:22:56 +01:00
zsviczian
46b03725e9 2.0.15 2024-01-07 10:57:53 +01:00
zsviczian
65d6577b28 Update README.md 2024-01-03 21:35:01 +01:00
zsviczian
97967f5b70 2.0.14 2024-01-03 16:32:07 +01:00
zsviczian
9323e1fad4 minor formatting change in rollup.config.json 2024-01-03 09:51:04 +01:00
zsviczian
3e4e741b54 2.0.13 2023-12-23 15:28:54 +01:00
zsviczian
4e2d8374e6 2.0.12 2023-12-23 11:02:57 +01:00
zsviczian
2b3037402a 2.0.11 2023-12-21 20:51:21 +01:00
zsviczian
bc67c27a82 updated ExcaliAI script 2023-12-21 19:49:15 +01:00
zsviczian
ffb8f6f00f css rework 2023-12-19 13:52:27 +01:00
zsviczian
d179dfe703 sliding panes support disabled 2023-12-18 17:26:28 +01:00
zsviczian
5701020901 Golden Ratio updated 2023-12-17 12:54:53 +01:00
zsviczian
1f2d795b58 stroke color currentColor GoldenRatio.svg 2023-12-17 09:36:54 +01:00
zsviczian
c123b3ef51 golden ratio svg 2023-12-17 07:57:13 +01:00
zsviczian
d565c7e9e9 Golden Ratio script 2023-12-17 07:54:15 +01:00
zsviczian
710a40c36b golden ratio image 2023-12-17 07:48:25 +01:00
zsviczian
64f9a5dd7d 2.0.10 2023-12-10 21:58:04 +01:00
zsviczian
323fe33c2f 2.0.9 2023-12-10 12:35:10 +01:00
zsviczian
2470f6f531 2.0.8 2023-12-08 17:19:16 +01:00
zsviczian
42968d8299 publishing relative font size cycle 2023-12-07 19:24:31 +01:00
zsviczian
7a50b6c77e Merge pull request #1425 from soraliu/master
feat(scripts): support repeat texts
2023-12-07 18:18:46 +01:00
zsviczian
0a10d5fbc9 Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2023-12-06 18:14:47 +01:00
zsviczian
40d2345501 LaTeX Cleanup 2023-12-06 18:14:44 +01:00
zsviczian
ab97ae5ebc updated modifier key combinations table 2023-12-04 17:25:10 +01:00
zsviczian
7c93e90fc0 2.0.7 2023-12-04 14:49:33 +01:00
zsviczian
d44cf3306b 2.0.6 manifest 2023-12-04 08:44:30 +01:00
zsviczian
324609999f rollback 2023-12-03 23:35:03 +01:00
zsviczian
3f54b851ae updated grid selected images 2023-12-03 20:17:22 +01:00
zsviczian
8bbb04b421 Merge pull request #1465 from 7flash/patch-6
Update Grid Selected Images.md
2023-12-03 20:13:04 +01:00
zsviczian
dc396c8707 fix file utils var 2023-12-03 20:11:22 +01:00
zsviczian
52cc5d3aa7 2.0.5 2023-12-03 20:00:47 +01:00
zsviczian
87b6335905 cleaned out React.createElement 2023-12-01 20:45:37 +01:00
zsviczian
17358f16c8 fix getFileFromURL, remove observers when not needed 2023-12-01 17:21:41 +01:00
zsviczian
23eb268031 Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2023-11-29 17:28:12 +01:00
zsviczian
27f4cb248d better error handling in ExcaliAI 2023-11-29 17:26:07 +01:00
zsviczian
bbaf4f7a34 updated ExcaliAI and GPT-Draw-a-UI timestamps 2023-11-26 19:59:39 +01:00
zsviczian
559455bf5b fix element.src to element.link 2023-11-26 19:57:42 +01:00
zsviczian
bd519aff08 fixed element.src to element.link 2023-11-26 19:57:04 +01:00
zsviczian
febeb787b5 Address undefined after installing the new version 2023-11-26 18:51:22 +01:00
zsviczian
9f8a9bfa8a 2.0.4 2023-11-26 18:38:31 +01:00
zsviczian
8b1daed0ef excaliAI 2023-11-26 16:06:55 +01:00
zsviczian
44c828c7e7 corrected minor error 2023-11-25 07:06:14 +01:00
Igor Berlenko
afabeaa2f3 Update Grid Selected Images.md 2023-11-24 22:41:51 +08:00
zsviczian
e72c1676c2 2.0.3 2023-11-21 22:51:56 +01:00
zsviczian
5a17eb7054 updated generator 2023-11-21 06:05:19 +01:00
zsviczian
75d52c07b8 lint 2023-11-20 23:05:55 +01:00
zsviczian
4dc6c17486 updated script 2023-11-20 22:32:13 +01:00
zsviczian
e780930799 draw a UI 2023-11-20 21:27:40 +01:00
zsviczian
49cd6a36a1 draw-a-ui-image 2023-11-20 21:20:12 +01:00
zsviczian
d4830983e2 2.0.2 2023-11-19 17:41:41 +01:00
zsviczian
a69fefffdc beta-2 2023-11-19 08:42:41 +01:00
zsviczian
1d0466dae7 2.0.1-beta-1 2023-11-18 17:43:47 +01:00
zsviczian
6e5a853d0f 2.0.2-beta-1 2023-11-18 17:40:59 +01:00
zsviczian
0c702ddf7b Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2023-11-18 17:16:26 +01:00
zsviczian
fdbffce1f9 Embeddable Settings 2023-11-18 17:16:24 +01:00
zsviczian
2872b4e3ce Merge pull request #1441 from heinrich26/patch-1
Fixed some typos
2023-11-18 17:14:45 +01:00
Hendrik Horstmann
0ba55e51e9 Fixed some typos 2023-11-15 10:50:16 +01:00
zsviczian
5887bf377d 2.0.1 2023-11-14 20:35:47 +01:00
zsviczian
c440dd9cf0 2.0.0 2023-11-13 22:16:43 +01:00
zsviczian
21bc1f7fa6 Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2023-11-12 17:29:40 +01:00
zsviczian
4279b13554 fix svg export 2023-11-12 17:29:37 +01:00
zsviczian
0e106b7c7b Merge pull request #1429 from 7flash/patch-5
Update Slideshow.md
2023-11-12 10:59:33 +01:00
zsviczian
4d7d1fba3a Image cache initialization watchdog 2023-11-12 09:43:34 +01:00
zsviczian
a35ea5e9da Adding the Assistant font, dynamic styling improvements, frame export colors, Removing ExcalidrawRef 2023-11-12 07:19:09 +01:00
zsviczian
48466f624d 1.9.29 beta 2023-11-11 11:57:23 +01:00
Igor Berlenko
f306b20449 Update Slideshow.md 2023-11-11 18:28:57 +08:00
zsviczian
fc4fd685ba publish support 2023-11-11 07:21:14 +01:00
zsviczian
7449df6ac6 docs 2023-11-09 19:00:03 +01:00
zsviczian
1390333c4c publish EA.d.ts to docs 2023-11-09 18:58:24 +01:00
zsviczian
a9f545a1b2 updated docs 2023-11-09 18:57:08 +01:00
Sora Liu
f80a96c703 feat(scripts): support repeat texts 2023-11-08 17:11:55 +04:00
zsviczian
f291c15bbc restructured render Excalidraw, support for cssclass, context menu 2023-11-06 20:37:53 +01:00
zsviczian
18821e1a67 1.9.28 2023-11-04 14:28:30 +01:00
zsviczian
5dd65d691c mermaid to retain SVG for legacy, LASER-config, PDF import improvements, image link click improvements, hover on image with link, dynamic styling fix, image cache fix 2023-11-03 23:02:09 +01:00
zsviczian
8f96dbc21d 1.9.27 2023-10-28 20:51:55 +02:00
zsviczian
f71623f8a1 1.9.26 2023-10-25 18:55:18 +02:00
zsviczian
b380420cac Update README.md 2023-10-23 07:50:52 +02:00
zsviczian
21cccd4475 updated readme 2023-10-23 07:50:10 +02:00
zsviczian
06475aea78 1.9.25 2023-10-22 21:15:32 +02:00
zsviczian
84af0c2d5c Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2023-10-15 17:14:41 +02:00
zsviczian
84648f5a56 1.9.24 2023-10-15 17:14:39 +02:00
zsviczian
60b7988860 fixed keyboard event handler when switching back and forth between views 2023-10-12 22:12:26 +02:00
zsviczian
732b7ad424 updated the slideshow script with the laserpointer 2023-10-07 21:44:06 +02:00
zsviczian
a664c34418 bump excalidraw package 2023-10-07 18:56:04 +02:00
zsviczian
eaf3b7f7d7 1.9.23 2023-10-07 18:47:33 +02:00
zsviczian
03edbaf545 1.9.22 2023-10-07 14:22:07 +02:00
zsviczian
c3edf23023 Merge pull request #1354 from LiamSwayne/patch-1
Grammar fix
2023-10-07 14:09:34 +02:00
Liam Swayne
5f765609f9 grammar fix 2023-10-04 13:47:07 -04:00
zsviczian
3c1beac822 added image 2023-10-01 17:55:33 +02:00
zsviczian
f30f66969c concatenate lines script 2023-10-01 17:48:43 +02:00
zsviczian
4e6fb48bf0 1.9.20 2023-09-30 22:27:16 +02:00
zsviczian
53225ba5a7 Merge pull request #1338 from Aeases/master
Fix Excalidraw Pasting with middle mouse button on linux
2023-09-30 18:41:34 +02:00
zsviczian
c04967d775 should render mermaid and disabling ctrl hover preview when object snapping 2023-09-30 18:41:11 +02:00
zsviczian
6bb7a3c0e5 publish boolean operations 2023-09-30 18:31:42 +02:00
zsviczian
2c85191671 mermaid 2 2023-09-29 19:22:22 +02:00
zsviczian
637235eb9d mermaid-2 beta 2023-09-29 19:16:38 +02:00
zsviczian
13e4203c44 Merge pull request #1346 from Raboro/patch-1
fixed typos & syntax errors
2023-09-26 18:49:21 +02:00
Marius Wörfel
d04b628d55 fixed typos & syntax errors 2023-09-25 22:16:57 +02:00
zsviczian
2eab13661d Merge pull request #1341 from GColoy/boolean-Operations
Boolean operations
2023-09-24 16:32:39 +02:00
GColoy
6eefb49eb0 Added version requirement to Boolean operations 2023-09-23 01:14:25 +02:00
GColoy
6878b1f969 added description and index entries 2023-09-23 01:07:58 +02:00
GColoy
0ba784564f replaced ShadowClone Propertie with group id 2023-09-23 00:07:33 +02:00
GColoy
62804cdc63 Boolean Operations Icon 2023-09-22 21:56:49 +02:00
GColoy
ff900b4b61 account for unclosed Lines 2023-09-22 21:56:34 +02:00
GColoy
e34f03c0d1 updated boolean Operations for new api 2023-09-20 21:25:40 +02:00
Felix
2a9cf1cf6b Merge branch 'zsviczian:master' into boolean-Operations 2023-09-20 21:24:09 +02:00
GColoy
d4159dbe75 Corrected Polybool import 2023-09-20 21:23:28 +02:00
GColoy
7cd68304c3 added Boolean Operations.md 2023-09-19 19:00:20 +02:00
GColoy
0d7a76310c added polybool library 2023-09-16 21:06:34 +02:00
zsviczian
056be574ef mermaid beta 2023-09-16 14:30:49 +02:00
zsviczian
f6e2886185 added basic mermaid support 2023-09-16 14:24:04 +02:00
Aeases
7b178ce2c8 Fix Excalidraw Pasting with middle mouse button on linux 2023-09-16 20:02:16 +08:00
zsviczian
e4d05ac284 added otf, fixed replaceSVGColors null 2023-09-12 18:28:14 +02:00
zsviczian
5311f53612 Merge pull request #1318 from tswwe/patch-3
Update zh-cn.ts
2023-09-09 09:58:10 +02:00
thxnder
f20ce396f0 Update zh-cn.ts
Keep up with en.ts
2023-09-09 12:26:23 +08:00
zsviczian
996f1f79f1 update index-new with deconstruct video link 2023-09-03 11:30:28 +02:00
zsviczian
cd58d1af06 1.9.19 2023-09-03 11:27:04 +02:00
zsviczian
9d9201b4d1 text aura 2023-09-03 11:03:24 +02:00
zsviczian
684b2f6268 text aura image 2023-09-03 10:02:48 +02:00
zsviczian
de08f4584d silent create - EA 2023-09-03 07:46:32 +02:00
zsviczian
a937b5d70a 1.9.18 2023-08-27 22:22:05 +02:00
zsviczian
95b99edede implemented editor-paste event listner 2023-08-27 15:13:45 +02:00
zsviczian
cdf7591e0f publis ellipse script 2023-08-27 13:03:52 +02:00
zsviczian
f373867356 Merge pull request #1293 from GColoy/Split-Ellipse
ea-script: Split ellipse
2023-08-27 13:00:27 +02:00
zsviczian
041efcab74 grid color now sets two colors in appstate 2023-08-27 07:18:11 +02:00
zsviczian
e1fe3eeaab fixed constants.ts filename casing issue. Updated tsconfig and rollout.config 2023-08-26 11:02:00 +02:00
GColoy
3793148c77 added index entries for split ellipse 2023-08-22 01:02:06 +02:00
GColoy
5dbfeb085e added split ellipse script and images 2023-08-22 01:01:50 +02:00
zsviczian
f79181c76a fix embed quote 2023-08-20 07:09:35 +02:00
zsviczian
c0df46cb7b 1.9.17 2023-08-19 20:23:59 +02:00
zsviczian
aa7dcf7604 fix image cache, fix ea error, insert quote 2023-08-16 06:32:51 +02:00
zsviczian
fe4a39afc5 toggle grid 2023-08-15 20:50:33 +02:00
zsviczian
4185192954 toggle grid 2023-08-15 20:41:27 +02:00
zsviczian
3a9ee63c97 Merge pull request #1280 from GColoy/ToggleGrid
Toggle grid ea-script
2023-08-15 20:38:08 +02:00
GColoy
d2d2537867 Added Index entrys 2023-08-15 16:29:19 +02:00
GColoy
97fe819737 added ToggleGrid script 2023-08-15 16:09:51 +02:00
zsviczian
9fdca28579 1.9.16 2023-08-11 17:30:31 +02:00
zsviczian
261e093700 1.9.15 2023-08-10 19:18:24 +02:00
zsviczian
07a651c2c8 update 2023-08-10 18:49:03 +02:00
zsviczian
6c0a1f9a4d index-new update 2023-08-10 18:47:18 +02:00
zsviczian
9d941a4e44 updated index-new.md 2023-08-10 18:45:37 +02:00
zsviczian
cbb8f676af implemented script store search 2023-08-09 19:59:16 +02:00
zsviczian
3bcf460ce4 styles manager improvements 2023-08-08 20:00:37 +02:00
zsviczian
23dd4883e3 semi translucent background for embeddables 2023-08-07 21:43:40 +02:00
zsviczian
ce2e0fd408 stylesManager 2023-08-07 20:31:51 +02:00
zsviczian
9438031b4a 1.9.14 2023-08-06 15:10:10 +02:00
zsviczian
d5e584c1f0 fixed save on workspace click, updated lib, updated packages 2023-08-06 10:00:12 +02:00
zsviczian
8624671c4c Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2023-08-06 08:21:36 +02:00
zsviczian
88094c056c before npm audit fix 2023-08-06 08:21:26 +02:00
zsviczian
fa918e1c76 publish select similar elements script 2023-08-05 23:33:59 +02:00
zsviczian
d89d35e420 publish select similar elements script image 2023-08-05 23:25:58 +02:00
zsviczian
ddcfddd698 1.9.13 2023-08-05 15:24:07 +02:00
zsviczian
bdce2477c3 Native SVG support 2023-08-03 21:57:11 +02:00
zsviczian
83aa04396c Support Templater scripts in Embeddable Markdown Documents 2023-08-02 22:18:43 +02:00
zsviczian
c9c5468fe4 allowFrameButtonsInViewMode 2023-08-02 16:58:41 +02:00
zsviczian
4d6ec72717 added video link to organic line script description 2023-07-29 07:22:44 +02:00
zsviczian
b8ce29214f index-new update 2023-07-29 07:14:09 +02:00
zsviczian
66197e81b3 publishing Organic Line Legacy 2023-07-29 07:11:35 +02:00
zsviczian
d925ece80a updated slideshow frame sorting 2023-07-27 22:49:40 +02:00
zsviczian
8b3c61ae24 1.9.12 2023-07-27 10:45:06 +02:00
zsviczian
3f0086359a sword 2023-07-27 10:14:24 +02:00
zsviczian
9a807e4f8a 1.9.11 release message 2023-07-26 15:54:24 +02:00
zsviczian
2eb5fc476c 1.9.11 2023-07-25 23:07:34 +02:00
zsviczian
bb2d30f9e3 1.9.10 2023-07-23 19:09:19 +02:00
zsviczian
71582220ee added ellipse elements script 2023-07-23 19:07:15 +02:00
zsviczian
cfc872f3f1 Merge pull request #1212 from mazurov/master
Add script for drawing ellipse around selected elements
2023-07-23 18:56:57 +02:00
zsviczian
d914bd0678 1.9.9 2023-07-22 22:18:05 +02:00
zsviczian
a5fdf6efbb deleted ExcalidrawAutomateInterface declaration 2023-07-22 14:42:25 +02:00
zsviczian
02b1f035d3 ea.newFilePrompt 2023-07-22 10:02:07 +02:00
zsviczian
395fde7982 Stop tracking yarn.lock 2023-07-22 07:55:20 +02:00
zsviczian
8edab82308 release notes, fixing changes to match excalidraw package (twitter), 1.9.9 release notes. 2023-07-22 07:39:12 +02:00
zsviczian
d483ac55b5 Tweaks for ExcaliBrain next release 2023-07-17 22:48:09 +02:00
zsviczian
982f206ca4 shouldRestoreElements 2023-07-17 13:36:42 +00:00
zsviczian
1a9f56bb09 updated ExcalidrawAutomateInterface definition and exported type library 2023-07-16 21:10:03 +02:00
zsviczian
8ff312b8e4 correct embed link for twitter (and others in the future) in the exported SVG 2023-07-16 15:36:42 +02:00
zsviczian
d92349925a imagecache fix 2023-07-16 14:54:54 +02:00
zsviczian
d8cd929ebe migrated from "iframe" to "embeddable" 2023-07-16 09:39:46 +02:00
zsviczian
58d8780ac8 slideshow with video link 2023-07-15 15:24:29 +02:00
zsviczian
d3a0e43a2b fixed frames in slideshow script 2023-07-15 13:40:51 +02:00
zsviczian
0f5744eb43 archive alternative pens 2023-07-15 13:15:31 +02:00
zsviczian
85386c6b9b delete alternative pens 2023-07-15 13:13:31 +02:00
zsviczian
f708bf14fc removed alternative pens 2023-07-15 13:12:19 +02:00
zsviczian
64388096b8 publish updated slideshow script 2023-07-15 12:36:22 +02:00
Alexander Mazurov
ee92f91b86 Add script for drawing ellipse around selected elements
Based on the "Box Selected Elements" script
2023-07-13 14:48:56 +02:00
zsviczian
d82815c56a 1.9.8 2023-07-09 16:12:41 +02:00
zsviczian
1d6005f3c5 before updating renderWebview 2023-07-09 13:45:17 +02:00
zsviczian
a6ec0ceab5 before update iframe menu 2023-07-09 10:02:21 +02:00
zsviczian
65ecd8556f Merge pull request #1208 from Mqlvin/master
Fix grammatical "effect" "affect" issues
2023-07-09 07:50:05 +02:00
zsviczian
9067f2b79a frame menu and section zoom ready 2023-07-09 07:49:49 +02:00
Mqlvin
159166d03e Fix grammatical "effect" "affect" issues 2023-07-08 11:47:53 +01:00
zsviczian
b869bd6861 canvas node wip 2023-07-07 06:26:39 +02:00
zsviczian
de5b8b64a6 update frame placement 2023-07-02 17:24:16 +02:00
zsviczian
ea01c73e57 added youtube frame to scrip 2023-07-02 17:20:48 +02:00
zsviczian
4f726cbcd0 1.9.7 2023-07-02 15:43:58 +02:00
zsviczian
2f77988473 Merge pull request #1188 from chenpx976/feat-next-step
next-step: Supports fit ellipse diamond shape
2023-07-02 15:32:18 +02:00
zsviczian
d00247029b before update 2023-07-02 08:25:20 +02:00
zsviczian
1692d07b37 before adding backup store 2023-07-02 08:03:46 +02:00
zsviczian
24a2d39e63 draw.io script 2023-07-01 22:47:09 +02:00
zsviczian
a9847ec864 draw.io script 2023-07-01 22:44:34 +02:00
zsviczian
81fc788adc 1.9.6.1-beta 2023-06-30 20:14:07 +02:00
zsviczian
834343f821 update package 2023-06-30 06:10:15 +02:00
zsviczian
6b4f9fddae image cache take 1 2023-06-30 06:09:18 +02:00
chenpx976
791f98309d Supports fit ellipse diamond shape 2023-06-29 09:54:45 +08:00
zsviczian
fa86ef1136 added video to collaboration frame script 2023-06-27 19:38:31 +02:00
zsviczian
bf20919552 publish collababoration Frame 2023-06-27 18:01:27 +02:00
zsviczian
5931be2aa4 publish collaboration frame scripot 2023-06-27 17:56:11 +02:00
zsviczian
ef20226ace 1.9.6 2023-06-25 23:01:38 +02:00
zsviczian
fdec83d3a4 1.9.5 2023-06-25 16:19:06 +02:00
zsviczian
90b1bcbc3b iframe beta 2 2023-06-23 23:01:52 +02:00
zsviczian
c3650fd0ff Merge pull request #1158 from bennyyip/master
Fix typo: forth -> fourth
2023-06-19 18:24:53 +02:00
zsviczian
ba8c2a7995 Merge pull request #1161 from chenpx976/feat-next-step
feat: next step style same as previous rect
2023-06-19 18:24:22 +02:00
zsviczian
1a0783b56a Merge pull request #1164 from firai/fix-readme-spelling-engine
Fix spelling errors in readme
2023-06-19 18:20:33 +02:00
zsviczian
e9bce326f9 customIframes v0.1 2023-06-18 23:09:10 +02:00
zsviczian
0956f41b92 fix obsidian icon errors and toolspanel key prop 2023-06-15 20:41:33 +02:00
firai
25473770c6 Fix spelling errors in readme 2023-06-16 01:48:56 +08:00
chenpx976
81c5a2cca1 fix: tab style 2023-06-15 15:47:53 +08:00
chenpx976
90bc310643 feat: next step style same as previous rect 2023-06-15 15:45:22 +08:00
zsviczian
b8ab8e1084 fixed oldPalette is undefined error 2023-06-11 21:32:23 +02:00
bennyyip
cc7d3d894c Fix typo: forth -> fourth 2023-06-11 22:59:37 +08:00
zsviczian
8d04ac01a1 1.9.3 2023-06-03 12:28:01 +02:00
zsviczian
81ddbec324 slideshow update 2023-05-28 14:41:41 +02:00
zsviczian
35bc366f10 slideshow script 2023-05-28 14:39:51 +02:00
zsviczian
9aee982e8e slideshow script update 2023-05-28 14:27:27 +02:00
zsviczian
5638f91b25 Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2023-05-21 10:23:06 +02:00
zsviczian
443fd0eae3 1.9.2 2023-05-21 10:22:13 +02:00
zsviczian
454db1f315 Update directory-info.json 2023-05-19 10:26:21 +02:00
zsviczian
c3440e2b54 Merge pull request #1137 from wszybisty/mindmap-format-script-group-support
Fixed issue with mindmap format script that elements within group were not formatted along with selected element
2023-05-19 10:24:36 +02:00
Wojtek Szybisty
0b51636d8a Fixed issue with mindmap format script that elements within group were not formatted along with selected element 2023-05-18 05:33:23 +02:00
zsviczian
f52b011817 1.9.1 2023-05-14 19:23:19 +02:00
zsviczian
7b76acd9c9 update pdf page to clipboard 2023-05-13 15:21:25 +02:00
zsviczian
2de1ba1f45 publish pdf text to clipboard 2023-05-13 14:21:26 +02:00
zsviczian
5e702499b0 pdf page text to clipboard script 2023-05-13 14:17:37 +02:00
zsviczian
79d67bc1f4 getBoundTextMaxWidth 2023-05-12 20:10:42 +02:00
zsviczian
9fca82bb6f 1.9.0 2023-05-12 16:51:10 +02:00
zsviczian
00c801e338 updated slideshow script - do not select arrow at end if hidden 2023-05-01 07:03:08 +02:00
zsviczian
dd0c0cd021 updated slideshow script 2023-04-29 18:12:29 +02:00
zsviczian
12594baac6 1.8.26 2023-04-23 08:42:03 +02:00
zsviczian
b03bd7e4f9 updated scribble helper 2023-04-23 07:39:42 +02:00
zsviczian
02b21aeea9 lint 2023-04-23 07:27:07 +02:00
zsviczian
a67bdfa5e8 updates scribble helper 2023-04-23 07:25:06 +02:00
zsviczian
52407e89fb updated image 2023-04-22 21:41:07 +02:00
zsviczian
7e930c2339 1.8.25 2023-04-22 21:24:33 +02:00
zsviczian
7ab8f07d1f updated scribble helper 2023-04-21 05:57:19 +02:00
zsviczian
d34086a395 1.8.24 2023-04-17 23:44:46 +02:00
zsviczian
334f122cca Upgraded Script util.inputPrompt 2023-04-16 21:49:56 +02:00
zsviczian
f80202e5e7 1.8.23 2023-04-15 13:41:00 +02:00
zsviczian
29736f10fc bump excalidraw package and minor styling change 2023-04-13 18:16:40 +02:00
zsviczian
0654663dff Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2023-04-08 19:42:57 +02:00
zsviczian
4e12f7cc4c gitignore 2023-04-08 19:41:58 +02:00
zsviczian
a42dbc0cdc updates messages 2023-04-02 11:04:05 +02:00
zsviczian
5c40cdb3d3 1.8.22 2023-04-02 10:20:09 +02:00
zsviczian
d47a206206 script update 2023-04-02 09:33:21 +02:00
zsviczian
ba0eaf067b Merge pull request #1016 from threethan/master
Add Hardware Eraser and Auto Draw scripts
2023-04-02 09:23:59 +02:00
zsviczian
f80edce3dc renam invert-colors image 2023-03-26 22:52:09 +02:00
zsviczian
21968214af 1.8.21 2023-03-26 22:40:46 +02:00
zsviczian
7770eb51dc Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2023-03-19 20:12:29 +01:00
zsviczian
d0229259a6 1.8.20 2023-03-19 20:12:26 +01:00
zsviczian
00cbea3705 folder note core script 2023-03-16 14:41:59 +01:00
zsviczian
e85857c29f Update directory-info.json 2023-03-16 14:36:33 +01:00
zsviczian
1704a016b1 Add files via upload 2023-03-16 14:34:40 +01:00
zsviczian
f5af19557a publish Text to Sticky Notes 2023-03-11 13:29:11 +01:00
zsviczian
17b8b154c2 publish image 2023-03-11 13:19:41 +01:00
zsviczian
5c1030880a 1.8.19 2023-03-07 20:10:47 +01:00
zsviczian
1b62983016 1.8.18 2023-03-04 19:14:15 +01:00
zsviczian
52fb7ab546 updated excalidraw package 2023-02-26 17:35:13 +01:00
zsviczian
604bfbf23f 1.8.17 2023-02-26 17:28:59 +01:00
zsviczian
3c1a3c18c2 Merge pull request #1038 from tswwe/patch-2
Update zh-cn.ts
2023-02-26 17:16:20 +01:00
thxnder
f531c361de Update zh-cn.ts
keep up with en.ts
2023-02-26 18:20:37 +08:00
zsviczian
4609ea33bb 1.8.16 2023-02-21 21:11:23 +01:00
zsviczian
41b1a170f7 1.8.15-beta 2023-02-19 20:58:04 +01:00
zsviczian
e6d39eca75 upload modifiers table 2023-02-19 16:56:12 +01:00
threethan
2a1e3731ba Improved pen plugins (better compatibility) 2023-02-14 23:12:03 -05:00
zsviczian
8ca6a9fe96 1.8.14 2023-02-13 17:16:32 +01:00
threethan
6f2248ffa0 Add Hardware Eraser and Auto Draw scripts 2023-02-13 00:58:04 -05:00
zsviczian
48e47f333e 1.8.13 2023-02-12 15:41:18 +01:00
zsviczian
3091ed629a 1.8.12 2023-01-29 13:29:41 +01:00
zsviczian
a9193dd695 updated mindmap format description 2023-01-28 23:47:06 +01:00
zsviczian
3122e86e22 mindmap format script 2023-01-28 23:37:48 +01:00
zsviczian
a6efe27146 Merge pull request #968 from pandoralink/feature/mindmap-format
feat: add Mindmap format ea-script
2023-01-28 23:24:21 +01:00
zsviczian
adbec35e30 Merge pull request #989 from SamRidgeway/typo-fix
Fixes a typo in the fullscreen action
2023-01-28 23:23:35 +01:00
zsviczian
205a94d3a3 custom pen debug 2023-01-28 23:23:04 +01:00
zsviczian
6e88c8f0eb Pens and pinned scripts 2023-01-27 22:35:27 +01:00
Sam Ridgeway
32a05322d0 Fixes a typo in the fullscreen action 2023-01-27 09:39:14 -05:00
zsviczian
8738b74236 1.8.11 2023-01-22 14:19:41 +01:00
zsviczian
aa0ddd85fd handle link click cleanup duplicat function 2023-01-20 20:32:50 +01:00
zsviczian
bcd47ddb8e wheel, pinch, image url, grid 2023-01-19 22:24:15 +01:00
zsviczian
50f24b42cb 1.8.10 2023-01-15 16:49:28 +01:00
poplink
0a5d511c96 feat: add Mindmap format ea-script 2023-01-11 17:50:04 +08:00
zsviczian
70d93602f7 added new customized menu 2023-01-09 23:05:48 +01:00
zsviczian
68c7c9f55e 1.8.9 2023-01-08 14:29:56 +01:00
zsviczian
42f1fa88b9 added funding URL 2023-01-06 18:00:27 +01:00
zsviczian
3a2d064024 1.8.8 2023-01-06 17:57:05 +01:00
zsviczian
38cfdd4e0e corrected spelling of filename 2023-01-06 12:15:26 +01:00
zsviczian
fb93c0c352 publish updated scripts 2023-01-06 12:11:07 +01:00
zsviczian
f4fb1e3cc8 Alternative pen assets 2023-01-06 11:49:00 +01:00
zsviczian
5c11e5733e updated slideshow and deconstruct scripts 2023-01-02 16:09:21 +01:00
zsviczian
8d8f7a7866 1.8.7 2022-12-23 21:48:04 +01:00
zsviczian
da89e32213 1.8.6 2022-12-17 14:04:46 +01:00
zsviczian
b6d36e5076 Update manifest.json 2022-12-17 13:41:36 +01:00
zsviczian
90c377f125 1.8.5 2022-12-17 12:41:31 +01:00
zsviczian
df85138890 updated elbow connectors 2022-12-15 18:55:43 +01:00
zsviczian
654b656a2f Merge pull request #935 from 1-2-3/master
feat:add "Center connect points" option to "Elbow connectors" script
2022-12-15 18:54:26 +01:00
zsviczian
1d22dcb488 roundness 2022-12-12 23:17:50 +01:00
zahuifan
a42e907d0c feat:add "Center connect points" option. 2022-12-11 12:19:18 +08:00
zsviczian
353cad21d6 Update directory-info.json 2022-12-07 10:14:46 +01:00
zsviczian
2b02f186a6 Update Text Arch.svg 2022-12-07 10:13:47 +01:00
zsviczian
3e12d1e815 Update directory-info.json 2022-12-07 10:03:54 +01:00
zsviczian
539bbdf07f Merge pull request #929 from 1-2-3/master
fix: scripts not work with arrows that contain text.
2022-12-07 10:02:07 +01:00
zsviczian
b993b358fe 1.8.4 2022-12-06 22:51:54 +01:00
zahuifan
0639ea5969 fix: not work with arrows that contain text. 2022-12-06 12:15:46 +08:00
zsviczian
05b7bc6029 1.8.3-beta 2022-12-05 23:27:21 +01:00
zsviczian
c9755be0e9 organic line update 2022-12-04 22:16:27 +01:00
zsviczian
9526fb5726 cleanup script library 2022-12-04 21:36:36 +01:00
zsviczian
c04587ab5c added Uniform size script 2022-12-04 19:53:46 +01:00
zsviczian
ab94fe304b add auto layout.md 2022-12-04 18:47:04 +01:00
zsviczian
b080fec85e Merge pull request #922 from 1-2-3/master
add auto layout ea-script
2022-12-04 18:44:16 +01:00
zsviczian
c9972116b3 fixed #905 2022-12-04 17:00:30 +01:00
zahuifan
c254c8cc5d add direction suggester 2022-12-04 18:01:06 +08:00
zahuifan
14c2ce3766 add auto layout ea-script 2022-12-04 15:50:34 +08:00
zsviczian
437a01c194 1.8.2 2022-12-03 00:19:32 +01:00
215 changed files with 32084 additions and 17494 deletions

View File

@@ -1,12 +1,22 @@
---
name: Bug report
about: Create a report to help me improve Excalidraw
about: When something is clearly broken. Everything else is a feature request.
title: 'BUG: '
labels: ''
assignees: ''
---
Help me help you. I am a one man show doing this plugin as a part time hobby. There is no point in flooding me with issues, if there are too many, and they are poorly documented, I will just ignore them. Sorry...
Before creating a bug report, please
1. review recent release notes - maybe there is already an answer,
2. search issues (including closed ones) to see if there is anything similar.
⚠️ I will have to close all recorded bugs that do not provide this background information. Sorry, I need to control my workload/time. ⚠️
--------
**Your environment**
Please run `Command Palette/Show Debug info` in Obsidian and paste the result here.

8
.gitignore vendored
View File

@@ -13,7 +13,11 @@ stats.html
hot-reload.bat
data.json
lib
dist
#VSCode
.vscode
yarn.lock
.vscode
yarn.lock
.DS_Store
.lock
.lock

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
16

View File

@@ -195,7 +195,7 @@ This is relevant when setting a fix height using the `addText()` function.
### startArrowHead, endArrowHead
String. Valid values are "arrow", "bar", "dot", and "none". Specifies the beginning and ending of an arrow.
This is relavant when using the `addArrow()` and the `connectObjects()` functions.
This is relevant when using the `addArrow()` and the `connectObjects()` functions.
## canvas
Sets the properties of the canvas.

32
README-BUILD.md Normal file
View File

@@ -0,0 +1,32 @@
The project runs with `node 18`.
After running `npm -i` you'll need to make two manual changes:
## postprocess
postprocess is used in rollup.config.js.
However, the version available on npmjs does not work, after installing packages you need this update:
`npm install brettz9/rollup-plugin-postprocess#update --save-dev``
More info here: https://github.com/developit/rollup-plugin-postprocess/issues/10
## colormaster
1.2.1 misses 3 plugin references after installing the package you need to update
`node_modules/colormaster/package.json` adding the following to the `exports:` section:
```typescript
,
"./plugins/luv": {
"import": "./plugins/luv.mjs",
"require": "./plugins/luv.js",
"default": "./plugins/luv.mjs"
},
"./plugins/uvw": {
"import": "./plugins/uvw.mjs",
"require": "./plugins/uvw.js",
"default": "./plugins/uvw.mjs"
},
"./plugins/ryb": {
"import": "./plugins/ryb.mjs",
"require": "./plugins/ryb.js",
"default": "./plugins/ryb.mjs"
}
```

287
README.md
View File

@@ -3,8 +3,13 @@
The Obsidian-Excalidraw plugin integrates [Excalidraw](https://excalidraw.com/), a feature rich sketching tool, into Obsidian. You can store and edit Excalidraw files in your vault, you can embed drawings into your documents, and you can link to documents and other drawings to/and from Excalidraw. For a showcase of Excalidraw features, please read my blog post [here](https://www.zsolt.blog/2021/03/showcasing-excalidraw.html) and/or watch the videos below.
## Video Walkthrough
<a href="https://youtu.be/P_Q6avJGoWI" target="_blank"><img src="https://github.com/zsviczian/obsidian-excalidraw-plugin/assets/14358394/da34bb33-7610-45e6-b36f-cb7a02a9141b" width="300"/></a>
<a href="https://youtu.be/o0exK-xFP3k" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/156931370-aa4d88de-c4a8-46cc-aeb2-dc09aa0bea39.jpg" width="300"/></a>
<a href="https://youtu.be/QKnQgSjJVuc" target="_blank"><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/thumbnail-getting-started.jpg" width="300"/></a>
<a href="https://youtu.be/o0exK-xFP3k" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/156931370-aa4d88de-c4a8-46cc-aeb2-dc09aa0bea39.jpg" width="300"/></a>
### Here's my complete catalog of videos:
<a href="https://excalidraw-obsidian.online/Hobbies/Excalidraw+Blog/Catalogue+of+Videos"><img width="380" alt="image" src="https://github.com/zsviczian/obsidian-excalidraw-plugin/assets/14358394/2577e5ad-7a21-4c62-acd5-4fe80c8a8a95"></a>
<br>
<details><summary>10 Part (slightly outdated) Video Walkthrough</summary>
<a href="https://youtu.be/sY4FoflGaiM" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/125160304-7f211180-e17c-11eb-8363-c52723de1ffd.jpg" width="100" style="vertical-align: middle;"/>&nbsp;&nbsp;1 Getting Started</a><br>
@@ -27,7 +32,7 @@ The Obsidian-Excalidraw plugin integrates [Excalidraw](https://excalidraw.com/),
</details>
<details><summary>The Script Engine Store - Excalidraw Automation</summary>
<a href="https://youtu.be/hePJcObHIso" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/145684531-8d9c2992-59ac-4ebc-804a-4cce1777ded2.jpg" width="100" style="vertical-align: middle;"/>&nbsp;&nbsp;Introducing the Script Engine</a><br>
<a href="https://youtu.be/lzYdOQ6z8F0" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/147889174-6c306d0d-2d29-46cc-a53f-3f0013cf14de.jpg" width="100" style="vertical-align: middle;"/>&nbsp;&nbsp;Script Enginge Store</a><br>
<a href="https://youtu.be/lzYdOQ6z8F0" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/147889174-6c306d0d-2d29-46cc-a53f-3f0013cf14de.jpg" width="100" style="vertical-align: middle;"/>&nbsp;&nbsp;Script Engine Store</a><br>
</details>
<details><summary>Working with colors</summary>
<a href="https://youtu.be/6PLGHBH9VZ4" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/194773147-5418a0ab-6be5-4eb0-a8e4-d6af21b1b483.png" width="100" style="vertical-align: middle;"/>&nbsp;&nbsp;Colors - Excalidraw Basics (Custom)</a><br>
@@ -44,10 +49,11 @@ The Obsidian-Excalidraw plugin integrates [Excalidraw](https://excalidraw.com/),
</details>
<details><summary>Powertools</summary>
<a href="https://youtu.be/NOuddK6xrr8" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/147283367-e5689385-ea51-4983-81a3-04d810d39f62.jpg" width="100" style="vertical-align: middle;"/>&nbsp;&nbsp;Sticky Notes (word wrapping)</a><br>
<a href="https://youtu.be/eKFmrSQhFA4" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/149659524-2a4e0a24-40c9-4e66-a6b1-c92f3b88ecd5.jpg" width="100" style="vertical-align: middle;"/>&nbsp;&nbsp;Fourt Font</a><br>
<a href="https://youtu.be/eKFmrSQhFA4" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/149659524-2a4e0a24-40c9-4e66-a6b1-c92f3b88ecd5.jpg" width="100" style="vertical-align: middle;"/>&nbsp;&nbsp;Fourth Font</a><br>
<a href="https://youtu.be/vlC1-iBvIfo" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/199207784-8bbe14e0-7d10-47d7-971d-20dce8dbd659.png" width="100" style="vertical-align: middle;"/>&nbsp;&nbsp;SVG import</a><br>
<a href="https://youtu.be/7gu4ETx7zro" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/202916770-28f2fa64-1ba2-4b40-a7fe-d721b42634f7.png" width="100" style="vertical-align: middle;"/>&nbsp;&nbsp;OCR</a><br>
<a href="https://youtu.be/U2LkBRBk4LY" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/159369910-6371f08d-b5fa-454d-9c6c-948f7e7a7d26.jpg" width="100" style="vertical-align: middle;"/>&nbsp;&nbsp;Bind/unbind text from container, Frontmatter tags to customize export</a><br>
<a href="https://youtu.be/uZz5MgzWXiM" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/211054371-8872e01a-77d6-4afc-a0c2-86a55410a8d3.png" width="100" style="vertical-align: middle;"/>&nbsp;&nbsp;Custom pen support</a><br>
</details>
<details><summary>Quality of life improvements</summary>
<a href="https://youtu.be/qbPIAZguJeo" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/151705333-54e9ffd2-0bd7-4d02-b99e-0bd4e4708d4d.jpg" width="100" style="vertical-align: middle;"/>&nbsp;&nbsp;Mobile Support</a><br>
@@ -61,185 +67,198 @@ The Obsidian-Excalidraw plugin integrates [Excalidraw](https://excalidraw.com/),
## Features
- The plugin integrates Excalidraw seamlessly into Obsidian including Command Palette actions, File
Explorer features, Option Menu commands, and the Ribbon Button.
- <kbd>CTRL/CMD+Click</kbd> on the ribbon button, or in the file explorer to create / open drawings
in a new pane.
- The plugin integrates Excalidraw seamlessly into Obsidian, including Command Palette actions, File Explorer features, Option Menu commands, and the Ribbon Button.
- <kbd>CTRL/CMD+Click</kbd> on the ribbon button or in the file explorer to create / open drawings in a new pane.
### Settings
Settings will allow you to customize Excalidraw to your needs:
Settings will allow you to customize Excalidraw to your needs. The plugin comes with tons of settings. I tried adding meaningful explanations to these settings, so please be patient and look for the setting, for most requests, a setting already exists.
- Default folder for new drawings and define custom filename pattern for new drawings.
Plugin settings are grouped into the following sections:
- **Basic settings**: such as default folders to use.
- **Saving**: compression and autosave timer.
- **Filename**: configure the automatically created Excalidraw filename.
- **Display**: settings that effect the handling of Excalidraw (e.g.: left-handed mode, theme settings, mouse wheel and pinch zoom settings, zoom to fit settings).
- **Links and transclusions**: Settings that effect how links and embedded items behave on the Excalidraw canvas.
- **Markdown-embed settings**: These settings control how markdown documents from your Vault embedded into Excalidraw drawings will behave.
- **Embed & Export**: Settings that control how Excalidraw images are displayed when embedding them into markdown documents.
- **Auto-export Settings**: You can configure Excalidraw to create a PNG or SVG copy of your drawing each time it gets saved.
- **Compatibility features**: Check these settings if you edit the Excalidraw drawings outside Obsidian (e.g. in LogSeq, Visual Studio, on the web, etc.).
- **Experimental features**: There are advanced features that are implemented as "clever" hacks. Features include defining a fourth font, adding a custom icon to distinguish Exalidraw files in the Obsidian file explorer, OCR settings, and more.
- **Settings for installed Scripts**: Some of the scripts you install from the Script Library come with settings. Script settings are installed the first time you run the script. So to access settings for a script, install the script, run it for the first time and then look for the settings in plugin settings.
#### Templates
- Template for new drawings. The template will restore stroke properties.
This means you can set up defaults in your template for stroke color, stroke width,
opacity, font family, font size, fill style, stroke style, etc.
This also applies to ExcalidrawAutomate.
- Via the template you can customize the color palette used by Excalidraw.
- Switch to Markdown view.
- Scroll down to the bottom of the file and find `"AppState": {`.
- Find `"customColorPalette": {` at the end of the AppState section.
- You may specify the 3 palettes used in Excalidraw by adding any or all of the following 3 variables:
- `"canvasBackground":[], "elementBackground":[], "elementStroke": []`.
- Add a comma separated list of valid HTML colors (e.g. `#FF0000` for red)
in the array for each of the variables.
- See my videos above for further help.
- Template for new drawings. The template will restore stroke properties. This means you can set up defaults in your template for stroke color, stroke width, opacity, font family, font size, fill style, stroke style, etc. This also applies to ExcalidrawAutomate.
- Via the template, you can customize the color palette used by Excalidraw.
- Switch to Markdown view.
- Scroll down to the bottom of the file and find `"AppState": {`.
- Find `"customColorPalette": {` at the end of the AppState section.
- You may specify the 3 palettes used in Excalidraw by adding any or all of the following 3 variables:
- `"canvasBackground":[], "elementBackground":[], "elementStroke": []`.
- Add a comma-separated list of valid HTML colors (e.g. `#FF0000` for red).
in the array for each of the variables.
- See my videos above for further help.
#### Export
- If portability is important to you:
- Auto-export SVG and/or PNG files including keep-in-sync feature so you can
embed SVG/PNG into your documents instead of embedding excalidraw files.
- You can override export settings for an individual file by adding the `excalidraw-autoexport`
frontmatter key. Valid values for this key are `none`, `both`, `png` and `svg`.
- Auto-export SVG and/or PNG files, including the keep-in-sync feature, so you can
embed SVG/PNG into your documents instead of embedding excalidraw files.
- You can override export settings for an individual file by adding the `excalidraw-autoexport`
frontmatter key. Valid values for this key are `none`, `both`, 'png', and `svg`.
- Specify the default width of embedded drawings.
- Compatibility features to auto-export and keep in sync markdown excalidraw files and legacy `.excalidraw` files.
- Experimental feature to add custom TAG to file explorer to mark drawing files.
- Enable / disable autosave.
### Embedded images
### Embedding your drawings into markdown documents
- You can customize the size and position of the embedded images using the
- `![[image.excalidraw|100]]`,
- `![[image.excalidraw|100x100]]`,
- `![[image.excalidraw|100|left]]`,
- `![[image.excalidraw|right-wrap]]`, formatting options.
- `![[<filename.excalidraw>|<width>x<height>|<alignment>]]`.
- You can add your custom [alignment via CSS](https://www.scaler.com/topics/align-image-in-html/).
- Any text that appears in `<alignment>` will be added to the rendered SVG element style and to the wrapper DIV element.
- See [styles.css](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/styles.css) for more insight.
- `![[image.excalidraw|100]]`,
- `![[image.excalidraw|100x100]]`,
- `![[image.excalidraw|100|left]]`,
- `![[image.excalidraw|right-wrap]]`, formatting options.
- `![[<filename.excalidraw>|<width>x<height>|<alignment>]]`.
- You can add your custom [alignment via CSS](https://www.scaler.com/topics/align-image-in-html/).
- Any text that appears in `<alignment>` will be added to the rendered SVG element style and to the wrapper DIV element.
- See [styles.css](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/styles.css) for more insight.
- Excalidraw drawings do not display in Obsidian Publish. If you want to use Excalidraw in your published documents, you can configure in plugin settings, under `Embed & Export`, to automatically insert a PNG or SVG version of the drawing in your document when creating a new file. See `type of file to insert into document`
- Under `Export settings`, you can also configure to automatically export a dark and light version of the image, in case your published site supports dark and light modes.
### Hyperlinks
### Hyperlinks and Drag & Drop support
- Supports hyperlinks e.g.
- `https://zsolt.blog`,
- `[Obsidian](https://obsidian.md)`, and
- internal links e.g. `[[My file in vault|Alias]]` in drawing text.
- Links will update when files are moved or renamed, if you have the Obsidian
setting Files & Links/Automatically Update Internal Links enabled.
- Links in drawings will show up in backlinks of documents
- Transclusions are supported
- `![[myfile#^blockref]]` will convert in the drawing into the transcluded text of the block
- `![[myfile#section]]` also works, this will transclude the section
- you can also specify word wrapping for transcluded text by adding the max character count
in curly brackets right after the transclusion e.g. `![[myfile#^blockref]]{40}` will wrap text at 40 characters.
- For convenience you can also use the command palette to insert links into drawings
- <kbd>CTRL/CMD + hover</kbd> to bring up the Obsidian quick preview for the link. (On Mac it is <kbd>CTRL+CMD+hover</kbd>).
- <kbd>CTRL/CMD + CLICK</kbd> a text element to open it as a link.
- <kbd>CTRL/CMD + ALT + CLICK</kbd> to create the file (if it does not yet exist) and open it
- <kbd>CTRL/CMD + SHIFT + CLICK</kbd> to open the file in a new pane
- <kbd>CTRL/CMD + ALT + SHIFT + CLICK</kbd> to create the file (if it does not yet exist) and open it in a new pane
- Using the block reference you can also reference & transclude text that appears on drawings, in other documents
![](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/images/excalidraw-modifiers.png)
#### Hyperlinks
- Supports hyperlinks, e.g.
- `https://zsolt.blog`,
- `[Obsidian](https://obsidian.md)`, and
- Internal links, e.g. `[[My file in vault|Alias]]` in drawing text.
- Links will update when files are moved or renamed, if you have the Obsidian setting Files & Links/Automatically Update Internal Links enabled.
- Links in drawings will show up in the backlinks of documents.
- Transclusions are supported:
- `![[myfile#^blockref]]` will convert the drawing into the transcluded text of the block
- `![[myfile#section]]` also works, this will transclude the section.
- You can also specify word wrapping for transcluded text by adding the max character count:
in curly brackets right after the transclusion e.g. `![[myfile#^blockref]]{40}` will wrap text at 40 characters.
- For convenience, you can also use the command palette to insert links into drawings.
- <kbd>CTRL/CMD + hover</kbd> to bring up the Obsidian quick preview for the link. (On Mac, it is <kbd>CTRL+CMD+hover</kbd>).
- Using the block reference, you can also reference & transclude text that appears on drawings, in other documents.
#### Drag & Drop support
- You can drag files from the Obsidian file explorer, and they will become links to those files in Excalidraw. See the table above for the various modifier key combinations.
- Note: Anchoring an image to 100% of its size is a very niche feature with a very particular behavior that I built primarily for myself.
- (even more so than other features in Excalidraw Obsidian - also built primarily for myself 😉).
- This will reset your embedded image to 100% size every time you open the Excalidraw drawing,
or in case you have embedded an Excalidraw drawing on your canvas inserted using this function,
every time you update the embedded drawing, it will be scaled back to 100% size.
- This means that even if you resize the image on the drawing, it will reset to 100% the next time you open
the file, or you modify the original embedded object. This feature is useful when you
decompose a drawing into separate Excalidraw files, but when combined onto a single canvas
you want the individual pieces to maintain their actual sizes. I use this feature to
construct book-on-a-page summaries from atomic drawings.
- You can drag and drop text from Markdown views onto Excalidraw.
- You can drag and drop web addresses from your browser, and they will become links.
- You can drag and drop YouTube links and thumbnails, and they will be YouTube links with thumbnails in Excalidraw.
### LaTeX
Insert LaTeX formulas using the Command Palette action "Insert LaTeX formula".
You can edit formulas either in Markdown view, or by <kbd>CTRL/CMD + Click</kbd> on the formula.
### Drag & Drop support
- You can drag files from the Obsidian file explorer and they will become links to those files in Excalidraw.
- Dragging image files (PNG, SVG, JPG, ICO, GIF, WEBP, Excalidraw) from Obsidian's file explorer while pressing the
<kbd>CTRL</kbd> (<kbd>SHIFT</kbd> on Mac) button will embed the image into your drawing.
- If in addition to <kbd>CTRL</kbd> or <kbd>SHIFT</kbd> you also hold down <kbd>ALT</kbd>,
the image will be inserted at 100% of its size.
- Note: this is a very niche feature with a very particular behavior that I built primarily for myself
- (even more so than other features in Excalidraw Obsidian - also built primarily for myself 😉).
- This will reset your embedded image to 100% size every time you open the Excalidraw drawing,
or in case you have embedded an Excalidraw drawing on your canvas inserted using this function,
every time you update the embedded drawing, it will be scaled back to 100% size.
- This means that even if you resize the image on the drawing, it will reset to 100% the next time you open
the file or you modify the original embedded object. This feature is useful when you
decompose a drawing into separate Excalidraw files, but when combined onto a single canvas
you want the individual pieces to maintain their actual sizes. I use this feature to
construct Book-on-a-Page summaries from atomic drawings.
- You can drag and drop text from Markdown views onto Excalidraw.
- You can drag and drop web addresses from your browser and they will become links.
You can edit formulas either in Markdown view or by <kbd>CTRL/CMD + Click</kbd> on the formula.
### Image support
- On iOS and Android you can add images from your camera by pressing the add image button in Excalidraw.
- On iOS and Android, you can add images from your camera by pressing the add image button in Excalidraw.
- You can copy/paste images into your drawing. Images will be saved in your vault.
- You can drag and drop images as explained above.
- URL link to images on the web: You can drag images from a webpage to Excalidraw. If you hold down the CTRL button while dropping the image to Excalidraw, the image will not be saved to your vault. Excalidraw will load the image from the URL. Note that if you do not have internet access or if these images are deleted from the internet, they will also disappear from your drawing.
- If you page an image URL to excalidraw (simply click copy on the url, then click paste on the excalidraw canvas), the image will be inserted with a link to the image on the web. Again, the image won't be saved to your vault, only the link.
- If you drop a YouTube video link, it will be converted into a thumbnail photo with an element link pointing to the video.
### Block referencing parts of images
For more details, see this [video](https://youtu.be/yZQoJg2RCKI)
- When referencing an element on the canvas in a link pointing to an Excalidraw file using
- the elementId or the section header (i.e. a Text Element containing the `# <Section title>`)
- e.g. `[[file#^elementID]]`,
- you can add the `group=` prefix,
- e.g. `[[file#^group=elementID]]` or
- the `area=` prefix,
- e.g. `[[file#area=Section heading]]`.
- If the `group=` prefix is found Excalidraw will select the group of elements in the
same group as the element referenced by the elementID (block reference) or the section heading.
- If the `area=` prefix is found Excalidraw will insert a cutout of the image around the referenced element.
- Note that the `area=` selector is not supported when embedding Excalidraw as PNG into your markdown documents.
- Referencing the elementID of a text element without the `group=` or `area=` prefix will
transclude the element as plain text. Referencing a non-Text Element (e.g. rectangle,
ellipse, etc.) without the `group=` or `area=` prefix will result in an Obsidian error
since these elementIds are not present in the Excalidraw markdown file as block
references.
- The elementId or the section header (i.e. a Text Element containing the `# <Section title>`)
- e.g. `[[file#^elementID]]`,
- You can add the `group=` prefix,
- e.g. `[[file#^group=elementID]]` or
- The `area=` prefix,
- e.g. `[[file#area=Section heading]]`.
- If the `group=` prefix is found, Excalidraw will select the group of elements in the
same group as the element referenced by the elementID (block reference) or the section heading.
- If the `area=` prefix is found, Excalidraw will insert a cutout of the image around the referenced element.
- Note that the `area=` selector is not supported when embedding Excalidraw as a PNG into your markdown documents.
- Referencing the elementID of a text element without the `group=` or `area=` prefix will
transclude the element as plain text. Referencing a non-Text Element (e.g. rectangle,
ellipse, etc.) without the `group=` or `area=` prefix will result in an Obsidian error.
since these elementIds are not present in the Excalidraw markdown file as block
references.
### Markdown
- Since 1.2.0 Drawing files are stored in Markdown files
- You can add tags to drawings
- You can add metadata to the YAML front matter of drawings
- Anything you add between the frontmatter and the `# Text Elements` heading will be ignored by Excalidraw, i.e. you can add whatever you like here, it will be preserved as part of the document.
- Excalidraw documents now show in graph view.
- The following front matter keys will customize how the drawing is displayed - overriding general settings:
- `excalidraw-link-prefix: "📍"` preview prefix for internal links
- `excalidraw-url-prefix: "🌐"` preview prefix for external links
- `excalidraw-link-brackets: true|false` whether or not to display brackets around links in preview
- `excalidraw-default-mode: view|zen` Open this document in view mode or zen mode by defult. Default view mode is excellent for presentation slides.
- Frontmatter tags to customize image export at a file level [519](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/519). If these keys are present they will override the default excalidraw embed and export settings.
- `excalidraw-export-transparent: true`: true == Transparent / false == with background.
- `excalidraw-export-dark`: true == Dark mode / false == light mode.
- `excalidraw-export-padding`: Specify the export padding for the image
- `excalidraw-export-pngscale`: This only affects export to PNG. Specify the export scale for the image. The typical range is between 0.5 and 5, but you can experiment with other values as well.
- Since 1.2.0, drawing files are stored in Markdown files.
- You can add tags to drawings.
- You can add metadata to the YAML front matter of drawings.
- Anything you add between the frontmatter and the `# Text Elements` heading will be ignored by Excalidraw, i.e. you can add whatever you like here, and it will be preserved as part of the document.
- Excalidraw documents now show up in graph view.
- The following front matter keys will customize how the drawing is displayed - overriding general settings:
- `excalidraw-link-prefix: "📍"` preview prefix for internal links
- `excalidraw-url-prefix: "🌐"` preview prefix for external links
- `excalidraw-link-brackets: true|false` whether or not to display brackets around links in preview
- `excalidraw-default-mode: view|zen` Open this document in view mode or zen mode by defult. The default view mode is excellent for presentation slides.
- Frontmatter tags to customize image export at a file level [519](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/519). If these keys are present, they will override the default Excalidraw embed and export settings.
- `excalidraw-export-transparent: true`:  true == Transparent / false == with background.
- `excalidraw-export-dark`: true == Dark mode / false == light mode.
- `excalidraw-export-padding`: Specify the export padding for the image.
- `excalidraw-export-pngscale`: This only affects export to PNG. Specify the export scale for the image. The typical range is between 0.5 and 5, but you can experiment with other values as well.
### Embed complete markdown files into your drawings
- Drag from the desired file from the Obsidian file explorer and hold down <kbd>CTRL/CMD</kbd> while dropping the file onto the canvas.
Drag the desired file from the Obsidian file explorer and hold down <kbd>SHIFT</kbd> while dropping the file onto the canvas.
- Use the command palette action: `Insert markdown file from vault`
- Use custom woff, woff2 or TTF font to display the document, you can set the default font to use under Excalidraw Settings.
- You can set a custom css for rendering the snapshot image of your markdown document.
Only operating system standard fonts are supported as the font-family (
[Win10](https://docs.microsoft.com/en-us/typography/fonts/windows_10_font_list),
[Mac & iOS](https://developer.apple.com/fonts/system-fonts/)
), plus you can set one additional custom font using the setting explained above.
- (for a demonstration watch this [video](https://youtu.be/K6qZkTz8GHs) and check out this
- [sample css](https://github.com/zsviczian/obsidian-excalidraw-plugin/discussions/281)).
- To help with styling you can observe the SVG snapshot of the markdown document created by Excalidraw.
- Open Obsidian Developer Console (<kbd>CTRL+Shift+i</kbd>) and
- execute the following command: `ExcalidrawAutomate.mostRecentMarkdownSVG`
- You can control appearance of the embedded markdown file on a file by file
bases by adding the following front matter keys to your markdown document:
- `excalidraw-font: Virgil|Cascadia|font_file_name.extension`
- `excalidraw-font-color: css-color-name|#HEXcolor|any-other-html-standard-format`,
- you can find css color names [here](https://www.w3schools.com/colors/colors_names.asp).
- `excalidraw-border-color: css-color-name|#HEXcolor|any-other-html-standard-format`
- `excalidraw-css: "css-filename|css snippet"`
- Switch to markdown view or use <kbd>CTRL/CMD+ALT/OPT</kbd> click on the image to edit properties of the embed:
- `[[filename#^blockref|WIDTHxMAXHEIGHT]]`
- Use custom woff, woff2, or TTF font to display the document, you can set the default font to use under Excalidraw Settings.
- You can set a custom CSS for rendering the snapshot image of your markdown document. Only operating system-standard fonts are supported as the font-family ([Win10](https://docs.microsoft.com/en-us/typography/fonts/windows_10_font_list), [Mac & iOS](https://developer.apple.com/fonts/system-fonts/)), plus you can set one additional custom font using the setting explained above.
- (for a demonstration, watch this [video](https://youtu.be/K6qZkTz8GHs) and check out this
- [sample css](https://github.com/zsviczian/obsidian-excalidraw-plugin/discussions/281)).
- To help with styling, you can observe the SVG snapshot of the markdown document created by Excalidraw.
- Open Obsidian Developer Console (<kbd>CTRL+Shift+i</kbd>/<kbd>CMD+OPT+i</kbd>) and
- Execute the following command: `ExcalidrawAutomate.mostRecentMarkdownSVG`
- You can control the appearance of the embedded markdown file on a file by file
  bases by adding the following front matter keys to your markdown document:
- `excalidraw-font: Virgil|Cascadia|font_file_name.extension`
- `excalidraw-font-color: css-color-name|#HEXcolor|any-other-html-standard-format`,
- You can find css color names [here](https://www.w3schools.com/colors/colors_names.asp).
- `excalidraw-border-color: css-color-name|#HEXcolor|any-other-html-standard-format`
- `excalidraw-css: "css-filename|css snippet"`
- Switch to markdown view or use <kbd>WIN+CTRL</kbd>/<kbd>CMD+CTRL</kbd> click on the image to edit the properties of the embed:
- `[[filename#^blockref|WIDTHxMAXHEIGHT]]`
### Custom Font, Custom Pen, OCR support, SVG import
- In plugin settings, you can add a custom fourth font. For more details, see this [video](https://youtu.be/eKFmrSQhFA4)
- The plugin includes OCR support using Taskbone OCR. For more details, see this [video](https://youtu.be/7gu4ETx7zro)
- You can convert SVG files into Excalidraw drawings (with some limitation). For more details, see this [video](https://youtu.be/vlC1-iBvIfo)
- You can define custom freedraw pens. See documentation [here].(https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Alternative%20Pens.md), [video](https://youtu.be/uZz5MgzWXiM)
### Script Engine
- Since 1.5.0, you can easily execute ExcalidrawAutomate macros and assign command palette shortcuts to them, using the ScriptEngine. You will find an intro video and a growing library of ready to install scripts [here](https://github.com/zsviczian/obsidian-excalidraw-plugin/tree/master/ea-scripts).
- You can organize scripts into groups on the Obsidian Tools Panel in Excalidraw by moving scripts and accompanying SVG icon files to folders. See the demo [video](https://youtu.be/wTtaXmRJ7wg?t=16).
### Other
- Left-handed mode
- Includes full
- [QuickAdd](https://github.com/chhoumann/quickadd),
- [Templater](https://silentvoid13.github.io/Templater/) and
- [Dataview](https://blacksmithgu.github.io/obsidian-dataview/docs/api/intro/) support through ExcalidrawAutomate.
- Check out the [detailed help + examples](https://zsviczian.github.io/obsidian-excalidraw-plugin/).
- I also have a [YouTube ExcalidrawAutomate Playlist](https://www.youtube.com/playlist?list=PL6mqgtMZ4NP1IR4nXxSlMA4PA5E-qpyHZ) with lots of examples.
- Since 1.5.0 you can easily execute ExcalidrawAutomate macros and assign command palette
shortcuts to them, using the ScriptEngine. You will find an intro video and a growing library
of ready to install scripts
[here](https://github.com/zsviczian/obsidian-excalidraw-plugin/tree/master/ea-scripts).
- REQUIRES AN OBSIDIAN SYNC SUBSCRIPTION: Full drawing file history and synchronization between devices
- Multilanguage support: if you'd like to help out by translating the plugin, please get in contact with me.
@@ -257,7 +276,7 @@ report a bug or request an enhancement.
## Say Thank You
If you are enjoying Excalidraw then please support my work and enthusiasm by buying me a coffee on
If you are enjoying Excalidraw, then please support my work and enthusiasm by buying me a coffee on
[https://ko-fi/zsolt](https://ko-fi.com/zsolt).
Please also help spread the word by sharing about the Obsidian Excalidraw Plugin on Twitter, Reddit,
@@ -272,4 +291,4 @@ You can find me on Twitter [@zsviczian](https://twitter.com/zsviczian), and on m
## Friends of Excalidraw
If you enjoy Excalidraw, consider giving [ExcaliBrain](https://github.com/zsviczian/excalibrain) a try (also available via Obsidian Community Plugins).
<a href="https://youtu.be/gOkniMkDPyM" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/169708346-9e41289d-9536-43ec-8f70-2d2ad2d369d6.png" width="300"/></a>
<a href="https://youtu.be/gOkniMkDPyM" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/169708346-9e41289d-9536-43ec-8f70-2d2ad2d369d6.png" width="300"/></a>

14
TODO.md
View File

@@ -1,14 +0,0 @@
[x] do not embed font into SVG when embedding Excalidraw into other Excalidraw
[x] add ```html <SVG>...</SVG> ``` codeblock to excalidraw markdown
[x] read pre-saved `<SVG>` when generating image preview
[x] update code to adopt change files moving from AppState to App
- Add "files" to legacy excalidraw export
[x] PNG preview
[x] markdown embed SVG 190
[x] markdown embed PNG
[x] embed Excalidraw into other Excalidraw

773
docs/API/ExcalidrawAutomate.d.ts vendored Normal file
View File

@@ -0,0 +1,773 @@
/// <reference types="react" />
import ExcalidrawPlugin from "src/main";
import { FillStyle, StrokeStyle, ExcalidrawElement, ExcalidrawBindableElement, FileId, NonDeletedExcalidrawElement, ExcalidrawImageElement, StrokeRoundness, RoundnessType } from "@zsviczian/excalidraw/types/excalidraw/element/types";
import { Editor, OpenViewState, TFile, WorkspaceLeaf } from "obsidian";
import * as obsidian_module from "obsidian";
import ExcalidrawView, { ExportSettings } from "src/ExcalidrawView";
import { AppState, BinaryFileData, DataURL, ExcalidrawImperativeAPI, Point } from "@zsviczian/excalidraw/types/excalidraw/types";
import { EmbeddedFilesLoader } from "src/EmbeddedFileLoader";
import { ConnectionPoint, DeviceType } from "src/types";
import { ColorMaster } from "colormaster";
import { TInput } from "colormaster/types";
import { ClipboardData } from "@zsviczian/excalidraw/types/excalidraw/clipboard";
import { PaneTarget } from "src/utils/ModifierkeyHelper";
export declare class ExcalidrawAutomate {
/**
* Utility function that returns the Obsidian Module object.
*/
get obsidian(): typeof obsidian_module;
get DEVICE(): DeviceType;
getAttachmentFilepath(filename: string): Promise<string>;
/**
* Prompts the user with a dialog to select new file action.
* - create markdown file
* - create excalidraw file
* - cancel action
* The new file will be relative to this.targetView.file.path, unless parentFile is provided.
* If shouldOpenNewFile is true, the new file will be opened in a workspace leaf.
* targetPane control which leaf will be used for the new file.
* Returns the TFile for the new file or null if the user cancelled the action.
* @param newFileNameOrPath
* @param shouldOpenNewFile
* @param targetPane //type PaneTarget = "active-pane"|"new-pane"|"popout-window"|"new-tab"|"md-properties";
* @param parentFile
* @returns
*/
newFilePrompt(newFileNameOrPath: string, shouldOpenNewFile: boolean, targetPane?: PaneTarget, parentFile?: TFile): Promise<TFile | null>;
/**
* Generates a new Obsidian Leaf following Excalidraw plugin settings such as open in Main Workspace or not, open in adjacent pane if available, etc.
* @param origo // the currently active leaf, the origin of the new leaf
* @param targetPane //type PaneTarget = "active-pane"|"new-pane"|"popout-window"|"new-tab"|"md-properties";
* @returns
*/
getLeaf(origo: WorkspaceLeaf, targetPane?: PaneTarget): WorkspaceLeaf;
/**
* Returns the editor or leaf.view of the currently active embedded obsidian file.
* If view is not provided, ea.targetView is used.
* If the embedded file is a markdown document the function will return
* {file:TFile, editor:Editor} otherwise it will return {view:any}. You can check view type with view.getViewType();
* @param view
* @returns
*/
getActiveEmbeddableViewOrEditor(view?: ExcalidrawView): {
view: any;
} | {
file: TFile;
editor: Editor;
} | null;
plugin: ExcalidrawPlugin;
elementsDict: {
[key: string]: any;
};
imagesDict: {
[key: FileId]: any;
};
mostRecentMarkdownSVG: SVGSVGElement;
style: {
strokeColor: string;
backgroundColor: string;
angle: number;
fillStyle: FillStyle;
strokeWidth: number;
strokeStyle: StrokeStyle;
roughness: number;
opacity: number;
strokeSharpness?: StrokeRoundness;
roundness: null | {
type: RoundnessType;
value?: number;
};
fontFamily: number;
fontSize: number;
textAlign: string;
verticalAlign: string;
startArrowHead: string;
endArrowHead: string;
};
canvas: {
theme: string;
viewBackgroundColor: string;
gridSize: number;
};
colorPalette: {};
constructor(plugin: ExcalidrawPlugin, view?: ExcalidrawView);
/**
*
* @returns the last recorded pointer position on the Excalidraw canvas
*/
getViewLastPointerPosition(): {
x: number;
y: number;
};
/**
*
* @returns
*/
getAPI(view?: ExcalidrawView): ExcalidrawAutomate;
/**
* @param val //0:"hachure", 1:"cross-hatch" 2:"solid"
* @returns
*/
setFillStyle(val: number): "hachure" | "cross-hatch" | "solid";
/**
* @param val //0:"solid", 1:"dashed", 2:"dotted"
* @returns
*/
setStrokeStyle(val: number): "solid" | "dashed" | "dotted";
/**
* @param val //0:"round", 1:"sharp"
* @returns
*/
setStrokeSharpness(val: number): "round" | "sharp";
/**
* @param val //1: Virgil, 2:Helvetica, 3:Cascadia
* @returns
*/
setFontFamily(val: number): "Virgil, Segoe UI Emoji" | "Helvetica, Segoe UI Emoji" | "Cascadia, Segoe UI Emoji" | "LocalFont";
/**
* @param val //0:"light", 1:"dark"
* @returns
*/
setTheme(val: number): "light" | "dark";
/**
* @param objectIds
* @returns
*/
addToGroup(objectIds: string[]): string;
/**
* @param templatePath
*/
toClipboard(templatePath?: string): Promise<void>;
/**
* @param file: TFile
* @returns ExcalidrawScene
*/
getSceneFromFile(file: TFile): Promise<{
elements: ExcalidrawElement[];
appState: AppState;
}>;
/**
* get all elements from ExcalidrawAutomate elementsDict
* @returns elements from elementsDict
*/
getElements(): ExcalidrawElement[];
/**
* get single element from ExcalidrawAutomate elementsDict
* @param id
* @returns
*/
getElement(id: string): ExcalidrawElement;
/**
* create a drawing and save it to filename
* @param params
* filename: if null, default filename as defined in Excalidraw settings
* foldername: if null, default folder as defined in Excalidraw settings
* @returns
*/
create(params?: {
filename?: string;
foldername?: string;
templatePath?: string;
onNewPane?: boolean;
frontmatterKeys?: {
"excalidraw-plugin"?: "raw" | "parsed";
"excalidraw-link-prefix"?: string;
"excalidraw-link-brackets"?: boolean;
"excalidraw-url-prefix"?: string;
"excalidraw-export-transparent"?: boolean;
"excalidraw-export-dark"?: boolean;
"excalidraw-export-padding"?: number;
"excalidraw-export-pngscale"?: number;
"excalidraw-default-mode"?: "view" | "zen";
"excalidraw-onload-script"?: string;
"excalidraw-linkbutton-opacity"?: number;
"excalidraw-autoexport"?: boolean;
};
plaintext?: string;
}): Promise<string>;
/**
*
* @param templatePath
* @param embedFont
* @param exportSettings use ExcalidrawAutomate.getExportSettings(boolean,boolean)
* @param loader use ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?)
* @param theme
* @returns
*/
createSVG(templatePath?: string, embedFont?: boolean, exportSettings?: ExportSettings, loader?: EmbeddedFilesLoader, theme?: string, padding?: number): Promise<SVGSVGElement>;
/**
*
* @param templatePath
* @param scale
* @param exportSettings use ExcalidrawAutomate.getExportSettings(boolean,boolean)
* @param loader use ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?)
* @param theme
* @returns
*/
createPNG(templatePath?: string, scale?: number, exportSettings?: ExportSettings, loader?: EmbeddedFilesLoader, theme?: string, padding?: number): Promise<any>;
/**
*
* @param text
* @param lineLen
* @returns
*/
wrapText(text: string, lineLen: number): string;
private boxedElement;
addIFrame(topX: number, topY: number, width: number, height: number, url?: string, file?: TFile): string;
/**
*
* @param topX
* @param topY
* @param width
* @param height
* @returns
*/
addEmbeddable(topX: number, topY: number, width: number, height: number, url?: string, file?: TFile): string;
/**
*
* @param topX
* @param topY
* @param width
* @param height
* @returns
*/
addRect(topX: number, topY: number, width: number, height: number): string;
/**
*
* @param topX
* @param topY
* @param width
* @param height
* @returns
*/
addDiamond(topX: number, topY: number, width: number, height: number): string;
/**
*
* @param topX
* @param topY
* @param width
* @param height
* @returns
*/
addEllipse(topX: number, topY: number, width: number, height: number): string;
/**
*
* @param topX
* @param topY
* @param width
* @param height
* @returns
*/
addBlob(topX: number, topY: number, width: number, height: number): string;
/**
* Refresh the size of a text element to fit its contents
* @param id - the id of the text element
*/
refreshTextElementSize(id: string): void;
/**
*
* @param topX
* @param topY
* @param text
* @param formatting
* box: if !null, text will be boxed
* @param id
* @returns
*/
addText(topX: number, topY: number, text: string, formatting?: {
wrapAt?: number;
width?: number;
height?: number;
textAlign?: "left" | "center" | "right";
box?: boolean | "box" | "blob" | "ellipse" | "diamond";
boxPadding?: number;
boxStrokeColor?: string;
textVerticalAlign?: "top" | "middle" | "bottom";
}, id?: string): string;
/**
*
* @param points
* @returns
*/
addLine(points: [[x: number, y: number]]): string;
/**
*
* @param points
* @param formatting
* @returns
*/
addArrow(points: [x: number, y: number][], formatting?: {
startArrowHead?: string;
endArrowHead?: string;
startObjectId?: string;
endObjectId?: string;
}): string;
/**
*
* @param topX
* @param topY
* @param imageFile
* @returns
*/
addImage(topX: number, topY: number, imageFile: TFile | string, scale?: boolean, //default is true which will scale the image to MAX_IMAGE_SIZE, false will insert image at 100% of its size
anchor?: boolean): Promise<string>;
/**
*
* @param topX
* @param topY
* @param tex
* @returns
*/
addLaTex(topX: number, topY: number, tex: string): Promise<string>;
/**
*
* @param objectA
* @param connectionA type ConnectionPoint = "top" | "bottom" | "left" | "right" | null
* @param objectB
* @param connectionB when passed null, Excalidraw will automatically decide
* @param formatting
* numberOfPoints: points on the line. Default is 0 ie. line will only have a start and end point
* startArrowHead: "triangle"|"dot"|"arrow"|"bar"|null
* endArrowHead: "triangle"|"dot"|"arrow"|"bar"|null
* padding:
* @returns
*/
connectObjects(objectA: string, connectionA: ConnectionPoint | null, objectB: string, connectionB: ConnectionPoint | null, formatting?: {
numberOfPoints?: number;
startArrowHead?: "triangle" | "dot" | "arrow" | "bar" | null;
endArrowHead?: "triangle" | "dot" | "arrow" | "bar" | null;
padding?: number;
}): string;
/**
* Adds a text label to a line or arrow. Currently only works with a straight (2 point - start & end - line)
* @param lineId id of the line or arrow object in elementsDict
* @param label the label text
* @returns undefined (if unsuccessful) or the id of the new text element
*/
addLabelToLine(lineId: string, label: string): string;
/**
* clear elementsDict and imagesDict only
*/
clear(): void;
/**
* clear() + reset all style values to default
*/
reset(): void;
/**
* returns true if MD file is an Excalidraw file
* @param f
* @returns
*/
isExcalidrawFile(f: TFile): boolean;
targetView: ExcalidrawView;
/**
* sets the target view for EA. All the view operations and the access to Excalidraw API will be performend on this view
* if view is null or undefined, the function will first try setView("active"), then setView("first").
* @param view
* @returns targetView
*/
setView(view?: ExcalidrawView | "first" | "active"): ExcalidrawView;
/**
*
* @returns https://github.com/excalidraw/excalidraw/tree/master/src/packages/excalidraw#ref
*/
getExcalidrawAPI(): any;
/**
* get elements in View
* @returns
*/
getViewElements(): ExcalidrawElement[];
/**
*
* @param elToDelete
* @returns
*/
deleteViewElements(elToDelete: ExcalidrawElement[]): boolean;
/**
* get the selected element in the view, if more are selected, get the first
* @returns
*/
getViewSelectedElement(): any;
/**
*
* @returns
*/
getViewSelectedElements(): any[];
/**
*
* @param el
* @returns TFile file handle for the image element
*/
getViewFileForImageElement(el: ExcalidrawElement): TFile | null;
/**
* copies elements from view to elementsDict for editing
* @param elements
*/
copyViewElementsToEAforEditing(elements: ExcalidrawElement[]): void;
/**
*
* @param forceViewMode
* @returns
*/
viewToggleFullScreen(forceViewMode?: boolean): void;
setViewModeEnabled(enabled: boolean): void;
/**
* This function gives you a more hands on access to Excalidraw.
* @param scene - The scene you want to load to Excalidraw
* @param restore - Use this if the scene includes legacy excalidraw file elements that need to be converted to the latest excalidraw data format (not a typical usecase)
* @returns
*/
viewUpdateScene(scene: {
elements?: ExcalidrawElement[];
appState?: AppState;
files?: BinaryFileData;
commitToHistory?: boolean;
storeAction?: "capture" | "none" | "update";
}, restore?: boolean): void;
/**
* connect an object to the selected element in the view
* @param objectA ID of the element
* @param connectionA
* @param connectionB
* @param formatting
* @returns
*/
connectObjectWithViewSelectedElement(objectA: string, connectionA: ConnectionPoint | null, connectionB: ConnectionPoint | null, formatting?: {
numberOfPoints?: number;
startArrowHead?: "triangle" | "dot" | "arrow" | "bar" | null;
endArrowHead?: "triangle" | "dot" | "arrow" | "bar" | null;
padding?: number;
}): boolean;
/**
* zoom tarteView to fit elements provided as input
* elements === [] will zoom to fit the entire scene
* selectElements toggles whether the elements should be in a selected state at the end of the operation
* @param selectElements
* @param elements
*/
viewZoomToElements(selectElements: boolean, elements: ExcalidrawElement[]): void;
/**
* Adds elements from elementsDict to the current view
* @param repositionToCursor default is false
* @param save default is true
* @param newElementsOnTop controls whether elements created with ExcalidrawAutomate
* are added at the bottom of the stack or the top of the stack of elements already in the view
* Note that elements copied to the view with copyViewElementsToEAforEditing retain their
* position in the stack of elements in the view even if modified using EA
* default is false, i.e. the new elements get to the bottom of the stack
* @param shouldRestoreElements - restore elements - auto-corrects broken, incomplete or old elements included in the update
* @returns
*/
addElementsToView(repositionToCursor?: boolean, save?: boolean, newElementsOnTop?: boolean, shouldRestoreElements?: boolean): Promise<boolean>;
/**
* Register instance of EA to use for hooks with TargetView
* By default ExcalidrawViews will check window.ExcalidrawAutomate for event hooks.
* Using this event you can set a different instance of Excalidraw Automate for hooks
* @returns true if successful
*/
registerThisAsViewEA(): boolean;
/**
* Sets the targetView EA to window.ExcalidrawAutomate
* @returns true if successful
*/
deregisterThisAsViewEA(): boolean;
/**
* If set, this callback is triggered when the user closes an Excalidraw view.
*/
onViewUnloadHook: (view: ExcalidrawView) => void;
/**
* If set, this callback is triggered, when the user changes the view mode.
* You can use this callback in case you want to do something additional when the user switches to view mode and back.
*/
onViewModeChangeHook: (isViewModeEnabled: boolean, view: ExcalidrawView, ea: ExcalidrawAutomate) => void;
/**
* If set, this callback is triggered, when the user hovers a link in the scene.
* You can use this callback in case you want to do something additional when the onLinkHover event occurs.
* This callback must return a boolean value.
* In case you want to prevent the excalidraw onLinkHover action you must return false, it will stop the native excalidraw onLinkHover management flow.
*/
onLinkHoverHook: (element: NonDeletedExcalidrawElement, linkText: string, view: ExcalidrawView, ea: ExcalidrawAutomate) => boolean;
/**
* If set, this callback is triggered, when the user clicks a link in the scene.
* You can use this callback in case you want to do something additional when the onLinkClick event occurs.
* This callback must return a boolean value.
* In case you want to prevent the excalidraw onLinkClick action you must return false, it will stop the native excalidraw onLinkClick management flow.
*/
onLinkClickHook: (element: ExcalidrawElement, linkText: string, event: MouseEvent, view: ExcalidrawView, ea: ExcalidrawAutomate) => boolean;
/**
* If set, this callback is triggered, when Excalidraw receives an onDrop event.
* You can use this callback in case you want to do something additional when the onDrop event occurs.
* This callback must return a boolean value.
* In case you want to prevent the excalidraw onDrop action you must return false, it will stop the native excalidraw onDrop management flow.
*/
onDropHook: (data: {
ea: ExcalidrawAutomate;
event: React.DragEvent<HTMLDivElement>;
draggable: any;
type: "file" | "text" | "unknown";
payload: {
files: TFile[];
text: string;
};
excalidrawFile: TFile;
view: ExcalidrawView;
pointerPosition: {
x: number;
y: number;
};
}) => boolean;
/**
* If set, this callback is triggered, when Excalidraw receives an onPaste event.
* You can use this callback in case you want to do something additional when the
* onPaste event occurs.
* This callback must return a boolean value.
* In case you want to prevent the excalidraw onPaste action you must return false,
* it will stop the native excalidraw onPaste management flow.
*/
onPasteHook: (data: {
ea: ExcalidrawAutomate;
payload: ClipboardData;
event: ClipboardEvent;
excalidrawFile: TFile;
view: ExcalidrawView;
pointerPosition: {
x: number;
y: number;
};
}) => boolean;
/**
* if set, this callback is triggered, when an Excalidraw file is opened
* You can use this callback in case you want to do something additional when the file is opened.
* This will run before the file level script defined in the `excalidraw-onload-script` frontmatter.
*/
onFileOpenHook: (data: {
ea: ExcalidrawAutomate;
excalidrawFile: TFile;
view: ExcalidrawView;
}) => Promise<void>;
/**
* if set, this callback is triggered, when an Excalidraw file is created
* see also: https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1124
*/
onFileCreateHook: (data: {
ea: ExcalidrawAutomate;
excalidrawFile: TFile;
view: ExcalidrawView;
}) => Promise<void>;
/**
* If set, this callback is triggered whenever the active canvas color changes
*/
onCanvasColorChangeHook: (ea: ExcalidrawAutomate, view: ExcalidrawView, //the excalidraw view
color: string) => void;
/**
* utility function to generate EmbeddedFilesLoader object
* @param isDark
* @returns
*/
getEmbeddedFilesLoader(isDark?: boolean): EmbeddedFilesLoader;
/**
* utility function to generate ExportSettings object
* @param withBackground
* @param withTheme
* @returns
*/
getExportSettings(withBackground: boolean, withTheme: boolean): ExportSettings;
/**
* get bounding box of elements
* bounding box is the box encapsulating all of the elements completely
* @param elements
* @returns
*/
getBoundingBox(elements: ExcalidrawElement[]): {
topX: number;
topY: number;
width: number;
height: number;
};
/**
* elements grouped by the highest level groups
* @param elements
* @returns
*/
getMaximumGroups(elements: ExcalidrawElement[]): ExcalidrawElement[][];
/**
* gets the largest element from a group. useful when a text element is grouped with a box, and you want to connect an arrow to the box
* @param elements
* @returns
*/
getLargestElement(elements: ExcalidrawElement[]): ExcalidrawElement;
/**
* @param element
* @param a
* @param b
* @param gap
* @returns 2 or 0 intersection points between line going through `a` and `b`
* and the `element`, in ascending order of distance from `a`.
*/
intersectElementWithLine(element: ExcalidrawBindableElement, a: readonly [number, number], b: readonly [number, number], gap?: number): Point[];
/**
* Gets the groupId for the group that contains all the elements, or null if such a group does not exist
* @param elements
* @returns null or the groupId
*/
getCommonGroupForElements(elements: ExcalidrawElement[]): string;
/**
* Gets all the elements from elements[] that share one or more groupIds with element.
* @param element
* @param elements - typically all the non-deleted elements in the scene
* @returns
*/
getElementsInTheSameGroupWithElement(element: ExcalidrawElement, elements: ExcalidrawElement[]): ExcalidrawElement[];
/**
* Gets all the elements from elements[] that are contained in the frame.
* @param element
* @param elements - typically all the non-deleted elements in the scene
* @returns
*/
getElementsInFrame(frameElement: ExcalidrawElement, elements: ExcalidrawElement[]): ExcalidrawElement[];
/**
* See OCR plugin for example on how to use scriptSettings
* Set by the ScriptEngine
*/
activeScript: string;
/**
*
* @returns script settings. Saves settings in plugin settings, under the activeScript key
*/
getScriptSettings(): {};
/**
* sets script settings.
* @param settings
* @returns
*/
setScriptSettings(settings: any): Promise<void>;
/**
* Open a file in a new workspaceleaf or reuse an existing adjacent leaf depending on Excalidraw Plugin Settings
* @param file
* @param openState - if not provided {active: true} will be used
* @returns
*/
openFileInNewOrAdjacentLeaf(file: TFile, openState?: OpenViewState): WorkspaceLeaf;
/**
* measure text size based on current style settings
* @param text
* @returns
*/
measureText(text: string): {
width: number;
height: number;
};
/**
* Returns the size of the image element at 100% (i.e. the original size)
* @param imageElement an image element from the active scene on targetView
*/
getOriginalImageSize(imageElement: ExcalidrawImageElement): Promise<{
width: number;
height: number;
}>;
/**
* verifyMinimumPluginVersion returns true if plugin version is >= than required
* recommended use:
* if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.20")) {new Notice("message");return;}
* @param requiredVersion
* @returns
*/
verifyMinimumPluginVersion(requiredVersion: string): boolean;
/**
* Check if view is instance of ExcalidrawView
* @param view
* @returns
*/
isExcalidrawView(view: any): boolean;
/**
* sets selection in view
* @param elements
* @returns
*/
selectElementsInView(elements: ExcalidrawElement[] | string[]): void;
/**
* @returns an 8 character long random id
*/
generateElementId(): string;
/**
* @param element
* @returns a clone of the element with a new id
*/
cloneElement(element: ExcalidrawElement): ExcalidrawElement;
/**
* Moves the element to a specific position in the z-index
*/
moveViewElementToZIndex(elementId: number, newZIndex: number): void;
/**
* Deprecated. Use getCM / ColorMaster instead
* @param color
* @returns
*/
hexStringToRgb(color: string): number[];
/**
* Deprecated. Use getCM / ColorMaster instead
* @param color
* @returns
*/
rgbToHexString(color: number[]): string;
/**
* Deprecated. Use getCM / ColorMaster instead
* @param color
* @returns
*/
hslToRgb(color: number[]): number[];
/**
* Deprecated. Use getCM / ColorMaster instead
* @param color
* @returns
*/
rgbToHsl(color: number[]): number[];
/**
*
* @param color
* @returns
*/
colorNameToHex(color: string): string;
/**
* https://github.com/lbragile/ColorMaster
* @param color
* @returns
*/
getCM(color: TInput): ColorMaster;
importSVG(svgString: string): boolean;
}
export declare function initExcalidrawAutomate(plugin: ExcalidrawPlugin): Promise<ExcalidrawAutomate>;
export declare function destroyExcalidrawAutomate(): void;
export declare function _measureText(newText: string, fontSize: number, fontFamily: number, lineHeight: number): {
w: number;
h: number;
baseline: number;
};
export declare const generatePlaceholderDataURL: (width: number, height: number) => DataURL;
export declare function createPNG(templatePath: string, scale: number, exportSettings: ExportSettings, loader: EmbeddedFilesLoader, forceTheme: string, canvasTheme: string, canvasBackgroundColor: string, automateElements: ExcalidrawElement[], plugin: ExcalidrawPlugin, depth: number, padding?: number, imagesDict?: any): Promise<Blob>;
export declare function createSVG(templatePath: string, embedFont: boolean, exportSettings: ExportSettings, loader: EmbeddedFilesLoader, forceTheme: string, canvasTheme: string, canvasBackgroundColor: string, automateElements: ExcalidrawElement[], plugin: ExcalidrawPlugin, depth: number, padding?: number, imagesDict?: any, convertMarkdownLinksToObsidianURLs?: boolean): Promise<SVGSVGElement>;
export declare function estimateBounds(elements: ExcalidrawElement[]): [number, number, number, number];
export declare function repositionElementsToCursor(elements: ExcalidrawElement[], newPosition: {
x: number;
y: number;
}, center: boolean, api: ExcalidrawImperativeAPI): ExcalidrawElement[];
export declare const insertLaTeXToView: (view: ExcalidrawView) => void;
export declare const search: (view: ExcalidrawView) => Promise<void>;
/**
*
* @param elements
* @param query
* @param exactMatch - when searching for section header exactMatch should be set to true
* @returns the elements matching the query
*/
export declare const getTextElementsMatchingQuery: (elements: ExcalidrawElement[], query: string[], exactMatch?: boolean) => ExcalidrawElement[];
/**
*
* @param elements
* @param query
* @param exactMatch - when searching for section header exactMatch should be set to true
* @returns the elements matching the query
*/
export declare const getFrameElementsMatchingQuery: (elements: ExcalidrawElement[], query: string[], exactMatch?: boolean) => ExcalidrawElement[];
export declare const cloneElement: (el: ExcalidrawElement) => any;
export declare const verifyMinimumPluginVersion: (requiredVersion: string) => boolean;

View File

@@ -2,36 +2,110 @@
## Attributes and functions overview
Here's the interface implemented by ExcalidrawAutomate:
```typescript
export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
plugin: ExcalidrawPlugin;
targetView: ExcalidrawView = null; //the view currently edited
elementsDict: {[key:string]:any}; //contains the ExcalidrawElements currently edited in Automate indexed by el.id
imagesDict: {[key: FileId]: any}; //the images files including DataURL, indexed by fileId
mostRecentMarkdownSVG:SVGSVGElement = null; //Markdown renderer will drop a copy of the most recent SVG here for debugging purposes
style: {
strokeColor: string; //https://www.w3schools.com/colors/default.asp
backgroundColor: string;
angle: number; //radian
fillStyle: FillStyle; //type FillStyle = "hachure" | "cross-hatch" | "solid"
strokeWidth: number;
strokeStyle: StrokeStyle; //type StrokeStyle = "solid" | "dashed" | "dotted"
roughness: number;
opacity: number;
strokeSharpness: StrokeSharpness; //type StrokeSharpness = "round" | "sharp"
fontFamily: number; //1: Virgil, 2:Helvetica, 3:Cascadia, 4:LocalFont
fontSize: number;
textAlign: string; //"left"|"right"|"center"
verticalAlign: string; //"top"|"bottom"|"middle" :for future use, has no effect currently
startArrowHead: string; //"triangle"|"dot"|"arrow"|"bar"|null
endArrowHead: string;
};
canvas: {
theme: string; //"dark"|"light"
viewBackgroundColor: string;
gridSize: number;
};
You can find the source file here: [ExcalidrawAutomate.d.ts](ExcalidrawAutomate.d.ts).
```javascript
/// <reference types="react" />
import ExcalidrawPlugin from "src/main";
import { FillStyle, StrokeStyle, ExcalidrawElement, ExcalidrawBindableElement, FileId, NonDeletedExcalidrawElement, ExcalidrawImageElement, StrokeRoundness, RoundnessType } from "@zsviczian/excalidraw/types/element/types";
import { Editor, OpenViewState, TFile, WorkspaceLeaf } from "obsidian";
import * as obsidian_module from "obsidian";
import ExcalidrawView, { ExportSettings } from "src/ExcalidrawView";
import { AppState, BinaryFileData, DataURL, ExcalidrawImperativeAPI, Point } from "@zsviczian/excalidraw/types/types";
import { EmbeddedFilesLoader } from "src/EmbeddedFileLoader";
import { ConnectionPoint, DeviceType } from "src/types";
import { ColorMaster } from "colormaster";
import { TInput } from "colormaster/types";
import { ClipboardData } from "@zsviczian/excalidraw/types/clipboard";
import { PaneTarget } from "src/utils/ModifierkeyHelper";
export declare class ExcalidrawAutomate {
/**
* Utility function that returns the Obsidian Module object.
*/
get obsidian(): typeof obsidian_module;
get DEVICE(): DeviceType;
getAttachmentFilepath(filename: string): Promise<string>;
/**
* Prompts the user with a dialog to select new file action.
* - create markdown file
* - create excalidraw file
* - cancel action
* The new file will be relative to this.targetView.file.path, unless parentFile is provided.
* If shouldOpenNewFile is true, the new file will be opened in a workspace leaf.
* targetPane control which leaf will be used for the new file.
* Returns the TFile for the new file or null if the user cancelled the action.
* @param newFileNameOrPath
* @param shouldOpenNewFile
* @param targetPane //type PaneTarget = "active-pane"|"new-pane"|"popout-window"|"new-tab"|"md-properties";
* @param parentFile
* @returns
*/
newFilePrompt(newFileNameOrPath: string, shouldOpenNewFile: boolean, targetPane?: PaneTarget, parentFile?: TFile): Promise<TFile | null>;
/**
* Generates a new Obsidian Leaf following Excalidraw plugin settings such as open in Main Workspace or not, open in adjacent pane if available, etc.
* @param origo // the currently active leaf, the origin of the new leaf
* @param targetPane //type PaneTarget = "active-pane"|"new-pane"|"popout-window"|"new-tab"|"md-properties";
* @returns
*/
getLeaf(origo: WorkspaceLeaf, targetPane?: PaneTarget): WorkspaceLeaf;
/**
* Returns the editor or leaf.view of the currently active embedded obsidian file.
* If view is not provided, ea.targetView is used.
* If the embedded file is a markdown document the function will return
* {file:TFile, editor:Editor} otherwise it will return {view:any}. You can check view type with view.getViewType();
* @param view
* @returns
*/
getActiveEmbeddableViewOrEditor(view?: ExcalidrawView): {
view: any;
} | {
file: TFile;
editor: Editor;
} | null;
plugin: ExcalidrawPlugin;
elementsDict: {
[key: string]: any;
};
imagesDict: {
[key: FileId]: any;
};
mostRecentMarkdownSVG: SVGSVGElement;
style: {
strokeColor: string;
backgroundColor: string;
angle: number;
fillStyle: FillStyle;
strokeWidth: number;
strokeStyle: StrokeStyle;
roughness: number;
opacity: number;
strokeSharpness?: StrokeRoundness;
roundness: null | {
type: RoundnessType;
value?: number;
};
fontFamily: number;
fontSize: number;
textAlign: string;
verticalAlign: string;
startArrowHead: string;
endArrowHead: string;
};
canvas: {
theme: string;
viewBackgroundColor: string;
gridSize: number;
};
colorPalette: {};
constructor(plugin: ExcalidrawPlugin, view?: ExcalidrawView);
/**
*
* @returns the last recorded pointer position on the Excalidraw canvas
*/
getViewLastPointerPosition(): {
x: number;
y: number;
};
/**
*
* @returns
@@ -71,9 +145,17 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
* @param templatePath
*/
toClipboard(templatePath?: string): Promise<void>;
/**
* @param file: TFile
* @returns ExcalidrawScene
*/
getSceneFromFile(file: TFile): Promise<{
elements: ExcalidrawElement[];
appState: AppState;
}>;
/**
* get all elements from ExcalidrawAutomate elementsDict
* @returns elements from elemenetsDict
* @returns elements from elementsDict
*/
getElements(): ExcalidrawElement[];
/**
@@ -101,10 +183,14 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
"excalidraw-url-prefix"?: string;
"excalidraw-export-transparent"?: boolean;
"excalidraw-export-dark"?: boolean;
"excalidraw-export-svgpadding"?: number;
"excalidraw-export-padding"?: number;
"excalidraw-export-pngscale"?: number;
"excalidraw-default-mode"?: "view" | "zen";
"excalidraw-onload-script"?: string;
"excalidraw-linkbutton-opacity"?: number;
"excalidraw-autoexport"?: boolean;
};
plaintext?: string;
}): Promise<string>;
/**
*
@@ -134,6 +220,16 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
*/
wrapText(text: string, lineLen: number): string;
private boxedElement;
addIFrame(topX: number, topY: number, width: number, height: number, url?: string, file?: TFile): string;
/**
*
* @param topX
* @param topY
* @param width
* @param height
* @returns
*/
addEmbeddable(topX: number, topY: number, width: number, height: number, url?: string, file?: TFile): string;
/**
*
* @param topX
@@ -170,6 +266,11 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
* @returns
*/
addBlob(topX: number, topY: number, width: number, height: number): string;
/**
* Refresh the size of a text element to fit its contents
* @param id - the id of the text element
*/
refreshTextElementSize(id: string): void;
/**
*
* @param topX
@@ -184,9 +285,11 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
wrapAt?: number;
width?: number;
height?: number;
textAlign?: string;
textAlign?: "left" | "center" | "right";
box?: boolean | "box" | "blob" | "ellipse" | "diamond";
boxPadding?: number;
boxStrokeColor?: string;
textVerticalAlign?: "top" | "middle" | "bottom";
}, id?: string): string;
/**
*
@@ -213,7 +316,8 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
* @param imageFile
* @returns
*/
addImage(topX: number, topY: number, imageFile: TFile): Promise<string>;
addImage(topX: number, topY: number, imageFile: TFile | string, scale?: boolean, //default is true which will scale the image to MAX_IMAGE_SIZE, false will insert image at 100% of its size
anchor?: boolean): Promise<string>;
/**
*
* @param topX
@@ -262,12 +366,14 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
* @returns
*/
isExcalidrawFile(f: TFile): boolean;
targetView: ExcalidrawView;
/**
*
* sets the target view for EA. All the view operations and the access to Excalidraw API will be performend on this view
* if view is null or undefined, the function will first try setView("active"), then setView("first").
* @param view
* @returns
* @returns targetView
*/
setView(view: ExcalidrawView | "first" | "active"): ExcalidrawView;
setView(view?: ExcalidrawView | "first" | "active"): ExcalidrawView;
/**
*
* @returns https://github.com/excalidraw/excalidraw/tree/master/src/packages/excalidraw#ref
@@ -311,6 +417,19 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
* @returns
*/
viewToggleFullScreen(forceViewMode?: boolean): void;
setViewModeEnabled(enabled: boolean): void;
/**
* This function gives you a more hands on access to Excalidraw.
* @param scene - The scene you want to load to Excalidraw
* @param restore - Use this if the scene includes legacy excalidraw file elements that need to be converted to the latest excalidraw data format (not a typical usecase)
* @returns
*/
viewUpdateScene(scene: {
elements?: ExcalidrawElement[];
appState?: AppState;
files?: BinaryFileData;
commitToHistory?: boolean;
}, restore?: boolean): void;
/**
* connect an object to the selected element in the view
* @param objectA ID of the element
@@ -325,6 +444,14 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
endArrowHead?: "triangle" | "dot" | "arrow" | "bar" | null;
padding?: number;
}): boolean;
/**
* zoom tarteView to fit elements provided as input
* elements === [] will zoom to fit the entire scene
* selectElements toggles whether the elements should be in a selected state at the end of the operation
* @param selectElements
* @param elements
*/
viewZoomToElements(selectElements: boolean, elements: ExcalidrawElement[]): void;
/**
* Adds elements from elementsDict to the current view
* @param repositionToCursor default is false
@@ -334,9 +461,10 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
* Note that elements copied to the view with copyViewElementsToEAforEditing retain their
* position in the stack of elements in the view even if modified using EA
* default is false, i.e. the new elements get to the bottom of the stack
* @param shouldRestoreElements - restore elements - auto-corrects broken, incomplete or old elements included in the update
* @returns
*/
addElementsToView(repositionToCursor?: boolean, save?: boolean, newElementsOnTop?: boolean): Promise<boolean>;
addElementsToView(repositionToCursor?: boolean, save?: boolean, newElementsOnTop?: boolean, shouldRestoreElements?: boolean): Promise<boolean>;
/**
* Register instance of EA to use for hooks with TargetView
* By default ExcalidrawViews will check window.ExcalidrawAutomate for event hooks.
@@ -362,7 +490,7 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
* If set, this callback is triggered, when the user hovers a link in the scene.
* You can use this callback in case you want to do something additional when the onLinkHover event occurs.
* This callback must return a boolean value.
* In case you want to prevent the excalidraw onLinkHover action you must return true, it will stop the native excalidraw onLinkHover management flow.
* In case you want to prevent the excalidraw onLinkHover action you must return false, it will stop the native excalidraw onLinkHover management flow.
*/
onLinkHoverHook: (element: NonDeletedExcalidrawElement, linkText: string, view: ExcalidrawView, ea: ExcalidrawAutomate) => boolean;
/**
@@ -394,6 +522,49 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
y: number;
};
}) => boolean;
/**
* If set, this callback is triggered, when Excalidraw receives an onPaste event.
* You can use this callback in case you want to do something additional when the
* onPaste event occurs.
* This callback must return a boolean value.
* In case you want to prevent the excalidraw onPaste action you must return false,
* it will stop the native excalidraw onPaste management flow.
*/
onPasteHook: (data: {
ea: ExcalidrawAutomate;
payload: ClipboardData;
event: ClipboardEvent;
excalidrawFile: TFile;
view: ExcalidrawView;
pointerPosition: {
x: number;
y: number;
};
}) => boolean;
/**
* if set, this callback is triggered, when an Excalidraw file is opened
* You can use this callback in case you want to do something additional when the file is opened.
* This will run before the file level script defined in the `excalidraw-onload-script` frontmatter.
*/
onFileOpenHook: (data: {
ea: ExcalidrawAutomate;
excalidrawFile: TFile;
view: ExcalidrawView;
}) => Promise<void>;
/**
* if set, this callback is triggered, when an Excalidraw file is created
* see also: https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1124
*/
onFileCreateHook: (data: {
ea: ExcalidrawAutomate;
excalidrawFile: TFile;
view: ExcalidrawView;
}) => Promise<void>;
/**
* If set, this callback is triggered whenever the active canvas color changes
*/
onCanvasColorChangeHook: (ea: ExcalidrawAutomate, view: ExcalidrawView, //the excalidraw view
color: string) => void;
/**
* utility function to generate EmbeddedFilesLoader object
* @param isDark
@@ -431,6 +602,15 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
* @returns
*/
getLargestElement(elements: ExcalidrawElement[]): ExcalidrawElement;
/**
* @param element
* @param a
* @param b
* @param gap
* @returns 2 or 0 intersection points between line going through `a` and `b`
* and the `element`, in ascending order of distance from `a`.
*/
intersectElementWithLine(element: ExcalidrawBindableElement, a: readonly [number, number], b: readonly [number, number], gap?: number): Point[];
/**
* Gets the groupId for the group that contains all the elements, or null if such a group does not exist
* @param elements
@@ -445,14 +625,12 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
*/
getElementsInTheSameGroupWithElement(element: ExcalidrawElement, elements: ExcalidrawElement[]): ExcalidrawElement[];
/**
* Gets all the elements from elements[] that are contained in the frame.
* @param element
* @param a
* @param b
* @param gap
* @returns 2 or 0 intersection points between line going through `a` and `b`
* and the `element`, in ascending order of distance from `a`.
* @param elements - typically all the non-deleted elements in the scene
* @returns
*/
intersectElementWithLine(element: ExcalidrawBindableElement, a: readonly [number, number], b: readonly [number, number], gap?: number): Point[];
getElementsInFrame(frameElement: ExcalidrawElement, elements: ExcalidrawElement[]): ExcalidrawElement[];
/**
* See OCR plugin for example on how to use scriptSettings
* Set by the ScriptEngine
@@ -472,9 +650,10 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
/**
* Open a file in a new workspaceleaf or reuse an existing adjacent leaf depending on Excalidraw Plugin Settings
* @param file
* @param openState - if not provided {active: true} will be used
* @returns
*/
openFileInNewOrAdjacentLeaf(file: TFile): WorkspaceLeaf;
openFileInNewOrAdjacentLeaf(file: TFile, openState?: OpenViewState): WorkspaceLeaf;
/**
* measure text size based on current style settings
* @param text
@@ -484,6 +663,14 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
width: number;
height: number;
};
/**
* Returns the size of the image element at 100% (i.e. the original size)
* @param imageElement an image element from the active scene on targetView
*/
getOriginalImageSize(imageElement: ExcalidrawImageElement): Promise<{
width: number;
height: number;
}>;
/**
* verifyMinimumPluginVersion returns true if plugin version is >= than required
* recommended use:
@@ -503,7 +690,7 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
* @param elements
* @returns
*/
selectElementsInView(elements: ExcalidrawElement[]): void;
selectElementsInView(elements: ExcalidrawElement[] | string[]): void;
/**
* @returns an 8 character long random id
*/
@@ -518,25 +705,25 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
*/
moveViewElementToZIndex(elementId: number, newZIndex: number): void;
/**
*
* Deprecated. Use getCM / ColorMaster instead
* @param color
* @returns
*/
hexStringToRgb(color: string): number[];
/**
*
* Deprecated. Use getCM / ColorMaster instead
* @param color
* @returns
*/
rgbToHexString(color: number[]): string;
/**
*
* Deprecated. Use getCM / ColorMaster instead
* @param color
* @returns
*/
hslToRgb(color: number[]): number[];
/**
*
* Deprecated. Use getCM / ColorMaster instead
* @param color
* @returns
*/
@@ -547,5 +734,47 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
* @returns
*/
colorNameToHex(color: string): string;
}```
/**
* https://github.com/lbragile/ColorMaster
* @param color
* @returns
*/
getCM(color: TInput): ColorMaster;
importSVG(svgString: string): boolean;
}
export declare function initExcalidrawAutomate(plugin: ExcalidrawPlugin): Promise<ExcalidrawAutomate>;
export declare function destroyExcalidrawAutomate(): void;
export declare function _measureText(newText: string, fontSize: number, fontFamily: number, lineHeight: number): {
w: number;
h: number;
baseline: number;
};
export declare const generatePlaceholderDataURL: (width: number, height: number) => DataURL;
export declare function createPNG(templatePath: string, scale: number, exportSettings: ExportSettings, loader: EmbeddedFilesLoader, forceTheme: string, canvasTheme: string, canvasBackgroundColor: string, automateElements: ExcalidrawElement[], plugin: ExcalidrawPlugin, depth: number, padding?: number, imagesDict?: any): Promise<Blob>;
export declare function createSVG(templatePath: string, embedFont: boolean, exportSettings: ExportSettings, loader: EmbeddedFilesLoader, forceTheme: string, canvasTheme: string, canvasBackgroundColor: string, automateElements: ExcalidrawElement[], plugin: ExcalidrawPlugin, depth: number, padding?: number, imagesDict?: any, convertMarkdownLinksToObsidianURLs?: boolean): Promise<SVGSVGElement>;
export declare function estimateBounds(elements: ExcalidrawElement[]): [number, number, number, number];
export declare function repositionElementsToCursor(elements: ExcalidrawElement[], newPosition: {
x: number;
y: number;
}, center: boolean, api: ExcalidrawImperativeAPI): ExcalidrawElement[];
export declare const insertLaTeXToView: (view: ExcalidrawView) => void;
export declare const search: (view: ExcalidrawView) => Promise<void>;
/**
*
* @param elements
* @param query
* @param exactMatch - when searching for section header exactMatch should be set to true
* @returns the elements matching the query
*/
export declare const getTextElementsMatchingQuery: (elements: ExcalidrawElement[], query: string[], exactMatch?: boolean) => ExcalidrawElement[];
/**
*
* @param elements
* @param query
* @param exactMatch - when searching for section header exactMatch should be set to true
* @returns the elements matching the query
*/
export declare const getFrameElementsMatchingQuery: (elements: ExcalidrawElement[], query: string[], exactMatch?: boolean) => ExcalidrawElement[];
export declare const cloneElement: (el: ExcalidrawElement) => any;
export declare const verifyMinimumPluginVersion: (requiredVersion: string) => boolean;
```

View File

@@ -88,4 +88,4 @@ This is relevant when setting a fix height using the `addText()` function.
### startArrowHead, endArrowHead
String. Valid values are "arrow", "bar", "dot", and "none". Specifies the beginning and ending of an arrow.
This is relavant when using the `addArrow()` and the `connectObjects()` functions.
This is relevant when using the `addArrow()` and the `connectObjects()` functions.

View File

@@ -21,7 +21,7 @@ The second line resets ExcalidrawAutomate to defaults. This is important as you
You can change the styling between adding different elements. My logic for separating element styling and creation is based on the assumption that you will probably set a stroke color, stroke style, stroke roughness, etc. and draw most of your elements using that. There would be no point in setting all these parameters each time you add an element.
### Before we dive deeper, here are three a simple example [Templater](https://github.com/SilentVoid13/Templater) scripts
### Before we dive deeper, here are three simple example [Templater](https://github.com/SilentVoid13/Templater) scripts
#### Create a new drawing with custom name, in a custom folder, using a template
This simple script gives you significant additional flexibility over Excalidraw Plugin settings to name your drawings, place them into folders, and to apply templates.

View File

@@ -22,7 +22,7 @@ Places the generated drawing to the clipboard. Useful when you don't want to cre
```typescript
getElements():ExcalidrawElement[];
```
Returns the elements in ExcalidrawAutomate as an array of ExcalidrawElements. This format is usefull when working with ExcalidrawRef.
Returns the elements in ExcalidrawAutomate as an array of ExcalidrawElements. This format is useful when working with ExcalidrawRef.
### getElement()
```typescript
@@ -156,7 +156,7 @@ You first need to set the view calling `setView()`.
Gets the array of selected elements in the scene. Returns [] if no elements are selected.
Note: you can call `getExcalidrawAPI().getSceneElements()` to retreive all the elements in the scene.
Note: you can call `getExcalidrawAPI().getSceneElements()` to retrieve all the elements in the scene.
#### viewToggleFullScreen()
```typescript
@@ -178,7 +178,7 @@ Same as `connectObjects()`, but ObjectB is the currently selected element in the
async addElementsToView(repositionToCursor:boolean=false, save:boolean=false):Promise<boolean>
```
Adds elements created with ExcalidrawAutomate to the target ExcalidrawView.
`repositionToCursor` dafault is false
`repositionToCursor` default is false
- true: the elements will be moved such that the center point of the elements will be aligned with the current position of the pointer on ExcalidrawView. You can point and place elements to a desired location in your drawing using this switch.
- false: elements will be positioned as defined by the x&y coordinates of each element.
@@ -204,7 +204,7 @@ onDropHook (data: {
```
Callback function triggered when an draggable item is dropped on Excalidraw.
The function should return a boolean value. True if the drop was handled by the hook and futher native processing should be stopped, and false if Excalidraw should continue with the processing of the drop.
The function should return a boolean value. True if the drop was handled by the hook and further native processing should be stopped, and false if Excalidraw should continue with the processing of the drop.
type of drop can be one of:
- "file" if a file from Obsidian file explorer is dropped onto Excalidraw. In this case payload.files will contain the list of files dropped.
- "text" if a link (e.g. url, or wiki link) or other text is dropped. In this case payload.text will contain the received string

View File

@@ -1,6 +1,6 @@
# [◀ Excalidraw Automate How To](../readme.md)
## Generating a simple mindmap from a text outline
This is a slightly more elaborate example. This will generate an a mindmap from a tabulated outline.
This is a slightly more elaborate example. This will generate a mindmap from a tabulated outline.
### Output
![Drawing 2021-05-05 20 52 34](https://user-images.githubusercontent.com/14358394/117194124-00a69d00-ade4-11eb-8b75-5e18a9cbc3cd.png)

View File

@@ -21,7 +21,7 @@ This will allow you to assign hotkeys to your favorite scripts just like to any
## Script development
An Excalidraw script will automatically receive two objects:
- `ea`: The Script Enginge will initialize the `ea` object including setting the active view to the View from which the script was called.
- `ea`: The Script Engine will initialize the `ea` object including setting the active view to the View from which the script was called.
- `utils`: I have borrowed functions exposed on utils from [QuickAdd](https://github.com/chhoumann/quickadd/blob/master/docs/QuickAddAPI.md), though currently not all QuickAdd utility functions are implemented in Excalidraw. As of now, these are the available functions. See the example below for details.
- `inputPrompt: (header: string, placeholder?: string, value?: string, buttons?: [{caption:string, action:Function}])`
- Opens a prompt that asks for an input. Returns a string with the input.

View File

@@ -8,7 +8,7 @@ With a little work, using ExcalidrawAutomate you can generate simple mindmaps, b
## API documentation
- **start here** [Introduction to the API](API/introduction.md)
- [Overview of Attributes and Functions](API/attributes_functions_overview.md)
- [Element Sytle](API/element_style.md)
- [Element Style](API/element_style.md)
- [Canvas Style](API/canvas_style.md)
- [Adding Objects](API/objects.md)
- [Utility Functions](API/utility.md)

View File

@@ -67,6 +67,7 @@ if(!isFirst) {
ea.copyViewElementsToEAforEditing([fromElement]);
const previousTextElements = elements.filter((el)=>el.type==="text");
const previousRectElements = elements.filter((el)=> ['ellipse', 'rectangle', 'diamond'].includes(el.type));
if(previousTextElements.length>0) {
const el = previousTextElements[0];
ea.style.strokeColor = el.strokeColor;
@@ -77,7 +78,7 @@ if(!isFirst) {
textWidth = ea.measureText(text).width;
id = ea.addText(
fixWidth
fixWidth
? fromElement.x+fromElement.width/2-width/2
: fromElement.x+fromElement.width/2-textWidth/2-textPadding,
fromElement.y+fromElement.height+gapBetweenElements,
@@ -85,7 +86,8 @@ if(!isFirst) {
{
wrapAt: wrapLineLen,
textAlign: "center",
box: "rectangle",
textVerticalAlign: "middle",
box: previousRectElements.length > 0 ? previousRectElements[0].type : false,
...fixWidth
? {width: width, boxPadding:0}
: {boxPadding: textPadding}
@@ -104,14 +106,19 @@ if(!isFirst) {
}
);
const rect = ea.getElement(id);
rect.strokeColor = fromElement.strokeColor;
rect.strokeWidth = fromElement.strokeWidth;
rect.strokeStyle = fromElement.strokeStyle;
rect.roughness = fromElement.roughness;
rect.strokeSharpness = fromElement.strokeSharpness;
rect.backgroundColor = fromElement.backgroundColor;
rect.fillStyle = fromElement.fillStyle;
if (previousRectElements.length>0) {
const rect = ea.getElement(id);
rect.strokeColor = fromElement.strokeColor;
rect.strokeWidth = fromElement.strokeWidth;
rect.strokeStyle = fromElement.strokeStyle;
rect.roughness = fromElement.roughness;
rect.roundness = fromElement.roundness;
rect.strokeSharpness = fromElement.strokeSharpness;
rect.backgroundColor = fromElement.backgroundColor;
rect.fillStyle = fromElement.fillStyle;
rect.width = fromElement.width;
rect.height = fromElement.height;
}
await ea.addElementsToView(false,false);
} else {
@@ -122,6 +129,7 @@ if(!isFirst) {
{
wrapAt: wrapLineLen,
textAlign: "center",
textVerticalAlign: "middle",
box: "rectangle",
boxPadding: textPadding,
...fixWidth?{width: width}:null

View File

@@ -0,0 +1,208 @@
/*
IF YOU ACCIDENTLY MODIFY THIS FILE AND IT STOPS WORKING, SIMPLY DOWNLOAD IT AGAIN FROM THE SCRIPT LIBRARY.
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-alternative-pens.jpg)
# How to create a new pen template
It takes a bit of experimentation and skill to create a new pen, so be patient.
1. Create a folder in your Vault for your pen options. The default is `Excalidraw/Pens`.
2. Create a new markdown file in your in the `pen folder` (e.g. `My pen`).
3. Copy the following template to the markdown file.
```json
{
"highlighter": true,
"constantPressure": false,
"hasOutline": true,
"outlineWidth": 4,
"options": PASTE_PREFECT_FREEHAND_OPTIONS_HERE
}
```
4. If you don't want your pen to have an outline around your line, change `hasOutline` to `false`. You can also modify `outlineWidth` if you want a thinner or thicker outline around your line.
5. If you want your pen to be pressure sensitive (when drawing with a mouse the pressure is simulated based on the speed of your hand) leave `constantPressure` as `false`. If you want a constant line width regardless of speed and pen pressure, change it to `true`.
6. `highlighter` true will place the new line behind the existing strokes (i.e. like a highlighter pen). If `highlighter` is missing or it is set to `false` the new line will appear at the top of the existing strokes (the default behavior of Excalidraw pens).
7. Go to https://perfect-freehand-example.vercel.app/ and configure your pen.
8. Click `Copy Options`.
9. Go back to the pen file you created in step No.2 and replace the placeholder text with the options you just copied from perfect-freehand.
10. Look for `easing` in the file and replace the function e.g. `(t) => t*t,` with the name of the function in brackets (in this example it would be `easeInQuad`). You will find the function name on the perfect-freehand website, only change the first letter to be lower case.
11. Test your pen in Excalidraw by clicking the `Alternative Pens` script and selecting your new pen.
# Example pens
My pens: https://github.com/zsviczian/obsidian-excalidraw-plugin/tree/master/ea-scripts/pens
**Fine tipped pen:**
```json
{
constantPressure: true,
options: {
smoothing: 0.4,
thinning: -0.5,
streamline: 0.4,
easing: "linear",
start: {
taper: 5,
cap: false,
},
end: {
taper: 5,
cap: false,
},
}
}
```
**Thick marker:**
```json
{
constantPressure: true,
hasOutline: true,
outlineWidth: 4,
options: {
thinning: 1,
smoothing: 0.5,
streamline: 0.5,
easing: "linear",
start: {
taper: 0,
cap: true
},
end: {
taper: 0,
cap: true
}
}
}
```
**Fountain pen:**
```json
{
options: {
smoothing: 0.22,
thinning: 0.8,
streamline: 0.22,
easing: "easeInQuad",
start: {
taper: true,
cap: true,
},
end: {
taper: 1,
cap: true,
},
}
}
```
# Notes about the pen options
Note, that custom pens are currently not supported by Excalidraw.com. I've submitted a [PR](https://github.com/excalidraw/excalidraw/pull/6069) but there is no guarantee that it will get pushed to production. Your Excalidraw drawing can still be loaded to Excalidraw, but the special pen effects will not be visible there.
If you set a pen in your Excalidraw template file, that pen will be loaded automatically when you create a file using that template. Similarly, when you save a document, it will save your current pen settings as well. The next time you open the document, you can continue to use the same pen.
Pen options are saved with the stroke. This means, that even if you change the ped definition later on, your existing drawings will not be effected.
`outlineWidth` is relative to `strokeWidth`. i.e. if you make the stroke thinner in Excalidraw, the outline will become proportionally thinner as well. `outlineWidth` is only used if `hasOutline` is set to true.
If you don't want your pen to be pressure/speed sensitive, set `constantPressure` to `true`. Setting `constantPressure` to `true` automatically sets `simulatePressure` to `false`.
If you want your pen to be speed sensitive (i.e. the faster you draw the line the thinner it gets), set `options.simulatePressure` to `true`. If you omit `simulatePressure` from `options` then excalidraw will detect if you are drawing with a mouse or a pen and use pen pressures if available.
You can read more about configuring perfect freehand here: https://github.com/steveruizok/perfect-freehand#documentation
Excalidraw supports all of the easing functions listed here: https://easings.net/#, plus "linear". You can also find details about these easing functions here:
https://github.com/ai/easings.net/blob/master/src/easings/easingsFunctions.ts
From a performance perspective I recommend linear easing.
# The script
```javascript */
//--------------------------
// Load settings
//--------------------------
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.8.8")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
const api = ea.getExcalidrawAPI();
let settings = ea.getScriptSettings();
//set default values on first run
if(!settings["Pen folder"]) {
settings = {
"Pen folder" : {
value: "Excalidraw/Pens",
description: "The path to the folder where you store the perfect freehand options"
}
};
ea.setScriptSettings(settings);
}
let penFolder = settings["Pen folder"].value.toLowerCase();
if(penFolder === "" || penFolder === "/") {
new Notice("The pen folder cannot be the root folder of your vault");
return;
}
if(!penFolder.endsWith("/")) penFolder += "/";
//--------------------------
// Select pen
//--------------------------
const pens = app.vault.getFiles()
.filter(f=>f.extension === "md" && f.path.toLowerCase() === penFolder + f.name.toLowerCase())
.sort((a,b)=>a.basename.toLowerCase()<b.basename.toLowerCase()?-1:1);
if(pens.length === 0) {
const notice = new Notice(`You don't seem to have any pen definition files. Click this message to open the how-to guide.`,4000);
notice.noticeEl.onclick = async () => app.workspace.openLinkText(utils.scriptFile.path,"","tab");
return;
}
const file = await utils.suggester(["Excalidraw Default"].concat(pens.map(f=>(f.name.slice(0,f.name.length-3)))),["Default"].concat(pens), "Choose a pen preset, press ESC to abort");
if(!file) return;
if(file === "Default") {
api.updateScene({
appState: {
currentStrokeOptions: undefined
}
});
return;
}
//--------------------------
// Load pen
//--------------------------
const pen = await app.vault.read(file);
const parseJSON = (data) => {
try {
return JSON.parse(data);
} catch(e) {
try {
return JSON.parse(data.replaceAll(/\s(\w*)\:\s/g,' "$1": ').replaceAll(/,([^\w]*?})/gm,"$1"));
} catch(ee) {
const notice = new Notice(`Error loading the pen file. Maybe you accidently copy/pasted the easing function from perfect freehand website? Check the error message in Developer Console.\n(click=dismiss, right-click=Info) `,5000);
notice.noticeEl.oncontextmenu = async () => app.workspace.openLinkText(utils.scriptFile.path,"","tab");
console.error(ee);
console.error(data.replaceAll(/\s(\w*)\:\s/g,' "$1": ').replaceAll(/,([^\w]*?})/gm,"$1"));
return;
}
}
}
penJSON = parseJSON(pen);
if(!penJSON || typeof penJSON !== 'object') return;
//--------------------------
// Apply pen
//--------------------------
await api.updateScene({
appState: {
currentStrokeOptions: penJSON
}
});
api.setActiveTool({type:"freedraw"});

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M373.5 27.1C388.5 9.9 410.2 0 433 0c43.6 0 79 35.4 79 79c0 22.8-9.9 44.6-27.1 59.6L277.7 319l-10.3-10.3-64-64L193 234.3 373.5 27.1zM170.3 256.9l10.4 10.4 64 64 10.4 10.4-19.2 83.4c-3.9 17.1-16.9 30.7-33.8 35.4L24.4 510.3l95.4-95.4c2.6 .7 5.4 1.1 8.3 1.1c17.7 0 32-14.3 32-32s-14.3-32-32-32s-32 14.3-32 32c0 2.9 .4 5.6 1.1 8.3L1.7 487.6 51.5 310c4.7-16.9 18.3-29.9 35.4-33.8l83.4-19.2z"/></svg>

After

Width:  |  Height:  |  Size: 632 B

View File

@@ -1,120 +1,120 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-ocr.jpg)
THIS SCRIPT REQUIRES EXCALIDRAW 1.5.15
The script will
1) send the selected image file to [taskbone.com](https://taskbone.com) to exctract the text from the image, and
2) will add the text to your drawing as a text element
I recommend also installing the [Transfer TextElements to Excalidraw markdown metadata](Transfer%20TextElements%20to%20Excalidraw%20markdown%20metadata.md) script as well.
The script is based on [@schlundd](https://github.com/schlundd)'s [Obsidian-OCR-Plugin](https://github.com/schlundd/obsidian-ocr-plugin)
See ScriptEngine documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.24")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
let token = ea.getScriptSettings().token?.value??ea.getScriptSettings().token;
const BASE_URL = "https://ocr.taskbone.com";
//convert setting to 1.5.21 format
if(token && !ea.getScriptSettings().token.value) {
ea.setScriptSettings({token: {value: token, hidden: true}});
}
//get new token if token was not provided
if (!token) {
const tokenResponse = await fetch(
BASE_URL + "/get-new-token", {
method: 'post'
});
if (tokenResponse.status === 200) {
jsonResponse = await tokenResponse.json();
token = jsonResponse.token;
ea.setScriptSettings({token: {value: token, hidden: true}});
} else {
notice(`Taskbone OCR Error: ${tokenResponse.status}\nPlease try again later.`);
return;
}
}
//get image element
//if multiple image elements were selected prompt user to choose
const imageElements = ea.getViewSelectedElements().filter((el)=>el.type==="image");
//need to save the view to ensure recently pasted images are saved as files
await ea.targetView.save();
let selectedImageElement = null;
switch (imageElements.length) {
case 0:
return;
case 1:
selectedImageElement = imageElements[0];
break;
default:
const files = imageElements.map((el)=>ea.getViewFileForImageElement(el));
selectedImageElement = await utils.suggester(files.map((f)=>f.name),imageElements);
break;
}
if(!selectedImageElement) {
notice("No image element was selected");
return;
}
const imageFile = ea.getViewFileForImageElement(selectedImageElement);
if(!imageFile) {
notice("Can read image file");
return;
}
//Execute the OCR
let text = null;
const fileBuffer = await app.vault.readBinary(imageFile);
const formData = new FormData();
formData.append("image", new Blob([fileBuffer]))
try {
const response = await fetch(
BASE_URL + "/get-text", {
headers: {
Authorization: "Bearer " + token
},
method: "post",
body: formData
});
if (response.status == 200) {
jsonResponse = await response.json();
text = jsonResponse?.text;
} else {
notice(`Could not read Text from ${file.path}:\n Error: ${response.status}`);
return;
}
} catch (error) {
notice(`The OCR service seems unavailable right now. Please try again later.`);
return;
}
if(!text) {
notice("No text found");
return;
}
console.log({text});
//add text element to drawing
const id = ea.addText(selectedImageElement.x,selectedImageElement.y+selectedImageElement.height,text);
await ea.addElementsToView();
ea.selectElementsInView([ea.getElement(id)]);
ea.getExcalidrawAPI().zoomToFit(ea.getViewSelectedElements(),1);
//utility function
function notice(message) {
new Notice(message,10000);
console.log(message);
}
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-ocr.jpg)
THIS SCRIPT REQUIRES EXCALIDRAW 1.5.15
The script will
1) send the selected image file to [taskbone.com](https://taskbone.com) to extract the text from the image, and
2) will add the text to your drawing as a text element
I recommend also installing the [Transfer TextElements to Excalidraw markdown metadata](Transfer%20TextElements%20to%20Excalidraw%20markdown%20metadata.md) script as well.
The script is based on [@schlundd](https://github.com/schlundd)'s [Obsidian-OCR-Plugin](https://github.com/schlundd/obsidian-ocr-plugin)
See ScriptEngine documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.24")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
let token = ea.getScriptSettings().token?.value??ea.getScriptSettings().token;
const BASE_URL = "https://ocr.taskbone.com";
//convert setting to 1.5.21 format
if(token && !ea.getScriptSettings().token.value) {
ea.setScriptSettings({token: {value: token, hidden: true}});
}
//get new token if token was not provided
if (!token) {
const tokenResponse = await fetch(
BASE_URL + "/get-new-token", {
method: 'post'
});
if (tokenResponse.status === 200) {
jsonResponse = await tokenResponse.json();
token = jsonResponse.token;
ea.setScriptSettings({token: {value: token, hidden: true}});
} else {
notice(`Taskbone OCR Error: ${tokenResponse.status}\nPlease try again later.`);
return;
}
}
//get image element
//if multiple image elements were selected prompt user to choose
const imageElements = ea.getViewSelectedElements().filter((el)=>el.type==="image");
//need to save the view to ensure recently pasted images are saved as files
await ea.targetView.save();
let selectedImageElement = null;
switch (imageElements.length) {
case 0:
return;
case 1:
selectedImageElement = imageElements[0];
break;
default:
const files = imageElements.map((el)=>ea.getViewFileForImageElement(el));
selectedImageElement = await utils.suggester(files.map((f)=>f.name),imageElements);
break;
}
if(!selectedImageElement) {
notice("No image element was selected");
return;
}
const imageFile = ea.getViewFileForImageElement(selectedImageElement);
if(!imageFile) {
notice("Can read image file");
return;
}
//Execute the OCR
let text = null;
const fileBuffer = await app.vault.readBinary(imageFile);
const formData = new FormData();
formData.append("image", new Blob([fileBuffer]))
try {
const response = await fetch(
BASE_URL + "/get-text", {
headers: {
Authorization: "Bearer " + token
},
method: "post",
body: formData
});
if (response.status == 200) {
jsonResponse = await response.json();
text = jsonResponse?.text;
} else {
notice(`Could not read Text from ${file.path}:\n Error: ${response.status}`);
return;
}
} catch (error) {
notice(`The OCR service seems unavailable right now. Please try again later.`);
return;
}
if(!text) {
notice("No text found");
return;
}
console.log({text});
//add text element to drawing
const id = ea.addText(selectedImageElement.x,selectedImageElement.y+selectedImageElement.height,text);
await ea.addElementsToView();
ea.selectElementsInView([ea.getElement(id)]);
ea.getExcalidrawAPI().zoomToFit(ea.getViewSelectedElements(),1);
//utility function
function notice(message) {
new Notice(message,10000);
console.log(message);
}

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 605 B

After

Width:  |  Height:  |  Size: 605 B

View File

@@ -1,44 +1,44 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-download-raw.jpg)
Download this file and save to your Obsidian Vault including the first line, or open it in "Raw" and copy the entire contents to Obsidian.
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-text-to-metadata.jpg)
The script will delete the selected text elements from the canvas and will copy the text from these text elements into the Excalidraw markdown file as metadata. This means, that the text will no longer be visible in the drawing, however you will be able to search for the text in Obsidian and find the drawing containing this image.
See ScriptEngine documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
//get text elements
const textElements = ea.getViewSelectedElements().filter((el)=>el.type==="text");
if(textElements.length===0) {
notice("No text elements were selected")
return;
}
metadata = "# Metadata\n" + textElements
.map((el)=>el.rawText.replaceAll(/%|\^/g,"_")) //cleaning these characters for safety, might not be needed
.join("/n") + "\n";
ea.deleteViewElements(textElements);
await ea.targetView.save();
data = await app.vault.read(ea.targetView.file);
splitAfterFrontmatter = data.split(/(^---[\w\W]*?---\n)/);
if(splitAfterFrontmatter.length !== 3) {
notice("Error locating frontmatter in markdown file");
console.log({file:ea.targetView.file});
return;
}
newData = splitAfterFrontmatter[1]+metadata+splitAfterFrontmatter[2]
await app.vault.modify(ea.targetView.file,newData);
//utility function
function notice(message) {
new Notice(message);
console.log(message);
}
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-download-raw.jpg)
Download this file and save to your Obsidian Vault including the first line, or open it in "Raw" and copy the entire contents to Obsidian.
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-text-to-metadata.jpg)
The script will delete the selected text elements from the canvas and will copy the text from these text elements into the Excalidraw markdown file as metadata. This means, that the text will no longer be visible in the drawing, however you will be able to search for the text in Obsidian and find the drawing containing this image.
See ScriptEngine documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
//get text elements
const textElements = ea.getViewSelectedElements().filter((el)=>el.type==="text");
if(textElements.length===0) {
notice("No text elements were selected")
return;
}
metadata = "# Metadata\n" + textElements
.map((el)=>el.rawText.replaceAll(/%|\^/g,"_")) //cleaning these characters for safety, might not be needed
.join("/n") + "\n";
ea.deleteViewElements(textElements);
await ea.targetView.save();
data = await app.vault.read(ea.targetView.file);
splitAfterFrontmatter = data.split(/(^---[\w\W]*?---\n)/);
if(splitAfterFrontmatter.length !== 3) {
notice("Error locating frontmatter in markdown file");
console.log({file:ea.targetView.file});
return;
}
newData = splitAfterFrontmatter[1]+metadata+splitAfterFrontmatter[2]
await app.vault.modify(ea.targetView.file,newData);
//utility function
function notice(message) {
new Notice(message);
console.log(message);
}

View File

@@ -1,348 +1,348 @@
If you are enjoying the Excalidraw plugin then please support my work and enthusiasm by buying me a coffee on [https://ko-fi/zsolt](https://ko-fi.com/zsolt).
[<img src="https://user-images.githubusercontent.com/14358394/115450238-f39e8100-a21b-11eb-89d0-fa4b82cdbce8.png" class="coffee">](https://ko-fi.com/zsolt)
---
Jump ahead to the [[#List of available scripts]]
# Introducing Excalidraw Automate Script Engine
<iframe width="560" height="315" src="https://www.youtube.com/embed/hePJcObHIso" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
Script Engine scripts are installed in the `Downloaded` subfolder of the `Excalidraw Automate script folder` specified in plugin settings.
In the `Command Palette` installed scripts are prefixed with `Downloaded/`, thus you can always know if you are executing a local script of your own, or one that you have downloaded from GitHub.
## Attention developers and hobby hackers
<img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/hobby-programmer.svg' align='left' style='background-color:whitesmoke; width:80px; margin-right:15px; margin-bottom:10px;'/>
If you want to modify scripts, I recommend moving them to the `Excalidraw Automate script folder` or a different subfolder under the script folder. Scripts in the `Downloaded` folder will be overwritten when you click the `Update this script` button. Note also, that at this time, I do not check if the script file has been updated on GitHub, thus the `Update this script` button is always visible once you have installed a script, not only when an update is availble (hope to build this feature in the future).
I would love to include your contribution in the script library. If you have a script of your own that you would like to share with the community, please open a [PR](https://github.com/zsviczian/obsidian-excalidraw-plugin/pulls) on GitHub. Be sure to include the following in your pull request
- The [script file](https://github.com/zsviczian/obsidian-excalidraw-plugin/tree/master/ea-scripts) with a self explanetory name. The name of the file will be the name of the script in the Command Palette.
- An [image](https://github.com/zsviczian/obsidian-excalidraw-plugin/tree/master/images) explaining the scripts purpose. Remember a picture speaks thousand words!
- An update to this file [ea-scripts/index.md](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/index.md)
---
# List of available scripts
- [[#Add Connector Point]]
- [[#Add Link to Existing File and Open]]
- [[#Add Link to New Page and Open]]
- [[#Add Next Step in Process]]
- [[#Box Each Selected Groups]]
- [[#Box Selected Elements]]
- [[#Change shape of selected elements]]
- [[#Connect elements]]
- [[#Convert freedraw to line]]
- [[#Convert selected text elements to sticky notes]]
- [[#Convert text to link with folder and alias]]
- [[#Copy Selected Element Styles to Global]]
- [[#Create new markdown file and embed into active drawing]]
- [[#Darken background color]]
- [[#Elbow connectors]]
- [[#Expand rectangles horizontally keep text centered]]
- [[#Expand rectangles horizontally]]
- [[#Expand rectangles vertically keep text centered]]
- [[#Expand rectangles vertically]]
- [[#Fixed horizontal distance between centers]]
- [[#Fixed inner distance]]
- [[#Fixed spacing]]
- [[#Fixed vertical distance between centers]]
- [[#Fixed vertical distance]]
- [[#Lighten background color]]
- [[Mindmap connector]]
- [[#Modify background color opacity]]
- [[#Normalize Selected Arrows]]
- [[#OCR - Optical Character Recognition]]
- [[#Organic Line]]
- [[#Repeat Elements]]
- [[#Reverse arrows]]
- [[#Scribble Helper]]
- [[#Select Elements of Type]]
- [[#Set background color of unclosed line object by adding a shadow clone]]
- [[#Set Dimensions]]
- [[#Set Font Family]]
- [[#Set Grid]]
- [[#Set Link Alias]]
- [[#Set Stroke Width of Selected Elements]]
- [[#Set Text Alignment]]
- [[#Split text by lines]]
- [[#Toggle Fullscreen on Mobile]]
- [[#Transfer TextElements to Excalidraw markdown metadata]]
- [[#Zoom to Fit Selected Elements]]
## Add Connector Point
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Connector%20Point.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Add%20Connector%20Point.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will add a small circle to the top left of each text element in the selection and add the text and the "connector point" to a group. You can use the connector points to link text elements with an arrow (in for example a Wardley Map).<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-bullet-point.jpg'></td></tr></table>
## Add Link to Existing File and Open
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Link%20to%20Existing%20File%20and%20Open.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Add%20Link%20to%20Existing%20File%20and%20Open.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Prompts for a file from the vault. Adds a link to the selected element pointing to the selected file. You can control in settings to open the file in the current active pane or an adjacent pane.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-link-and-open.jpg'></td></tr></table>
## Add Link to New Page and Open
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Link%20to%20New%20Page%20and%20Open.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Add%20Link%20to%20New%20Page%20and%20Open.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Prompts for filename. Offers option to create and open a new Markdown or Excalidraw document. Adds link pointing to the new file, to the selected objects in the drawing. You can control in settings to open the file in the current active pane or an adjacent pane.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-link-to-new-page-and-pen.jpg'></td></tr></table>
## Add Next Step in Process
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Next%20Step%20in%20Process.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Add%20Next%20Step%20in%20Process.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will prompt you for the title of the process step, then will create a stick note with the text. If an element is selected then the script will connect this new step with an arrow to the previous step (the selected element). If no element is selected, then the script assumes this is the first step in the process and will only output the sticky note with the text that was entered.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-process-step.jpg'></td></tr></table>
## Box Each Selected Groups
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Box%20Each%20Selected%20Groups.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Box%20Each%20Selected%20Groups.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will add encapsulating boxes around each of the currently selected groups in Excalidraw.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-box-each-selected-groups.png'></td></tr></table>
## Box Selected Elements
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Box%20Selected%20Elements.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Box%20Selected%20Elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will add an encapsulating box around the currently selected elements in Excalidraw.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-box-elements.jpg'></td></tr></table>
## Change shape of selected elements
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Change%20shape%20of%20selected%20elements.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Change%20shape%20of%20selected%20elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script allows you to change the shape of selected Rectangles, Diamonds and Ellipses.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-change-shape.jpg'></td></tr></table>
## Connect elements
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Connect%20elements.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Connect%20elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will connect two objects with an arrow. If either of the objects are a set of grouped elements (e.g. a text element grouped with an encapsulating rectangle), the script will identify these groups, and connect the arrow to the largest object in the group (assuming you want to connect the arrow to the box around the text element).<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-connect-elements.jpg'></td></tr></table>
## Convert freedraw to line
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Convert%20freedraw%20to%20line.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Convert%20freedraw%20to%20line.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Convert selected freedraw objects into editable lines. This will allow you to adjust your drawings by dragging line points and will also allow you to select shape fill in case of enclosed lines. You can adjust conversion point density in settings.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-convert-freedraw-to-line.jpg'></td></tr></table>
## Convert selected text elements to sticky notes
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Convert%20selected%20text%20elements%20to%20sticky%20notes.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Convert%20selected%20text%20elements%20to%20sticky%20notes.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Converts selected plain text elements to sticky notes with transparent background and transparent stroke color (default setting, can be changed in plugin settings). Essentially converts text element into a wrappable format.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-textelement-to-transparent-stickynote.png'></td></tr></table>
## Convert text to link with folder and alias
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Convert%20text%20to%20link%20with%20folder%20and%20alias.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Convert%20text%20to%20link%20with%20folder%20and%20alias.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Converts text elements to links pointing to a file in a selected folder and with the alias set as the original text. The script will prompt the user to select an existing folder from the vault.<br><code>original text</code> - <code>[[selected folder/original text|original text]]</code></td></tr></table>
## Copy Selected Element Styles to Global
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Copy%20Selected%20Element%20Styles%20to%20Global.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Copy%20Selected%20Element%20Styles%20to%20Global.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will copy styles of any selected element into Excalidraw's global styles.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-copy-selected-element-styles-to-global.png'></td></tr></table>
## Create new markdown file and embed into active drawing
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Create%20new%20markdown%20file%20and%20embed%20into%20active%20drawing.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Create%20new%20markdown%20file%20and%20embed%20into%20active%20drawing.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script will prompt you for a filename, then create a new markdown document with the file name provided, open the new markdown document in an adjacent pane, and embed the markdown document into the active Excalidraw drawing.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-create-and-embed-new-markdown-file.jpg'></td></tr></table>
## Darken background color
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Darken%20background%20color.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Darken%20background%20color.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script darkens the background color of the selected element by 2% at a time. You can use this script several times until you are satisfied. It is recommended to set a shortcut key for this script so that you can quickly try to DARKEN and LIGHTEN the color effect. In contrast to the `Modify background color opacity` script, the advantage is that the background color of the element is not affected by the canvas color, and the color value does not appear in a strange rgba() form.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/darken-lighten-background-color.png'></td></tr></table>
## Elbow connectors
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Elbow%20connectors.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Elbow%20connectors.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script converts the selected connectors to elbows.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/elbow-connectors.png'></td></tr></table>
## Expand rectangles horizontally keep text centered
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Expand%20rectangles%20horizontally%20keep%20text%20centered.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Expand%20rectangles%20horizontally%20keep%20text%20centered.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script expands the width of the selected rectangles until they are all the same width and keep the text centered.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-expand-rectangles.gif'></td></tr></table>
## Expand rectangles horizontally
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Expand%20rectangles%20horizontally.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Expand%20rectangles%20horizontally.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script expands the width of the selected rectangles until they are all the same width.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-expand-rectangles.gif'></td></tr></table>
## Expand rectangles vertically keep text centered
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Expand%20rectangles%20vertically%20keep%20text%20centered.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Expand%20rectangles%20vertically%20keep%20text%20centered.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script expands the height of the selected rectangles until they are all the same height and keep the text centered.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-expand-rectangles.gif'></td></tr></table>
## Expand rectangles vertically
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Expand%20rectangles%20vertically.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Expand%20rectangles%20vertically.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script expands the height of the selected rectangles until they are all the same height.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-expand-rectangles.gif'></td></tr></table>
## Fixed horizontal distance between centers
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20horizontal%20distance%20between%20centers.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Fixed%20horizontal%20distance%20between%20centers.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script arranges the selected elements horizontally with a fixed center spacing.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fixed-horizontal-distance-between-centers.png'></td></tr></table>
## Fixed inner distance
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20inner%20distance.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Fixed%20inner%20distance.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script arranges selected elements and groups with a fixed inner distance.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fixed-inner-distance.png'></td></tr></table>
## Fixed spacing
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20spacing.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Fixed%20spacing.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script arranges the selected elements horizontally with a fixed spacing. When we create an architecture diagram or mind map, we often need to arrange a large number of elements in a fixed spacing. `Fixed spacing` and `Fixed vertical Distance` scripts can save us a lot of time.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fix-space-demo.png'></td></tr></table>
## Fixed vertical distance between centers
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20vertical%20distance%20between%20centers.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Fixed%20vertical%20distance%20between%20centers.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script arranges the selected elements vertically with a fixed center spacing.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fixed-vertical-distance-between-centers.png'></td></tr></table>
## Fixed vertical distance
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20vertical%20distance.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Fixed%20vertical%20distance.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script arranges the selected elements vertically with a fixed spacing. When we create an architecture diagram or mind map, we often need to arrange a large number of elements in a fixed spacing. `Fixed spacing` and `Fixed vertical Distance` scripts can save us a lot of time.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fixed-vertical-distance.png'></td></tr></table>
## Lighten background color
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Lighten%20background%20color.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Lighten%20background%20color.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script lightens the background color of the selected element by 2% at a time. You can use this script several times until you are satisfied. It is recommended to set a shortcut key for this script so that you can quickly try to DARKEN and LIGHTEN the color effect.In contrast to the `Modify background color opacity` script, the advantage is that the background color of the element is not affected by the canvas color, and the color value does not appear in a strange rgba() form.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/darken-lighten-background-color.png'></td></tr></table>
## Mindmap connector
```excalidraw-script-install
https://github.com/xllowl/obsidian-excalidraw-plugin/blob/master/ea-scripts/Mindmap%20connector.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/xllowl'>@xllowl</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/xllowl/obsidian-excalidraw-plugin/blob/master/ea-scripts/Mindmap%20connector.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script creates mindmap like lines(only right side available). The line will starts according to the creation time of the elements. So you may need to create the header element first.<br><img src='https://github.com/xllowl/obsidian-excalidraw-plugin/blob/master/images/mindmap%20connector.png'></td></tr></table>
## Modify background color opacity
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Modify%20background%20color%20opacity.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Modify%20background%20color%20opacity.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script changes the opacity of the background color of the selected boxes. The default background color in Excalidraw is so dark that the text is hard to read. You can lighten the color a bit by setting transparency. And you can tweak the transparency over and over again until you're happy with it. Although excalidraw has the opacity option in its native property Settings, it also changes the transparency of the border. Use this script to change only the opacity of the background color without affecting the border.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-modify-background-color-opacity.png'></td></tr></table>
## Normalize Selected Arrows
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Normalize%20Selected%20Arrows.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Normalize%20Selected%20Arrows.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will reset the start and end positions of the selected arrows. The arrow will point to the center of the connected box and will have a gap of 8px from the box.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-normalize-selected-arrows.png'></td></tr></table>
## OCR - Optical Character Recognition
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/OCR%20-%20Optical%20Character%20Recognition.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/OCR%20-%20Optical%20Character%20Recognition.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">REQUIRES EXCALIDRAW 1.5.15<br>The script will 1) send the selected image file to [taskbone.com](https://taskbone.com) to exctract the text from the image, and 2) will add the text to your drawing as a text element.<br><mark>⚠ Note that you will need to manually paste your token into the script after the first run! ⚠</mark><br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-ocr.jpg'><br><iframe width="560" height="315" src="https://www.youtube.com/embed/W2NMzR8s4eE" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></td></tr></table>
## Organic Line
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Organic%20Line.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Organic%20Line.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Converts selected freedraw lines such that pencil pressure will decrease from maximum to minimum from the beginning of the line to its end. The resulting line is placed at the back of the layers, under all other items. Helpful when drawing organic mindmaps.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-organic-line.jpg'></td></tr></table>
## Repeat Elements
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Repeat%20Elements.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Repeat%20Elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will detect the difference between 2 selected elements, including position, size, angle, stroke and background color, and create several elements that repeat these differences based on the number of repetitions entered by the user.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-repeat-elements.png'></td></tr></table>
## Reverse arrows
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Reverse%20arrows.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Reverse%20arrows.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Reverse the direction of **arrows** within the scope of selected elements.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-reverse-arrow.jpg'></td></tr></table>
## Scribble Helper
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Scribble%20Helper.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Scribble%20Helper.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">iOS scribble helper for better handwriting experience with text elements. If no elements are selected then the creates a text element at pointer position and you can use the edit box to modify the text with scribble. If a text element is selected then opens the input prompt where you can modify this text with scribble.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-scribble-helper.jpg'></td></tr></table>
## Select Elements of Type
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Select%20Elements%20of%20Type.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Select%20Elements%20of%20Type.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Prompts you with a list of the different element types in the active image. Only elements of the selected type will be selected on the canvas. If nothing is selected when running the script, then the script will process all the elements on the canvas. If some elements are selected when the script is executed, then the script will only process the selected elements.<br>The script is useful when, for example, you want to bring to front all the arrows, or want to change the color of all the text elements, etc.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-select-element-of-type.jpg'></td></tr></table>
## Set background color of unclosed line object by adding a shadow clone
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20background%20color%20of%20unclosed%20line%20object%20by%20adding%20a%20shadow%20clone.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20background%20color%20of%20unclosed%20line%20object%20by%20adding%20a%20shadow%20clone.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Use this script to set the background color of unclosed (i.e. open) line objects by creating a clone of the object. The script will set the stroke color of the clone to transparent and will add a straight line to close the object. Use settings to define the default background color, the fill style, and the strokeWidth of the clone. By default the clone will be grouped with the original object, you can disable this also in settings.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-set-background-color-of-unclosed-line.jpg'></td></tr></table>
## Set Dimensions
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Dimensions.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20Dimensions.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Currently there is no way to specify the exact location and size of objects in Excalidraw. You can bridge this gap with the following simple script.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-dimensions.jpg'></td></tr></table>
## Set Font Family
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Font%20Family.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20Font%20Family.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Sets font family of the text block (Virgil, Helvetica, Cascadia). Useful if you want to set a keyboard shortcut for selecting font family.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-font-family.jpg'></td></tr></table>
## Set Grid
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Grid.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20Grid.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The default grid size in Excalidraw is 20. Currently there is no way to change the grid size via the user interface. This script offers a way to bridge this gap.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-grid.jpg'></td></tr></table>
## Set Link Alias
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Link%20Alias.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20Link%20Alias.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Iterates all of the links in the selected TextElements and prompts the user to set or modify the alias for each link found.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-set-link-alias.jpg'></td></tr></table>
## Set Stroke Width of Selected Elements
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Stroke%20Width%20of%20Selected%20Elements.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20Stroke%20Width%20of%20Selected%20Elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will set the stroke width of selected elements. This is helpful, for example, when you scale freedraw sketches and want to reduce or increase their line width.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-stroke-width.jpg'></td></tr></table>
## Set Text Alignment
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Text%20Alignment.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20Text%20Alignment.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Sets text alignment of text block (cetner, right, left). Useful if you want to set a keyboard shortcut for selecting text alignment.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-text-align.jpg'></td></tr></table>
## Split text by lines
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Split%20text%20by%20lines.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Split%20text%20by%20lines.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Split lines of text into separate text elements for easier reorganization<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-split-lines.jpg'></td></tr></table>
## TheBrain-navigation
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/TheBrain-navigation.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/TheBrain-navigation.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">An Excalidraw based graph user interface for your Vault. Requires the <a href='https://github.com/SkepticMystic/breadcrumbs'>Breadcrumbs plugin</a> to be installed and configured as well. Generates a user interface similar to that of <a href='https://TheBrain.com'>TheBrain</a>. Watch this introduction to this script on <a href='https://youtu.be/J4T5KHERH_o'>YouTube</a>.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/TheBrain.jpg'></td></tr></table>
## Toggle Fullscreen on Mobile
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Toggle%20Fullscreen%20on%20Mobile.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Toggle%20Fullscreen%20on%20Mobile.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Hides Obsidian workspace leaf padding and header (based on option in settings, default is "hide header" = false) which will take Excalidraw to full screen. ⚠ Note that if the header is not visible, it will be very difficult to invoke the command palette to end full screen. Only hide the header if you have a keyboard or you've practiced opening command palette!<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/ea-toggle-fullscreen.jpg'></td></tr></table>
## Transfer TextElements to Excalidraw markdown metadata
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Transfer%20TextElements%20to%20Excalidraw%20markdown%20metadata.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Transfer%20TextElements%20to%20Excalidraw%20markdown%20metadata.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script will delete the selected text elements from the canvas and will copy the text from these text elements into the Excalidraw markdown file as metadata. This means, that the text will no longer be visible in the drawing, however you will be able to search for the text in Obsidian and find the drawing containing this image.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-text-to-metadata.jpg'></td></tr></table>
## Zoom to Fit Selected Elements
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Zoom%20to%20Fit%20Selected%20Elements.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Zoom%20to%20Fit%20Selected%20Elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Similar to Excalidraw standard <kbd>SHIFT+2</kbd> feature: Zoom to fit selected elements, but with the ability to zoom to 1000%. Inspiration: [#272](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/272)</td></tr></table>
If you are enjoying the Excalidraw plugin then please support my work and enthusiasm by buying me a coffee on [https://ko-fi/zsolt](https://ko-fi.com/zsolt).
[<img src="https://user-images.githubusercontent.com/14358394/115450238-f39e8100-a21b-11eb-89d0-fa4b82cdbce8.png" class="coffee">](https://ko-fi.com/zsolt)
---
Jump ahead to the [[#List of available scripts]]
# Introducing Excalidraw Automate Script Engine
<iframe width="560" height="315" src="https://www.youtube.com/embed/hePJcObHIso" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
Script Engine scripts are installed in the `Downloaded` subfolder of the `Excalidraw Automate script folder` specified in plugin settings.
In the `Command Palette` installed scripts are prefixed with `Downloaded/`, thus you can always know if you are executing a local script of your own, or one that you have downloaded from GitHub.
## Attention developers and hobby hackers
<img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/hobby-programmer.svg' align='left' style='background-color:whitesmoke; width:80px; margin-right:15px; margin-bottom:10px;'/>
If you want to modify scripts, I recommend moving them to the `Excalidraw Automate script folder` or a different subfolder under the script folder. Scripts in the `Downloaded` folder will be overwritten when you click the `Update this script` button. Note also, that at this time, I do not check if the script file has been updated on GitHub, thus the `Update this script` button is always visible once you have installed a script, not only when an update is available (hope to build this feature in the future).
I would love to include your contribution in the script library. If you have a script of your own that you would like to share with the community, please open a [PR](https://github.com/zsviczian/obsidian-excalidraw-plugin/pulls) on GitHub. Be sure to include the following in your pull request
- The [script file](https://github.com/zsviczian/obsidian-excalidraw-plugin/tree/master/ea-scripts) with a self explanetory name. The name of the file will be the name of the script in the Command Palette.
- An [image](https://github.com/zsviczian/obsidian-excalidraw-plugin/tree/master/images) explaining the scripts purpose. Remember a picture speaks thousand words!
- An update to this file [ea-scripts/index.md](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/index.md)
---
# List of available scripts
- [[#Add Connector Point]]
- [[#Add Link to Existing File and Open]]
- [[#Add Link to New Page and Open]]
- [[#Add Next Step in Process]]
- [[#Box Each Selected Groups]]
- [[#Box Selected Elements]]
- [[#Change shape of selected elements]]
- [[#Connect elements]]
- [[#Convert freedraw to line]]
- [[#Convert selected text elements to sticky notes]]
- [[#Convert text to link with folder and alias]]
- [[#Copy Selected Element Styles to Global]]
- [[#Create new markdown file and embed into active drawing]]
- [[#Darken background color]]
- [[#Elbow connectors]]
- [[#Expand rectangles horizontally keep text centered]]
- [[#Expand rectangles horizontally]]
- [[#Expand rectangles vertically keep text centered]]
- [[#Expand rectangles vertically]]
- [[#Fixed horizontal distance between centers]]
- [[#Fixed inner distance]]
- [[#Fixed spacing]]
- [[#Fixed vertical distance between centers]]
- [[#Fixed vertical distance]]
- [[#Lighten background color]]
- [[Mindmap connector]]
- [[#Modify background color opacity]]
- [[#Normalize Selected Arrows]]
- [[#OCR - Optical Character Recognition]]
- [[#Organic Line]]
- [[#Repeat Elements]]
- [[#Reverse arrows]]
- [[#Scribble Helper]]
- [[#Select Elements of Type]]
- [[#Set background color of unclosed line object by adding a shadow clone]]
- [[#Set Dimensions]]
- [[#Set Font Family]]
- [[#Set Grid]]
- [[#Set Link Alias]]
- [[#Set Stroke Width of Selected Elements]]
- [[#Set Text Alignment]]
- [[#Split text by lines]]
- [[#Toggle Fullscreen on Mobile]]
- [[#Transfer TextElements to Excalidraw markdown metadata]]
- [[#Zoom to Fit Selected Elements]]
## Add Connector Point
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Connector%20Point.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Add%20Connector%20Point.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will add a small circle to the top left of each text element in the selection and add the text and the "connector point" to a group. You can use the connector points to link text elements with an arrow (in for example a Wardley Map).<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-bullet-point.jpg'></td></tr></table>
## Add Link to Existing File and Open
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Link%20to%20Existing%20File%20and%20Open.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Add%20Link%20to%20Existing%20File%20and%20Open.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Prompts for a file from the vault. Adds a link to the selected element pointing to the selected file. You can control in settings to open the file in the current active pane or an adjacent pane.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-link-and-open.jpg'></td></tr></table>
## Add Link to New Page and Open
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Link%20to%20New%20Page%20and%20Open.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Add%20Link%20to%20New%20Page%20and%20Open.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Prompts for filename. Offers option to create and open a new Markdown or Excalidraw document. Adds link pointing to the new file, to the selected objects in the drawing. You can control in settings to open the file in the current active pane or an adjacent pane.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-link-to-new-page-and-pen.jpg'></td></tr></table>
## Add Next Step in Process
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Next%20Step%20in%20Process.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Add%20Next%20Step%20in%20Process.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will prompt you for the title of the process step, then will create a stick note with the text. If an element is selected then the script will connect this new step with an arrow to the previous step (the selected element). If no element is selected, then the script assumes this is the first step in the process and will only output the sticky note with the text that was entered.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-process-step.jpg'></td></tr></table>
## Box Each Selected Groups
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Box%20Each%20Selected%20Groups.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Box%20Each%20Selected%20Groups.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will add encapsulating boxes around each of the currently selected groups in Excalidraw.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-box-each-selected-groups.png'></td></tr></table>
## Box Selected Elements
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Box%20Selected%20Elements.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Box%20Selected%20Elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will add an encapsulating box around the currently selected elements in Excalidraw.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-box-elements.jpg'></td></tr></table>
## Change shape of selected elements
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Change%20shape%20of%20selected%20elements.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Change%20shape%20of%20selected%20elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script allows you to change the shape of selected Rectangles, Diamonds and Ellipses.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-change-shape.jpg'></td></tr></table>
## Connect elements
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Connect%20elements.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Connect%20elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will connect two objects with an arrow. If either of the objects are a set of grouped elements (e.g. a text element grouped with an encapsulating rectangle), the script will identify these groups, and connect the arrow to the largest object in the group (assuming you want to connect the arrow to the box around the text element).<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-connect-elements.jpg'></td></tr></table>
## Convert freedraw to line
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Convert%20freedraw%20to%20line.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Convert%20freedraw%20to%20line.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Convert selected freedraw objects into editable lines. This will allow you to adjust your drawings by dragging line points and will also allow you to select shape fill in case of enclosed lines. You can adjust conversion point density in settings.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-convert-freedraw-to-line.jpg'></td></tr></table>
## Convert selected text elements to sticky notes
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Convert%20selected%20text%20elements%20to%20sticky%20notes.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Convert%20selected%20text%20elements%20to%20sticky%20notes.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Converts selected plain text elements to sticky notes with transparent background and transparent stroke color (default setting, can be changed in plugin settings). Essentially converts text element into a wrappable format.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-textelement-to-transparent-stickynote.png'></td></tr></table>
## Convert text to link with folder and alias
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Convert%20text%20to%20link%20with%20folder%20and%20alias.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Convert%20text%20to%20link%20with%20folder%20and%20alias.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Converts text elements to links pointing to a file in a selected folder and with the alias set as the original text. The script will prompt the user to select an existing folder from the vault.<br><code>original text</code> - <code>[[selected folder/original text|original text]]</code></td></tr></table>
## Copy Selected Element Styles to Global
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Copy%20Selected%20Element%20Styles%20to%20Global.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Copy%20Selected%20Element%20Styles%20to%20Global.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will copy styles of any selected element into Excalidraw's global styles.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-copy-selected-element-styles-to-global.png'></td></tr></table>
## Create new markdown file and embed into active drawing
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Create%20new%20markdown%20file%20and%20embed%20into%20active%20drawing.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Create%20new%20markdown%20file%20and%20embed%20into%20active%20drawing.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script will prompt you for a filename, then create a new markdown document with the file name provided, open the new markdown document in an adjacent pane, and embed the markdown document into the active Excalidraw drawing.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-create-and-embed-new-markdown-file.jpg'></td></tr></table>
## Darken background color
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Darken%20background%20color.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Darken%20background%20color.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script darkens the background color of the selected element by 2% at a time. You can use this script several times until you are satisfied. It is recommended to set a shortcut key for this script so that you can quickly try to DARKEN and LIGHTEN the color effect. In contrast to the `Modify background color opacity` script, the advantage is that the background color of the element is not affected by the canvas color, and the color value does not appear in a strange rgba() form.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/darken-lighten-background-color.png'></td></tr></table>
## Elbow connectors
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Elbow%20connectors.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Elbow%20connectors.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script converts the selected connectors to elbows.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/elbow-connectors.png'></td></tr></table>
## Expand rectangles horizontally keep text centered
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Expand%20rectangles%20horizontally%20keep%20text%20centered.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Expand%20rectangles%20horizontally%20keep%20text%20centered.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script expands the width of the selected rectangles until they are all the same width and keep the text centered.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-expand-rectangles.gif'></td></tr></table>
## Expand rectangles horizontally
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Expand%20rectangles%20horizontally.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Expand%20rectangles%20horizontally.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script expands the width of the selected rectangles until they are all the same width.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-expand-rectangles.gif'></td></tr></table>
## Expand rectangles vertically keep text centered
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Expand%20rectangles%20vertically%20keep%20text%20centered.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Expand%20rectangles%20vertically%20keep%20text%20centered.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script expands the height of the selected rectangles until they are all the same height and keep the text centered.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-expand-rectangles.gif'></td></tr></table>
## Expand rectangles vertically
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Expand%20rectangles%20vertically.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Expand%20rectangles%20vertically.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script expands the height of the selected rectangles until they are all the same height.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-expand-rectangles.gif'></td></tr></table>
## Fixed horizontal distance between centers
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20horizontal%20distance%20between%20centers.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Fixed%20horizontal%20distance%20between%20centers.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script arranges the selected elements horizontally with a fixed center spacing.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fixed-horizontal-distance-between-centers.png'></td></tr></table>
## Fixed inner distance
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20inner%20distance.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Fixed%20inner%20distance.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script arranges selected elements and groups with a fixed inner distance.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fixed-inner-distance.png'></td></tr></table>
## Fixed spacing
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20spacing.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Fixed%20spacing.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script arranges the selected elements horizontally with a fixed spacing. When we create an architecture diagram or mind map, we often need to arrange a large number of elements in a fixed spacing. `Fixed spacing` and `Fixed vertical Distance` scripts can save us a lot of time.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fix-space-demo.png'></td></tr></table>
## Fixed vertical distance between centers
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20vertical%20distance%20between%20centers.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Fixed%20vertical%20distance%20between%20centers.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script arranges the selected elements vertically with a fixed center spacing.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fixed-vertical-distance-between-centers.png'></td></tr></table>
## Fixed vertical distance
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20vertical%20distance.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Fixed%20vertical%20distance.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script arranges the selected elements vertically with a fixed spacing. When we create an architecture diagram or mind map, we often need to arrange a large number of elements in a fixed spacing. `Fixed spacing` and `Fixed vertical Distance` scripts can save us a lot of time.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fixed-vertical-distance.png'></td></tr></table>
## Lighten background color
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Lighten%20background%20color.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Lighten%20background%20color.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script lightens the background color of the selected element by 2% at a time. You can use this script several times until you are satisfied. It is recommended to set a shortcut key for this script so that you can quickly try to DARKEN and LIGHTEN the color effect.In contrast to the `Modify background color opacity` script, the advantage is that the background color of the element is not affected by the canvas color, and the color value does not appear in a strange rgba() form.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/darken-lighten-background-color.png'></td></tr></table>
## Mindmap connector
```excalidraw-script-install
https://github.com/xllowl/obsidian-excalidraw-plugin/blob/master/ea-scripts/Mindmap%20connector.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/xllowl'>@xllowl</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/xllowl/obsidian-excalidraw-plugin/blob/master/ea-scripts/Mindmap%20connector.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script creates mindmap like lines(only right side available). The line will starts according to the creation time of the elements. So you may need to create the header element first.<br><img src='https://github.com/xllowl/obsidian-excalidraw-plugin/blob/master/images/mindmap%20connector.png'></td></tr></table>
## Modify background color opacity
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Modify%20background%20color%20opacity.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Modify%20background%20color%20opacity.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script changes the opacity of the background color of the selected boxes. The default background color in Excalidraw is so dark that the text is hard to read. You can lighten the color a bit by setting transparency. And you can tweak the transparency over and over again until you're happy with it. Although excalidraw has the opacity option in its native property Settings, it also changes the transparency of the border. Use this script to change only the opacity of the background color without affecting the border.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-modify-background-color-opacity.png'></td></tr></table>
## Normalize Selected Arrows
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Normalize%20Selected%20Arrows.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Normalize%20Selected%20Arrows.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will reset the start and end positions of the selected arrows. The arrow will point to the center of the connected box and will have a gap of 8px from the box.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-normalize-selected-arrows.png'></td></tr></table>
## OCR - Optical Character Recognition
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/OCR%20-%20Optical%20Character%20Recognition.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/OCR%20-%20Optical%20Character%20Recognition.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">REQUIRES EXCALIDRAW 1.5.15<br>The script will 1) send the selected image file to [taskbone.com](https://taskbone.com) to extract the text from the image, and 2) will add the text to your drawing as a text element.<br><mark>⚠ Note that you will need to manually paste your token into the script after the first run! ⚠</mark><br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-ocr.jpg'><br><iframe width="560" height="315" src="https://www.youtube.com/embed/W2NMzR8s4eE" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></td></tr></table>
## Organic Line
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Organic%20Line.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Organic%20Line.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Converts selected freedraw lines such that pencil pressure will decrease from maximum to minimum from the beginning of the line to its end. The resulting line is placed at the back of the layers, under all other items. Helpful when drawing organic mindmaps.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-organic-line.jpg'></td></tr></table>
## Repeat Elements
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Repeat%20Elements.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Repeat%20Elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will detect the difference between 2 selected elements, including position, size, angle, stroke and background color, and create several elements that repeat these differences based on the number of repetitions entered by the user.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-repeat-elements.png'></td></tr></table>
## Reverse arrows
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Reverse%20arrows.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Reverse%20arrows.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Reverse the direction of **arrows** within the scope of selected elements.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-reverse-arrow.jpg'></td></tr></table>
## Scribble Helper
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Scribble%20Helper.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Scribble%20Helper.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">iOS scribble helper for better handwriting experience with text elements. If no elements are selected then the creates a text element at pointer position and you can use the edit box to modify the text with scribble. If a text element is selected then opens the input prompt where you can modify this text with scribble.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-scribble-helper.jpg'></td></tr></table>
## Select Elements of Type
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Select%20Elements%20of%20Type.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Select%20Elements%20of%20Type.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Prompts you with a list of the different element types in the active image. Only elements of the selected type will be selected on the canvas. If nothing is selected when running the script, then the script will process all the elements on the canvas. If some elements are selected when the script is executed, then the script will only process the selected elements.<br>The script is useful when, for example, you want to bring to front all the arrows, or want to change the color of all the text elements, etc.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-select-element-of-type.jpg'></td></tr></table>
## Set background color of unclosed line object by adding a shadow clone
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20background%20color%20of%20unclosed%20line%20object%20by%20adding%20a%20shadow%20clone.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20background%20color%20of%20unclosed%20line%20object%20by%20adding%20a%20shadow%20clone.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Use this script to set the background color of unclosed (i.e. open) line objects by creating a clone of the object. The script will set the stroke color of the clone to transparent and will add a straight line to close the object. Use settings to define the default background color, the fill style, and the strokeWidth of the clone. By default the clone will be grouped with the original object, you can disable this also in settings.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-set-background-color-of-unclosed-line.jpg'></td></tr></table>
## Set Dimensions
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Dimensions.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20Dimensions.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Currently there is no way to specify the exact location and size of objects in Excalidraw. You can bridge this gap with the following simple script.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-dimensions.jpg'></td></tr></table>
## Set Font Family
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Font%20Family.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20Font%20Family.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Sets font family of the text block (Virgil, Helvetica, Cascadia). Useful if you want to set a keyboard shortcut for selecting font family.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-font-family.jpg'></td></tr></table>
## Set Grid
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Grid.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20Grid.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The default grid size in Excalidraw is 20. Currently there is no way to change the grid size via the user interface. This script offers a way to bridge this gap.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-grid.jpg'></td></tr></table>
## Set Link Alias
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Link%20Alias.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20Link%20Alias.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Iterates all of the links in the selected TextElements and prompts the user to set or modify the alias for each link found.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-set-link-alias.jpg'></td></tr></table>
## Set Stroke Width of Selected Elements
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Stroke%20Width%20of%20Selected%20Elements.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20Stroke%20Width%20of%20Selected%20Elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will set the stroke width of selected elements. This is helpful, for example, when you scale freedraw sketches and want to reduce or increase their line width.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-stroke-width.jpg'></td></tr></table>
## Set Text Alignment
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Text%20Alignment.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20Text%20Alignment.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Sets text alignment of text block (cetner, right, left). Useful if you want to set a keyboard shortcut for selecting text alignment.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-text-align.jpg'></td></tr></table>
## Split text by lines
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Split%20text%20by%20lines.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Split%20text%20by%20lines.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Split lines of text into separate text elements for easier reorganization<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-split-lines.jpg'></td></tr></table>
## TheBrain-navigation
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/TheBrain-navigation.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/TheBrain-navigation.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">An Excalidraw based graph user interface for your Vault. Requires the <a href='https://github.com/SkepticMystic/breadcrumbs'>Breadcrumbs plugin</a> to be installed and configured as well. Generates a user interface similar to that of <a href='https://TheBrain.com'>TheBrain</a>. Watch this introduction to this script on <a href='https://youtu.be/J4T5KHERH_o'>YouTube</a>.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/TheBrain.jpg'></td></tr></table>
## Toggle Fullscreen on Mobile
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Toggle%20Fullscreen%20on%20Mobile.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Toggle%20Fullscreen%20on%20Mobile.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Hides Obsidian workspace leaf padding and header (based on option in settings, default is "hide header" = false) which will take Excalidraw to full screen. ⚠ Note that if the header is not visible, it will be very difficult to invoke the command palette to end full screen. Only hide the header if you have a keyboard or you've practiced opening command palette!<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/ea-toggle-fullscreen.jpg'></td></tr></table>
## Transfer TextElements to Excalidraw markdown metadata
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Transfer%20TextElements%20to%20Excalidraw%20markdown%20metadata.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Transfer%20TextElements%20to%20Excalidraw%20markdown%20metadata.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script will delete the selected text elements from the canvas and will copy the text from these text elements into the Excalidraw markdown file as metadata. This means, that the text will no longer be visible in the drawing, however you will be able to search for the text in Obsidian and find the drawing containing this image.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-text-to-metadata.jpg'></td></tr></table>
## Zoom to Fit Selected Elements
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Zoom%20to%20Fit%20Selected%20Elements.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Zoom%20to%20Fit%20Selected%20Elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Similar to Excalidraw standard <kbd>SHIFT+2</kbd> feature: Zoom to fit selected elements, but with the ability to zoom to 1000%. Inspiration: [#272](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/272)</td></tr></table>

View File

@@ -0,0 +1,67 @@
/*
Automatically switches between the select and draw tools, based on whether a pen is being used.
1. Choose the select tool
2. Hover/use the pen to draw, move it away to return to select mode
*This is based on pen hover status, so will only work if your pen supports hover!*
If you click draw with the mouse or press select with the pen, switching will be disabled until the opposite input method is used.
**Note:** This script will stay active until the *Obsidian* window is closed.
Compatible with my *Hardware Eraser Support* script
```javascript
*/
(function() {
'use strict';
let promise
let timeout
let disable
function handlePointer(e) {
ea.setView("active");
var activeTool = ea.getExcalidrawAPI().getAppState().activeTool;
function setActiveTool(t) {
ea.getExcalidrawAPI().setActiveTool(t)
}
if (e.pointerType === 'pen') {
if (disable) return
if (!promise && activeTool.type==='selection') {
setActiveTool({type:"freedraw"})
}
if (timeout) clearTimeout(timeout)
function setTimeoutX(a,b) {
timeout = setTimeout(a,b)
return timeout
}
function revert() {
activeTool = ea.getExcalidrawAPI().getAppState().activeTool;
disable = false
if (activeTool.type==='freedraw') {
setActiveTool({type:"selection"})
} else if (activeTool.type==='selection') {
disable = true
}
promise = false
}
promise = new Promise(resolve => setTimeoutX(resolve, 500))
promise.then(() => revert())
}
}
function handleClick(e) {
ea.setView("active");
if (e.pointerType !== 'pen') {
disable = false
}
}
window.addEventListener('pointermove', handlePointer, { capture: true })
window.addEventListener('pointerdown', handleClick, { capture: true })
})();

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 448 512" style="enable-background:new 0 0 448 512;" xml:space="preserve">
<style type="text/css">
.st0{stroke:#000000;stroke-width:2;stroke-miterlimit:10;}
</style>
<g>
<g>
<path class="st0" d="M355.8,234.1"/>
</g>
<g>
<path d="M32.3,139.7l28.8,24.2l63.5-71.7L95.7,67c-7.2-6.3-18.2-5.6-24.5,1.6l-40.6,46.6C24.3,122.4,25,133.3,32.3,139.7z"/>
<path d="M61.2,165.3l-29.6-24.9c-3.7-3.3-5.9-7.8-6.3-12.7c-0.3-4.9,1.3-9.6,4.5-13.2L70.5,68c6.7-7.6,18.3-8.4,25.9-1.7L126,92.1
L61.2,165.3z M32.9,138.9l28,23.6l62.2-70.2l-28-24.6c-6.8-5.9-17.1-5.2-23.1,1.5l-40.6,46.6c-2.9,3.3-4.3,7.5-4,11.8
C27.6,132,29.6,136,32.9,138.9z"/>
</g>
<g>
<polygon points="218.7,240.1 212.3,168.6 197.2,155.4 133.7,228.1 148.9,241.3 "/>
<path d="M148.5,242.3l-16.2-14.1l64.8-74.2l16.2,14.1l6.5,73L148.5,242.3z M135.1,228l14.1,12.3l68.4-1.2l-6.2-70.1l-14.1-12.3
L135.1,228z"/>
</g>
<g>
<polygon points="192.6,151.6 129.1,224.3 66.2,168.4 129.6,96.7 "/>
<path d="M129.2,225.7l-64.5-57.2l64.8-73.2l64.5,56.2L129.2,225.7z M67.6,168.3l61.5,54.6l62.2-71.2l-61.5-53.6L67.6,168.3z"/>
</g>
<g>
<path d="M109.7,381.6c-23.7-22.2-40-49.3-48.9-78.2c8.2-0.9,22.4-3.6,30.1-12.3c-12.6-12.5-25.3-25-37.9-37.5c0-0.1,0-0.3,0-0.4
l-23.6-22c-6,60.7,15.5,123.7,63.7,168.8s112.5,62.4,172.7,52.4l-24.1-22.6C194.8,432.3,146.9,416.4,109.7,381.6z"/>
<path d="M232.6,456.1c-19.6,0-39.2-2.8-57.9-8.3c-30.9-9.1-58.6-24.9-82.3-47.1C68.7,378.6,51.1,352,40,321.8
c-10.6-28.8-14.6-60.2-11.6-90.7l0.2-2L54,252.8v0.4c6.2,6.1,12.4,12.3,18.6,18.4c6.3,6.3,12.7,12.5,19,18.8l0.7,0.7l-0.6,0.7
c-7.2,8.1-19.8,11.3-29.5,12.5c9.2,29.2,25.9,55.6,48.3,76.6l0,0c35.8,33.5,82.4,50.5,131.3,47.9l0.4,0l25.9,24.3l-2,0.3
C255,455.2,243.8,456.1,232.6,456.1z M30.2,233.3c-5.6,62.5,17.5,122.9,63.6,166c46,43.1,107.8,62.1,169.8,52.5l-22.3-20.9
c-49.2,2.5-96.2-14.7-132.3-48.5l0,0c-22.9-21.5-39.9-48.7-49.2-78.6l-0.4-1.2l1.2-0.1c9.3-1,21.6-3.8,28.8-11.3
c-6.1-6-12.2-12.1-18.3-18.1c-6.3-6.2-12.6-12.5-18.9-18.7L52,254v-0.4L30.2,233.3z"/>
</g>
<g>
<path d="M368.8,105.4c-56-52.4-133.5-67.2-201.3-45.5l21,19.6c56.1-13.2,117.7,1.1,163.1,43.7c33,30.9,51.7,71.2,55.9,112.7
c-0.2-0.4-0.3-0.6-0.3-0.6s-25.1-0.1-36.5,12.7c11.8,11.6,23.5,23.3,35.3,34.9v0.1l2.4,2.3c0.4,0.4,0.9,0.9,1.3,1.3l0,0l17.7,16.6
C444.7,234.1,424.8,157.7,368.8,105.4z"/>
<path d="M428,305.1L409,287.3l-1.3-1.3l-2.7-2.6v-0.1c-5.8-5.7-11.5-11.4-17.3-17.1c-5.9-5.8-11.8-11.7-17.7-17.5l-0.7-0.7
l0.6-0.7c10.5-11.8,31.7-12.9,36.4-13c-4.7-42.1-24.3-81.3-55.4-110.4c-43.5-40.9-104.2-57.1-162.2-43.5l-0.5,0.1l-22.6-21.1
l1.6-0.5c70.5-22.6,148-5,202.3,45.7c54.3,50.7,76.9,126.9,58.9,198.8L428,305.1z M407,282.6l3.4,3.3l16.4,15.4
c17.1-70.7-5.3-145.3-58.7-195.2l0,0c-53.3-49.9-129.3-67.4-198.7-45.8l19.4,18.1c58.5-13.6,119.6,2.9,163.5,44.1
c31.9,29.8,51.8,70.1,56.2,113.3l0.5,5.4l-2.5-4.9c-3.8,0.1-24.3,1.1-34.5,11.7c5.7,5.6,11.3,11.2,17,16.8
c5.9,5.8,11.7,11.6,17.6,17.4L407,282.6L407,282.6z"/>
</g>
<polygon points="425.2,382.2 302.7,283.9 299.4,437.6 340.9,383.8 382.3,456.5 398,447.5 359.4,379.8 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

399
ea-scripts/Auto Layout.md Normal file
View File

@@ -0,0 +1,399 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-download-raw.jpg)
Download this file and save to your Obsidian Vault including the first line, or open it in "Raw" and copy the entire contents to Obsidian.
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-auto-layout.png)
This script performs automatic layout for the selected top-level grouping objects. It is powered by [elkjs](https://github.com/kieler/elkjs) and needs to be connected to the Internet.
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
if (
!ea.verifyMinimumPluginVersion ||
!ea.verifyMinimumPluginVersion("1.5.21")
) {
new Notice(
"This script requires a newer version of Excalidraw. Please install the latest version."
);
return;
}
settings = ea.getScriptSettings();
//set default values on first run
if (!settings["Layout Options JSON"]) {
settings = {
"Layout Options JSON": {
height: "450px",
value: `{\n "org.eclipse.elk.layered.crossingMinimization.semiInteractive": "true",\n "org.eclipse.elk.layered.considerModelOrder.components": "FORCE_MODEL_ORDER"\n}`,
description: `You can use layout options to configure the layout algorithm. A list of all options and further details of their exact effects is available in <a href="http://www.eclipse.org/elk/reference.html" rel="nofollow">ELK's documentation</a>.`,
},
};
ea.setScriptSettings(settings);
}
if (typeof ELK === "undefined") {
loadELK(doAutoLayout);
} else {
doAutoLayout();
}
async function doAutoLayout() {
const selectedElements = ea.getViewSelectedElements();
const groups = ea
.getMaximumGroups(selectedElements)
.map((g) => g.filter((el) => el.containerId == null)) // ignore text in stickynote
.filter((els) => els.length > 0);
const stickynotesMap = selectedElements
.filter((el) => el.containerId != null)
.reduce((result, el) => {
result.set(el.containerId, el);
return result;
}, new Map());
const elk = new ELK();
const knownLayoutAlgorithms = await elk.knownLayoutAlgorithms();
const layoutAlgorithms = knownLayoutAlgorithms
.map((knownLayoutAlgorithm) => ({
id: knownLayoutAlgorithm.id,
displayText:
knownLayoutAlgorithm.id === "org.eclipse.elk.layered" ||
knownLayoutAlgorithm.id === "org.eclipse.elk.radial" ||
knownLayoutAlgorithm.id === "org.eclipse.elk.mrtree"
? "* " +
knownLayoutAlgorithm.name +
": " +
knownLayoutAlgorithm.description
: knownLayoutAlgorithm.name + ": " + knownLayoutAlgorithm.description,
}))
.sort((lha, rha) => lha.displayText.localeCompare(rha.displayText));
const layoutAlgorithmsSimple = knownLayoutAlgorithms
.map((knownLayoutAlgorithm) => ({
id: knownLayoutAlgorithm.id,
displayText:
knownLayoutAlgorithm.id === "org.eclipse.elk.layered" ||
knownLayoutAlgorithm.id === "org.eclipse.elk.radial" ||
knownLayoutAlgorithm.id === "org.eclipse.elk.mrtree"
? "* " + knownLayoutAlgorithm.name
: knownLayoutAlgorithm.name,
}))
.sort((lha, rha) => lha.displayText.localeCompare(rha.displayText));
// const knownOptions = knownLayoutAlgorithms
// .reduce(
// (result, knownLayoutAlgorithm) => [
// ...result,
// ...knownLayoutAlgorithm.knownOptions,
// ],
// []
// )
// .filter((value, index, self) => self.indexOf(value) === index) // remove duplicates
// .sort((lha, rha) => lha.localeCompare(rha));
// console.log("knownOptions", knownOptions);
const selectedAlgorithm = await utils.suggester(
layoutAlgorithms.map((algorithmInfo) => algorithmInfo.displayText),
layoutAlgorithms.map((algorithmInfo) => algorithmInfo.id),
"Layout algorithm"
);
const knownNodePlacementStrategy = [
"SIMPLE",
"INTERACTIVE",
"LINEAR_SEGMENTS",
"BRANDES_KOEPF",
"NETWORK_SIMPLEX",
];
const knownDirections = [
"UNDEFINED",
"RIGHT",
"LEFT",
"DOWN",
"UP"
];
let nodePlacementStrategy = "BRANDES_KOEPF";
let componentComponentSpacing = "10";
let nodeNodeSpacing = "100";
let nodeNodeBetweenLayersSpacing = "100";
let discoComponentLayoutAlgorithm = "org.eclipse.elk.layered";
let direction = "UNDEFINED";
if (selectedAlgorithm === "org.eclipse.elk.layered") {
nodePlacementStrategy = await utils.suggester(
knownNodePlacementStrategy,
knownNodePlacementStrategy,
"Node placement strategy"
);
selectedDirection = await utils.suggester(
knownDirections,
knownDirections,
"Direction"
);
direction = selectedDirection??"UNDEFINED";
} else if (selectedAlgorithm === "org.eclipse.elk.disco") {
const componentLayoutAlgorithms = layoutAlgorithmsSimple.filter(al => al.id !== "org.eclipse.elk.disco");
const selectedDiscoComponentLayoutAlgorithm = await utils.suggester(
componentLayoutAlgorithms.map((algorithmInfo) => algorithmInfo.displayText),
componentLayoutAlgorithms.map((algorithmInfo) => algorithmInfo.id),
"Disco Connected Components Layout Algorithm"
);
discoComponentLayoutAlgorithm = selectedDiscoComponentLayoutAlgorithm??"org.eclipse.elk.layered";
}
if (
selectedAlgorithm === "org.eclipse.elk.box" ||
selectedAlgorithm === "org.eclipse.elk.rectpacking"
) {
nodeNodeSpacing = await utils.inputPrompt("Node Spacing", "number", "10");
} else {
let userSpacingStr = await utils.inputPrompt(
"Components Spacing, Node Spacing, Node Node Between Layers Spacing",
"number, number, number",
"10, 100, 100"
);
let userSpacingArr = (userSpacingStr??"").split(",");
componentComponentSpacing = userSpacingArr[0] ?? "10";
nodeNodeSpacing = userSpacingArr[1] ?? "100";
nodeNodeBetweenLayersSpacing = userSpacingArr[2] ?? "100";
}
let layoutOptionsJson = {};
try {
layoutOptionsJson = JSON.parse(settings["Layout Options JSON"].value);
} catch (e) {
new Notice(
"Error reading Layout Options JSON, see developer console for more information",
4000
);
console.log(e);
}
layoutOptionsJson["elk.algorithm"] = selectedAlgorithm;
layoutOptionsJson["org.eclipse.elk.spacing.componentComponent"] =
componentComponentSpacing;
layoutOptionsJson["org.eclipse.elk.spacing.nodeNode"] = nodeNodeSpacing;
layoutOptionsJson["org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers"] =
nodeNodeBetweenLayersSpacing;
layoutOptionsJson["org.eclipse.elk.layered.nodePlacement.strategy"] =
nodePlacementStrategy;
layoutOptionsJson["org.eclipse.elk.disco.componentCompaction.componentLayoutAlgorithm"] =
discoComponentLayoutAlgorithm;
layoutOptionsJson["org.eclipse.elk.direction"] = direction;
const graph = {
id: "root",
layoutOptions: layoutOptionsJson,
children: [],
edges: [],
};
let groupMap = new Map();
let targetElkMap = new Map();
let arrowEls = [];
for (let i = 0; i < groups.length; i++) {
const elements = groups[i];
if (
elements.length === 1 &&
(elements[0].type === "arrow" || elements[0].type === "line")
) {
if (
elements[0].type === "arrow" &&
elements[0].startBinding &&
elements[0].endBinding
) {
arrowEls.push(elements[0]);
}
} else {
let elkId = "g" + i;
elements.reduce((result, el) => {
result.set(el.id, elkId);
return result;
}, targetElkMap);
const box = ea.getBoundingBox(elements);
groupMap.set(elkId, {
elements: elements,
boundingBox: box,
});
graph.children.push({
id: elkId,
width: box.width,
height: box.height,
x: box.topX,
y: box.topY,
});
}
}
for (let i = 0; i < arrowEls.length; i++) {
const arrowEl = arrowEls[i];
const startElkId = targetElkMap.get(arrowEl.startBinding.elementId);
const endElkId = targetElkMap.get(arrowEl.endBinding.elementId);
graph.edges.push({
id: "e" + i,
sources: [startElkId],
targets: [endElkId],
});
}
const initTopX =
Math.min(...Array.from(groupMap.values()).map((v) => v.boundingBox.topX)) -
12;
const initTopY =
Math.min(...Array.from(groupMap.values()).map((v) => v.boundingBox.topY)) -
12;
elk
.layout(graph)
.then((resultGraph) => {
for (const elkEl of resultGraph.children) {
const group = groupMap.get(elkEl.id);
for (const groupEl of group.elements) {
const originalDistancX = groupEl.x - group.boundingBox.topX;
const originalDistancY = groupEl.y - group.boundingBox.topY;
const groupElDistanceX =
elkEl.x + initTopX + originalDistancX - groupEl.x;
const groupElDistanceY =
elkEl.y + initTopY + originalDistancY - groupEl.y;
groupEl.x = groupEl.x + groupElDistanceX;
groupEl.y = groupEl.y + groupElDistanceY;
if (stickynotesMap.has(groupEl.id)) {
const stickynote = stickynotesMap.get(groupEl.id);
stickynote.x = stickynote.x + groupElDistanceX;
stickynote.y = stickynote.y + groupElDistanceY;
}
}
}
ea.copyViewElementsToEAforEditing(selectedElements);
ea.addElementsToView(false, false);
normalizeSelectedArrows();
})
.catch(console.error);
}
function loadELK(doAfterLoaded) {
let script = document.createElement("script");
script.onload = function () {
if (typeof ELK !== "undefined") {
doAfterLoaded();
}
};
script.src =
"https://cdn.jsdelivr.net/npm/elkjs@0.8.2/lib/elk.bundled.min.js";
document.head.appendChild(script);
}
/*
* Normalize Selected Arrows
*/
function normalizeSelectedArrows() {
let gapValue = 2;
const selectedIndividualArrows = ea.getMaximumGroups(ea.getViewSelectedElements())
.reduce((result, g) => [...result, ...g.filter(el => el.type === 'arrow')], []);
const allElements = ea.getViewElements();
for (const arrow of selectedIndividualArrows) {
const startBindingEl = allElements.filter(
(el) => el.id === (arrow.startBinding || {}).elementId
)[0];
const endBindingEl = allElements.filter(
(el) => el.id === (arrow.endBinding || {}).elementId
)[0];
if (startBindingEl) {
recalculateStartPointOfLine(
arrow,
startBindingEl,
endBindingEl,
gapValue
);
}
if (endBindingEl) {
recalculateEndPointOfLine(arrow, endBindingEl, startBindingEl, gapValue);
}
}
ea.copyViewElementsToEAforEditing(selectedIndividualArrows);
ea.addElementsToView(false, false);
}
function recalculateStartPointOfLine(line, el, elB, gapValue) {
const aX = el.x + el.width / 2;
const bX =
line.points.length <= 2 && elB
? elB.x + elB.width / 2
: line.x + line.points[1][0];
const aY = el.y + el.height / 2;
const bY =
line.points.length <= 2 && elB
? elB.y + elB.height / 2
: line.y + line.points[1][1];
line.startBinding.gap = gapValue;
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 (let 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];
}
}
function recalculateEndPointOfLine(line, el, elB, gapValue) {
const aX = el.x + el.width / 2;
const bX =
line.points.length <= 2 && elB
? elB.x + elB.width / 2
: line.x + line.points[line.points.length - 2][0];
const aY = el.y + el.height / 2;
const bY =
line.points.length <= 2 && elB
? elB.y + elB.height / 2
: line.y + line.points[line.points.length - 2][1];
line.endBinding.gap = gapValue;
line.endBinding.focus = 0;
const intersectA = ea.intersectElementWithLine(
el,
[bX, bY],
[aX, aY],
line.endBinding.gap
);
if (intersectA.length > 0) {
line.points[line.points.length - 1] = [
intersectA[0][0] - line.x,
intersectA[0][1] - line.y,
];
}
}

View File

@@ -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="1670131481615" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3504" width="128" height="128" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M947.2 0H76.8C33.6 0 0 33.6 0 76.8v870.4C0 990.4 33.6 1024 76.8 1024h870.4c38.4 0 72-30.4 76.8-68.8V76.8C1024 33.6 990.4 0 947.2 0zM84.8 84.8h852.8V256H84.8V84.8z m256 256h596.8v256H340.8v-256z m-256 598.4V340.8H256v596.8H84.8z m256 0v-256h596.8v256H340.8z" p-id="3505"></path></svg>

After

Width:  |  Height:  |  Size: 616 B

View File

@@ -0,0 +1,369 @@
/*
With This Script it is possible to make boolean Operations on Shapes.
The style of the resulting shape will be the style of the highest ranking Element that was used.
The ranking of the elements is based on their background. The "denser" the background, the higher the ranking (the order of backgroundstyles is shown below). If they have the same background the opacity will decide. If thats also the same its decided by the order they were created.
The ranking is also important for the difference operation, so a transparent object for example will cut a hole into a solid object.
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-boolean-operations-showcase.png)
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-boolean-operations-element-ranking.png)
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.9.20")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
const ShadowGroupMarker = "ShadowCloneOf-";
const elements = ea.getViewSelectedElements().filter(
el=>["ellipse", "rectangle", "diamond"].includes(el.type) ||
el.groupIds.some(id => id.startsWith(ShadowGroupMarker)) ||
(["line", "arrow"].includes(el.type) && el.roundness === null)
);
if(elements.length === 0) {
new Notice ("Select ellipses, rectangles or diamonds");
return;
}
const PolyBool = ea.getPolyBool();
const polyboolAction = await utils.suggester(["union (a + b)", "intersect (a && b)", "difference (a - b)", "reversed difference (b - a)", "xor"], [
PolyBool.union, PolyBool.intersect, PolyBool.difference, PolyBool.differenceRev, PolyBool.xor
], "What would you like todo with the object");
const shadowClones = elements.filter(element => element.groupIds.some(id => id.startsWith(ShadowGroupMarker)));
shadowClones.forEach(shadowClone => {
let parentId = shadowClone.groupIds
.filter(id => id.startsWith(ShadowGroupMarker))[0]
.slice(ShadowGroupMarker.length);
const shadowCloneIndex = elements.findIndex(element => element.id == parentId);
if (shadowCloneIndex == -1) return;
elements[shadowCloneIndex].backgroundColor = shadowClone.backgroundColor;
elements[shadowCloneIndex].fillStyle = shadowClone.fillStyle;
})
const borderElements = elements.filter(element => !element.groupIds.some(id => id.startsWith(ShadowGroupMarker)));
groups = ea.getMaximumGroups(borderElements);
groups = groups.map((group) => group.sort((a, b) => RankElement(b) - RankElement(a)));
groups.sort((a, b) => RankElement(b[0]) - RankElement(a[0]));
ea.style.strokeColor = groups[0][0].strokeColor;
ea.style.backgroundColor = groups[0][0].backgroundColor;
ea.style.fillStyle = groups[0][0].fillStyle;
ea.style.strokeWidth = groups[0][0].strokeWidth;
ea.style.strokeStyle = groups[0][0].strokeStyle;
ea.style.roughness = groups[0][0].roughness;
ea.style.opacity = groups[0][0].opacity;
const basePolygons = groups.shift().map(element => traceElement(element));
const toolPolygons = groups.flatMap(group => group.map(element => traceElement(element)));
const result = polyboolAction({
regions: basePolygons,
inverted: false
}, {
regions: toolPolygons,
inverted: false
});
const polygonHierachy = subordinateInnerPolygons(result.regions);
drawPolygonHierachy(polygonHierachy);
ea.deleteViewElements(elements);
ea.addElementsToView(false,false,true);
return;
function traceElement(element) {
const diamondPath = (diamond) => [
SxVEC(1/2, [0, diamond.height]),
SxVEC(1/2, [diamond.width, 0]),
addVec([SxVEC(1/2, [0, diamond.height]), ([diamond.width, 0])]),
addVec([SxVEC(1/2, [diamond.width, 0]), ([0, diamond.height])]),
SxVEC(1/2, [0, diamond.height])
];
const rectanglePath = (rectangle) => [
[0,0],
[0, rectangle.height],
[rectangle.width, rectangle.height],
[rectangle.width, 0],
[0, 0]
]
const ellipsePath = (ellipse) => {
const angle = ellipse.angle;
const width = ellipse.width;
const height = ellipse.height;
const ellipseAtPoint = (t) => {
const spanningVector = [width/2*Math.cos(t), height/2*Math.sin(t)];
const baseVector = [width/2, height/2];
return addVec([spanningVector, baseVector]);
}
let points = [];
step = (2*Math.PI)/64
for (let t = 0; t < 2*Math.PI; t = t + step) {
points.push(ellipseAtPoint(t));
}
return points;
}
let polygon;
let correctForPolygon = [0, 0];
switch (element.type) {
case "diamond":
polygon = diamondPath(element);
break;
case "rectangle":
polygon = rectanglePath(element);
break;
case "ellipse":
polygon = ellipsePath(element);
break;
case "line":
case "arrow":
if (element.angle != 0) {
let smallestX = 0;
let smallestY = 0;
element.points.forEach(point => {
if (point[0] < smallestX) smallestX = point[0];
if (point[1] < smallestY) smallestY = point[1];
});
polygon = element.points.map(point => {
return [
point[0] -= smallestX,
point[1] -= smallestY
];
});
correctForPolygon = [smallestX, smallestY];
break;
}
if (element.roundness) {
new Notice("This script does not work with curved lines or arrows yet!");
return [];
}
polygon = element.points;
default:
break;
}
if (element.angle == 0) return polygon.map(v => addVec([v, [element.x, element.y]]));
polygon = polygon.map(v => addVec([v, SxVEC(-1/2, [element.width, element.height])]));
polygon = rotateVectorsByAngle(polygon, element.angle);
return polygon.map(v => addVec([v, [element.x, element.y], SxVEC(1/2, [element.width, element.height]), correctForPolygon]));
}
function RankElement(element) {
let score = 0;
const backgroundRank = [
"dashed",
"none",
"hachure",
"zigzag",
"zigzag-line",
"cross-hatch",
"solid"
]
score += (backgroundRank.findIndex((fillStyle) => fillStyle == element.fillStyle) + 1) * 10;
if (element.backgroundColor == "transparent") score -= 100;
if (element.points && getVectorLength(element.points[element.points.length - 1]) > 8) score -= 100;
if (score < 0) score = 0;
score += element.opacity / 100;
return score;
}
function drawPolygonHierachy(polygonHierachy) {
const backgroundColor = ea.style.backgroundColor;
const strokeColor = ea.style.strokeColor;
const setInnerStyle = () => {
ea.style.backgroundColor = backgroundColor;
ea.style.strokeColor = "transparent";
}
const setBorderStyle = () => {
ea.style.backgroundColor = "transparent";
ea.style.strokeColor = strokeColor;
}
const setFilledStyle = () => {
ea.style.backgroundColor = backgroundColor;
ea.style.strokeColor = strokeColor;
}
polygonHierachy.forEach(polygon => {
setFilledStyle();
let path = polygon.path;
path.push(polygon.path[0]);
if (polygon.innerPolygons.length === 0) {
ea.addLine(path);
return;
}
const outerBorder = path;
const innerPolygons = addInnerPolygons(polygon.innerPolygons);
path = path.concat(innerPolygons.backgroundPath);
path.push(polygon.path[0]);
setInnerStyle();
const backgroundId = ea.addLine(path);
setBorderStyle();
const outerBorderId = ea.addLine(outerBorder)
const innerBorderIds = innerPolygons.borderPaths.map(path => ea.addLine(path));
const allIds = [innerBorderIds, outerBorderId, backgroundId].flat();
ea.addToGroup(allIds);
const background = ea.getElement(backgroundId);
background.groupIds.push(ShadowGroupMarker + outerBorderId);
});
}
function addInnerPolygons(polygonHierachy) {
let firstPath = [];
let secondPath = [];
let borderPaths = [];
polygonHierachy.forEach(polygon => {
let path = polygon.path;
path.push(polygon.path[0]);
borderPaths.push(path);
firstPath = firstPath.concat(path);
secondPath.push(polygon.path[0]);
drawPolygonHierachy(polygon.innerPolygons);
});
return {
backgroundPath: firstPath.concat(secondPath.reverse()),
borderPaths: borderPaths
};
}
function subordinateInnerPolygons(polygons) {
const polygonObjectPrototype = (polygon) => {
return {
path: polygon,
innerPolygons: []
};
}
const insertPolygonIntoHierachy = (polygon, hierarchy) => {
for (let i = 0; i < hierarchy.length; i++) {
const polygonObject = hierarchy[i];
let inside = null;
let pointIndex = 0;
do {
inside = pointInPolygon(polygon[pointIndex], polygonObject.path);
pointIndex++
} while (inside === null);
if (inside) {
hierarchy[i].innerPolygons = insertPolygonIntoHierachy(polygon, hierarchy[i].innerPolygons);
return hierarchy;
}
}
polygon = polygonObjectPrototype(polygon);
for (let i = 0; i < hierarchy.length; i++) {
const polygonObject = hierarchy[i];
let inside = null;
let pointIndex = 0;
do {
inside = pointInPolygon(polygonObject.path[pointIndex], polygon.path);
pointIndex++
} while (inside === null);
if (inside) {
polygon.innerPolygons.push(hierarchy.splice(i, 1)[0]);
i--;
}
}
hierarchy.push(polygon);
return hierarchy;
}
let polygonHierachy = [];
polygons.forEach(polygon => {
polygonHierachy = insertPolygonIntoHierachy(polygon, polygonHierachy);
})
return polygonHierachy;
}
/**
* Checks if the given point lays in the polygon
* @param point array [x, y]
* @param polygon array [[x, y], ...]
* @returns true if inside, false if not, null if the point is on one of the polygons vertecies
*/
function pointInPolygon(point, polygon) {
const x = point[0];
const y = point[1];
let inside = false;
// odd even test if point is in polygon
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
const xi = polygon[i][0];
const yi = polygon[i][1];
const xj = polygon[j][0];
const yj = polygon[j][1];
const intersect =
yi > y !== yj > y &&
x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
if (intersect) {
inside = !inside;
}
if ((x === xi && y === yi) || (x === xj && y === yj)) {
return null;
}
}
return inside;
}
function getVectorLength(vector) {
return Math.sqrt(vector[0]**2+vector[1]**2);
}
/**
* Adds two Vectors together
*/
function addVec(vectors) {
return vectors.reduce((acc, vec) => [acc[0] + vec[0], acc[1] + vec[1]], [0, 0]);
}
/**
* Returns the negative of the vector
*/
function negVec(vector) {
return [-vector[0], -vector[1]];
}
/**
* Multiplies Vector with a scalar
*/
function SxVEC(scalar, vector) {
return [vector[0] * scalar, vector[1] * scalar];
}
function rotateVector (vec, ang) {
var cos = Math.cos(ang);
var sin = Math.sin(ang);
return [vec[0] * cos - vec[1] * sin, vec[0] * sin + vec[1] * cos];
}
function rotateVectorsByAngle(vectors, angle) {
const cosAngle = Math.cos(angle);
const sinAngle = Math.sin(angle);
const rotationMatrix = [
[cosAngle, -sinAngle],
[sinAngle, cosAngle]
];
return applyTranformationMatrix(vectors, rotationMatrix);
}
function applyTranformationMatrix(vectors, transformationMatrix) {
const result = [];
for (const vector of vectors) {
const x = vector[0];
const y = vector[1];
const newX = transformationMatrix[0][0] * x + transformationMatrix[0][1] * y;
const newY = transformationMatrix[1][0] * x + transformationMatrix[1][1] * y;
result.push([newX, newY]);
}
return result;
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,102 @@
/*
Connects two lines. Lines may be type of arrow or line. The resulting line will carry the style of the line higher in the drawing layers (bring to front the one you want to control the look and feel). Arrows are connected intelligently.
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-concatenate-lines.png)
```js*/
const lines = ea.getViewSelectedElements().filter(el=>el.type==="line" || el.type==="arrow");
if(lines.length !== 2) {
new Notice ("Select two lines or arrows");
return;
}
// https://math.stackexchange.com/questions/2204520/how-do-i-rotate-a-line-segment-in-a-specific-point-on-the-line
const rotate = (point, element) => {
const [x1, y1] = point;
const x2 = element.x + element.width/2;
const y2 = element.y - element.height/2;
const angle = element.angle;
return [
(x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle) + x2,
(x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2,
];
}
const points = lines.map(
el=>el.points.map(p=>rotate([p[0]+el.x, p[1]+el.y],el))
);
const last = (p) => p[p.length-1];
const first = (p) => p[0];
const distance = (p1,p2) => Math.sqrt((p1[0]-p2[0])**2+(p1[1]-p2[1])**2);
const distances = [
distance(first(points[0]),first(points[1])),
distance(first(points[0]),last (points[1])),
distance(last (points[0]),first(points[1])),
distance(last (points[0]),last (points[1]))
];
const connectDirection = distances.indexOf(Math.min(...distances));
let newPoints = [];
switch(connectDirection) {
case 0: //first-first
newPoints = [...points[0].reverse(),...points[1].slice(1)];
break;
case 1: //first-last
newPoints = [...points[0].reverse(),...points[1].reverse().slice(1)];
break;
case 2: //last-first
newPoints = [...points[0],...points[1].slice(1)];
break;
case 3: //last-last
newPoints = [...points[0],...points[1].reverse().slice(1)];
break;
}
["strokeColor", "backgrounColor", "fillStyle", "roundness", "roughness", "strokeWidth", "strokeStyle", "opacity"].forEach(prop=>{
ea.style[prop] = lines[1][prop];
})
ea.style.startArrowHead = null;
ea.style.endArrowHead = null;
ea.copyViewElementsToEAforEditing(lines);
ea.getElements().forEach(el=>{el.isDeleted = true});
const lineTypes = parseInt(lines.map(line => line.type === "line" ? '1' : '0').join(''),2);
switch (lineTypes) {
case 0: //arrow - arrow
ea.addArrow(
newPoints,
connectDirection === 0 //first-first
? { startArrowHead: lines[0].endArrowhead, endArrowHead: lines[1].endArrowhead }
: connectDirection === 1 //first-last
? { startArrowHead: lines[0].endArrowhead, endArrowHead: lines[1].startArrowhead }
: connectDirection === 2 //last-first
? { startArrowHead: lines[0].startArrowhead, endArrowHead: lines[1].endArrowhead }
//3: last-last
: { startArrowHead: lines[0].startArrowhead, endArrowHead: lines[1].startArrowhead }
);
break;
case 1: //arrow - line
reverse = connectDirection === 0 || connectDirection === 1;
ea.addArrow(newPoints,{
startArrowHead: reverse ? lines[0].endArrowhead : lines[0].startArrowhead,
endArrowHead: reverse ? lines[0].startArrowhead : lines[0].endArrowhead
});
break;
case 2: //line - arrow
reverse = connectDirection === 1 || connectDirection === 3;
ea.addArrow(newPoints,{
startArrowHead: reverse ? lines[1].endArrowhead : lines[1].startArrowhead,
endArrowHead: reverse ? lines[1].startArrowhead : lines[1].endArrowhead
});
break;
case 3: //line - line
ea.addLine(newPoints);
break;
}
ea.addElementsToView();

View File

@@ -0,0 +1,17 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72.75819749055177 80.03703336574608" width="72.75819749055177" height="80.03703336574608">
<!-- svg-source:excalidraw -->
<defs>
<style class="style-fonts">
@font-face {
font-family: "Virgil";
src: url("https://excalidraw.com/Virgil.woff2");
}
@font-face {
font-family: "Cascadia";
src: url("https://excalidraw.com/Cascadia.woff2");
}
</style>
</defs>
<g stroke-linecap="round"><g transform="translate(4 4) rotate(0 12.71901889991409 17.183109917454658)"><path d="M0 0 C0 7.02, 0 14.05, 0 34.37 M0 34.37 C7.62 34.37, 15.24 34.37, 25.44 34.37" stroke="black" stroke-width="4.5" fill="none" stroke-dasharray="1.5 10"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(51.379765518092086 61.93633577499986) rotate(0 5.684341886080802e-14 7.050348795373111)"><path d="M0 0 C0 4.06, 0 8.11, 0 14.1" stroke="black" stroke-width="4.5" fill="none" stroke-dasharray="1.5 10"></path></g></g><mask></mask><g stroke-linecap="round" transform="translate(34.0013341989918 20.987787610339183) rotate(0 17.378431645779926 17.378431645779983)"><path d="M34.76 17.38 C34.76 18.38, 34.67 19.41, 34.49 20.4 C34.32 21.39, 34.05 22.38, 33.71 23.32 C33.36 24.27, 32.93 25.2, 32.43 26.07 C31.93 26.94, 31.34 27.78, 30.69 28.55 C30.04 29.32, 29.32 30.04, 28.55 30.69 C27.78 31.34, 26.94 31.93, 26.07 32.43 C25.2 32.93, 24.27 33.36, 23.32 33.71 C22.38 34.05, 21.39 34.32, 20.4 34.49 C19.41 34.67, 18.38 34.76, 17.38 34.76 C16.37 34.76, 15.35 34.67, 14.36 34.49 C13.37 34.32, 12.38 34.05, 11.43 33.71 C10.49 33.36, 9.56 32.93, 8.69 32.43 C7.82 31.93, 6.98 31.34, 6.21 30.69 C5.44 30.04, 4.71 29.32, 4.07 28.55 C3.42 27.78, 2.83 26.94, 2.33 26.07 C1.83 25.2, 1.39 24.27, 1.05 23.32 C0.7 22.38, 0.44 21.39, 0.26 20.4 C0.09 19.41, 0 18.38, 0 17.38 C0 16.37, 0.09 15.35, 0.26 14.36 C0.44 13.37, 0.7 12.38, 1.05 11.43 C1.39 10.49, 1.83 9.56, 2.33 8.69 C2.83 7.82, 3.42 6.98, 4.07 6.21 C4.71 5.44, 5.44 4.71, 6.21 4.07 C6.98 3.42, 7.82 2.83, 8.69 2.33 C9.56 1.83, 10.49 1.39, 11.43 1.05 C12.38 0.7, 13.37 0.44, 14.36 0.26 C15.35 0.09, 16.37 0, 17.38 0 C18.38 0, 19.41 0.09, 20.4 0.26 C21.39 0.44, 22.38 0.7, 23.32 1.05 C24.27 1.39, 25.2 1.83, 26.07 2.33 C26.94 2.83, 27.78 3.42, 28.55 4.07 C29.32 4.71, 30.04 5.44, 30.69 6.21 C31.34 6.98, 31.93 7.82, 32.43 8.69 C32.93 9.56, 33.36 10.49, 33.71 11.43 C34.05 12.38, 34.32 13.37, 34.49 14.36 C34.67 15.35, 34.71 16.88, 34.76 17.38 C34.8 17.88, 34.8 16.88, 34.76 17.38" stroke="black" stroke-width="4" fill="none"></path></g><g stroke-linecap="round"><g transform="translate(41.72257566145686 38.36621939788711) rotate(0 9.65718949485347 0)"><path d="M0 0 C4.11 0, 8.22 0, 19.31 0 M0 0 C6.95 0, 13.9 0, 19.31 0" stroke="black" stroke-width="4" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(41.72257587602678 38.36622004108449) rotate(89.99999999999994 9.65718949485347 0)"><path d="M0 0 C5.31 0, 10.62 0, 19.31 0 M0 0 C4.56 0, 9.13 0, 19.31 0" stroke="black" stroke-width="4" fill="none"></path></g></g><mask></mask></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -9,13 +9,12 @@ if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.21")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
settings = ea.getScriptSettings();
let settings = ea.getScriptSettings();
//set default values on first run
if(!settings["Border color"]) {
settings = {
"Border color" : {
value: "transparent",
value: "#000000",
description: "Any legal HTML color (#000000, rgb, color-name, etc.). Set to 'transparent' for transparent color."
},
"Background color" : {
@@ -28,27 +27,44 @@ if(!settings["Border color"]) {
valueset: ["hachure","cross-hatch","solid"]
}
};
ea.setScriptSettings(settings);
await ea.setScriptSettings(settings);
}
if(!settings["Max sticky note width"]) {
settings["Max sticky note width"] = {
value: "600",
description: "Maximum width of new sticky note. If text is longer, it will be wrapped",
valueset: ["400","600","800","1000","1200","1400","2000"]
}
await ea.setScriptSettings(settings);
}
const maxWidth = parseInt(settings["Max sticky note width"].value);
const strokeColor = settings["Border color"].value;
const backgroundColor = settings["Background color"].value;
const fillStyle = settings["Background fill style"].value;
elements = ea.getViewSelectedElements()
.filter((el)=>(el.type==="text")&&(el.containerId===null));
if(elements.length===0) return;
const elements = ea
.getViewSelectedElements()
.filter((el)=>(el.type==="text")&&(el.containerId===null));
if(elements.length===0) {
new Notice("Please select a text element");
return;
}
ea.style.strokeColor = strokeColor;
ea.style.backgroundColor = backgroundColor;
ea.style.fillStyle = fillStyle;
const padding = 6;
let boxes = [];
elements.forEach((el)=>{
const id = ea.addRect(el.x-padding,el.y-padding,el.width+2*padding,el.height+2*padding);
const boxes = [];
ea.copyViewElementsToEAforEditing(elements);
ea.getElements().forEach((el)=>{
const width = el.width+2*padding;
const widthOK = width<=maxWidth;
const id = ea.addRect(el.x-padding,el.y-padding,widthOK?width:maxWidth,el.height+2*padding);
boxes.push(id);
ea.getElement(id).boundElements=[{type:"text",id:el.id}];
el.containerId = id;
});
ea.copyViewElementsToEAforEditing(elements);
await ea.addElementsToView(false,false);
ea.selectElementsInView(ea.getViewElements().filter(el=>boxes.includes(el.id)));
await ea.addElementsToView(false,true);
const containers = ea.getViewElements().filter(el=>boxes.includes(el.id));
ea.getExcalidrawAPI().updateContainerSize(containers);
ea.selectElementsInView(containers);

View File

@@ -0,0 +1,33 @@
/*
Creates a new draw.io diagram file and opens the file in the [Diagram plugin](https://github.com/zapthedingbat/drawio-obsidian) in a new tab.
```js*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.9.7")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
const drawIO = app.plugins.plugins["drawio-obsidian"];
if(!drawIO || !drawIO?._loaded) {
new Notice("Can't find the draw.io diagram plugin");
}
filename = await utils.inputPrompt("Diagram name?");
if(!filename) return;
filename = filename.toLowerCase().endsWith(".svg") ? filename : filename + ".svg";
const filepath = await ea.getAttachmentFilepath(filename);
if(!filepath) return;
const leaf = app.workspace.getLeaf('tab')
if(!leaf) return;
const file = await this.app.vault.create(filepath, `<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><!--${ea.generateElementId()}--><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="300px" height="300px" viewBox="-0.5 -0.5 1 1" content="&lt;mxGraphModel&gt;&lt;root&gt;&lt;mxCell id=&quot;0&quot;/&gt;&lt;mxCell id=&quot;1&quot; parent=&quot;0&quot;/&gt;&lt;/root&gt;&lt;/mxGraphModel&gt;"></svg>`);
await ea.addImage(0,0,file);
await ea.addElementsToView(true,true);
leaf.setViewState({
type: "diagram-edit",
state: {
file: filepath
}
});

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="24 26 68 68" stroke="#000"><path fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="3.553" d="m58.069 43.384-17.008 29.01"/><path fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="3.501" d="m58.068 43.384 17.008 29.01"/><path fill="#000" d="M52.773 77.084a3.564 3.564 0 0 1-3.553 3.553H36.999a3.564 3.564 0 0 1-3.553-3.553v-9.379a3.564 3.564 0 0 1 3.553-3.553h12.222a3.564 3.564 0 0 1 3.553 3.553v9.379zM67.762 48.074a3.564 3.564 0 0 1-3.553 3.553H51.988a3.564 3.564 0 0 1-3.553-3.553v-9.379a3.564 3.564 0 0 1 3.553-3.553H64.21a3.564 3.564 0 0 1 3.553 3.553v9.379zM82.752 77.084a3.564 3.564 0 0 1-3.553 3.553H66.977a3.564 3.564 0 0 1-3.553-3.553v-9.379a3.564 3.564 0 0 1 3.553-3.553h12.222a3.564 3.564 0 0 1 3.553 3.553v9.379z"/></svg>

After

Width:  |  Height:  |  Size: 830 B

View File

@@ -0,0 +1,75 @@
/*
Adds a rounded mask to the image by adding a full cover black mask and a rounded rectangle white mask. The script is also useful for adding just a black mask. In this case, run the script, then delete the white mask and add your custom white mask.
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-crop-vintage.jpg)
```js*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.0.18")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
if(!ea.isExcalidrawMaskFile()) {
new Notice("This script only works with Mask Files");
return;
}
const frames = ea.getViewElements().filter(el=>el.type==="frame")
if(frames.length !== 1) {
new Notice("Multiple frames found");
return;
}
const frame = frames[0];
ea.copyViewElementsToEAforEditing(ea.getViewElements().filter(el=>el.frameId === frame.id));
const frameId = ea.generateElementId();
ea.style.fillStyle = "solid";
ea.style.roughness = 0;
ea.style.strokeColor = "transparent";
ea.style.strokeWidth = 0.1;
ea.style.opacity = 50;
let blackEl = ea.getViewElements().find(el=>el.id === "allblack");
let whiteEl = ea.getViewElements().find(el=>el.id === "whiteovr");
if(blackEl && whiteEl) {
ea.copyViewElementsToEAforEditing([blackEl, whiteEl]);
} else
if (blackEl && !whiteEl) {
ea.copyViewElementsToEAforEditing([blackEl]);
ea.style.backgroundColor = "white";
ea.addRect(frame.x,frame.y,frame.width,frame.height, "whiteovr");
} else
if (!blackEl && whiteEl) {
ea.style.backgroundColor = "black";
ea.addRect(frame.x-2,frame.y-2,frame.width+4,frame.height+4, "allblack");
ea.copyViewElementsToEAforEditing([whiteEl]);
} else {
ea.style.backgroundColor = "black";
ea.addRect(frame.x-2,frame.y-2,frame.width+4,frame.height+4, "allblack");
ea.style.backgroundColor = "white";
ea.addRect(frame.x,frame.y,frame.width,frame.height, "whiteovr");
}
blackEl = ea.getElement("allblack");
whiteEl = ea.getElement("whiteovr");
//this "magic" is required to ensure the frame element is above in sequence of the new rectangle elements
ea.getElements().forEach(el=>{el.frameId = frameId});
ea.copyViewElementsToEAforEditing(ea.getViewElements().filter(el=>el.id === frame.id));
const newFrame = ea.getElement(frame.id);
newFrame.id = frameId;
ea.elementsDict[frameId] = newFrame;
ea.copyViewElementsToEAforEditing(ea.getViewElements().filter(el=>el.id === frame.id));
ea.getElement(frame.id).isDeleted = true;
let curve = await utils.inputPrompt(
"Set roundess",
"Positive whole number",
`${whiteEl.roundness?.value ?? "500"}`
);
if(!curve) return;
curve = parseInt(curve);
if(isNaN(curve) || curve < 0) {
new Notice ("Roudness is not a valid positive whole number");
return;
}
whiteEl.roundness = {type: 3, value: curve};
ea.addElementsToView(false,false,true);

View File

@@ -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="lucide lucide-squircle"><path fill="none" d="M12 3c7.2 0 9 1.8 9 9s-1.8 9-9 9-9-1.8-9-9 1.8-9 9-9"/></svg>

After

Width:  |  Height:  |  Size: 294 B

15
ea-scripts/Custom Zoom.md Normal file
View File

@@ -0,0 +1,15 @@
/*
You can set a custom zoom level with this script. This allows you to set a zoom level below 10% or set the zoom level to a specific value. Note however, that Excalidraw has a bug under 10% zoom, and a phantom copy of your image may appear on screen. If this happens, increase the zoom and the phantom should disappear, if it doesn't then close and open the drawing.
```js*/
const api = ea.getExcalidrawAPI();
const appState = api.getAppState();
const zoomStr = await utils.inputPrompt("Zoom [%]",null,`${appState.zoom.value*100}%`);
if(!zoomStr) return;
const zoomNum = parseFloat(zoomStr.match(/^\d*/)[0]);
if(isNaN(zoomNum)) {
new Notice("You must provide a number");
return;
}
ea.getExcalidrawAPI().updateScene({appState:{zoom:{value: zoomNum/100 }}});

View File

@@ -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="lucide lucide-scan-search"><g stroke-width="2"><path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><circle cx="12" cy="12" r="3"/><path d="m16 16-1.9-1.9"/></g></svg>

After

Width:  |  Height:  |  Size: 444 B

View File

@@ -3,13 +3,70 @@
Select some elements in the scene. The script will take these elements and move them into a new Excalidraw file, and open that file. The selected elements will also be replaced in your original drawing with the embedded Excalidraw file (the one that was just created). You will be prompted for the file name of the new deconstructed image. The script is useful if you want to break a larger drawing into smaller reusable parts that you want to reference in multiple drawings.
<iframe width="560" height="315" src="https://www.youtube.com/embed/HRtaaD34Zzg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="560" height="315" src="https://www.youtube.com/embed/mvMQcz401yo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.7.29")) {
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.0.25")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
// -------------------------------
// Utility variables and functions
// -------------------------------
const excalidrawTemplates = ea.getListOfTemplateFiles();
if(typeof window.ExcalidrawDeconstructElements === "undefined") {
window.ExcalidrawDeconstructElements = {
openDeconstructedImage: true,
templatePath: excalidrawTemplates?.[0].path??""
};
}
const splitFolderAndFilename = (filepath) => {
const lastIndex = filepath.lastIndexOf("/");
return {
foldername: ea.obsidian.normalizePath(filepath.substring(0, lastIndex)),
filename: (lastIndex == -1 ? filepath : filepath.substring(lastIndex + 1)) + ".md"
};
}
let settings = ea.getScriptSettings();
//set default values on first run
if(!settings["Templates"]) {
settings = {
"Templates" : {
value: "",
description: "Comma-separated list of template filepaths"
}
};
await ea.setScriptSettings(settings);
}
if(!settings["Default file name"]) {
settings["Default file name"] = {
value: "deconstructed",
description: "The default filename to use when deconstructing elements."
};
await ea.setScriptSettings(settings);
}
const DEFAULT_FILENAME = settings["Default file name"].value;
const templates = settings["Templates"]
.value
.split(",")
.map(p=>app.metadataCache.getFirstLinkpathDest(p.trim(),""))
.concat(excalidrawTemplates)
.filter(f=>Boolean(f))
.sort((a,b) => a.basename.localeCompare(b.basename));
// ------------------------------------
// Prepare elements to be deconstructed
// ------------------------------------
const els = ea.getViewSelectedElements();
if (els.length === 0) {
new Notice("You must select elements first")
@@ -20,52 +77,126 @@ const bb = ea.getBoundingBox(els);
ea.copyViewElementsToEAforEditing(els);
ea.getElements().filter(el=>el.type==="image").forEach(el=>{
const img = ea.targetView.excalidrawData.getFile(el.fileId);
const path = (img?.linkParts?.original)??(img?.file?.path);
if(img && path) {
ea.imagesDict[el.fileId] = {
mimeType: img.mimeType,
id: el.fileId,
dataURL: img.img,
created: img.mtime,
file: path,
hasSVGwithBitmap: img.isSVGwithBitmap,
latex: null,
};
return;
const img = ea.targetView.excalidrawData.getFile(el.fileId);
const path = (img?.linkParts?.original)??(img?.file?.path);
if(img && path) {
ea.imagesDict[el.fileId] = {
mimeType: img.mimeType,
id: el.fileId,
dataURL: img.img,
created: img.mtime,
file: path,
hasSVGwithBitmap: img.isSVGwithBitmap,
latex: null,
};
return;
}
const equation = ea.targetView.excalidrawData.getEquation(el.fileId);
eqImg = ea.targetView.getScene()?.files[el.fileId]
if(equation && eqImg) {
ea.imagesDict[el.fileId] = {
mimeType: eqImg.mimeType,
id: el.fileId,
dataURL: eqImg.dataURL,
created: eqImg.created,
file: null,
hasSVGwithBitmap: null,
latex: equation.latex,
};
return;
ea.imagesDict[el.fileId] = {
mimeType: eqImg.mimeType,
id: el.fileId,
dataURL: eqImg.dataURL,
created: eqImg.created,
file: null,
hasSVGwithBitmap: null,
latex: equation.latex,
};
return;
}
});
let folder = ea.targetView.file.path;
folder = folder.lastIndexOf("/")===-1?"":folder.substring(0,folder.lastIndexOf("/"))+"/";
const fname = await utils.inputPrompt("Filename for new file","Filename","");
const template = app.metadataCache.getFirstLinkpathDest(ea.plugin.settings.templateFilePath,"");
// ------------
// Input prompt
// ------------
let shouldAnchor = false;
const actionButtons = [
{
caption: "Insert @100%",
tooltip: "Anchor to 100% size",
action: () => {
shouldAnchor = true;
}
},
{
caption: "Insert",
tooltip: "Insert without anchoring",
action: () => {
shouldAnchor = false;
}
}];
const customControls = (container) => {
new ea.obsidian.Setting(container)
.setName(`Select template`)
.addDropdown(dropdown => {
templates.forEach(file => dropdown.addOption(file.path, file.basename));
if(templates.length === 0) dropdown.addOption(null, "none");
dropdown
.setValue(window.ExcalidrawDeconstructElements.templatePath)
.onChange(value => {
window.ExcalidrawDeconstructElements.templatePath = value;
})
})
new ea.obsidian.Setting(container)
.setName(`Open deconstructed image`)
.addToggle((toggle) => toggle
.setValue(window.ExcalidrawDeconstructElements.openDeconstructedImage)
.onChange(value => {
window.ExcalidrawDeconstructElements.openDeconstructedImage = value;
})
)
}
const path = await utils.inputPrompt(
"Filename for new file",
"Filename",
await ea.getAttachmentFilepath(DEFAULT_FILENAME),
actionButtons,
2,
false,
customControls
);
if(!path) return;
// ----------------------
// Execute deconstruction
// ----------------------
const {foldername, filename} = splitFolderAndFilename(path);
const newPath = await ea.create ({
filename: fname + ".md",
foldername: folder,
templatePath: template?.path,
onNewPane: true
filename,
foldername,
templatePath: window.ExcalidrawDeconstructElements.templatePath,
onNewPane: true,
silent: !window.ExcalidrawDeconstructElements.openDeconstructedImage
});
setTimeout(async ()=>{
const file = app.metadataCache.getFirstLinkpathDest(newPath,"")
ea.deleteViewElements(els);
ea.clear();
await ea.addImage(bb.topX,bb.topY,file,false);
ea.addElementsToView(false, true, true);
},1000);
let f = app.vault.getAbstractFileByPath(newPath);
let counter = 0;
while((!f || !ea.isExcalidrawFile(f)) && counter++<100) {
await sleep(50);
f = app.vault.getAbstractFileByPath(newPath);
}
if(!f || !ea.isExcalidrawFile(f)) {
new Notice("Something went wrong");
return;
}
let padding = parseFloat(app.metadataCache.getCache(f.path)?.frontmatter["excalidraw-export-padding"]);
if(isNaN(padding)) {
padding = ea.plugin.settings.exportPaddingSVG;
}
ea.getElements().forEach(el=>el.isDeleted = true);
await ea.addImage(bb.topX-padding,bb.topY-padding,f,false, shouldAnchor);
await ea.addElementsToView(false, true, true);
ea.getExcalidrawAPI().history.clear();
if(!window.ExcalidrawDeconstructElements.openDeconstructedImage) {
new Notice("Deconstruction ready");
}

View File

@@ -13,12 +13,49 @@ https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.h
```javascript
*/
const selectedCenterConnectPoints = await utils.suggester(
['Yes', 'No'],
[true, false],
"Center connect points?"
);
const centerConnectPoints = selectedCenterConnectPoints??false;
const allElements = ea.getViewElements();
const elements = ea.getViewSelectedElements();
const lines = elements.filter((el)=>el.type==="arrow" || el.type==="line");
for (const line of lines) {
if (line.points.length >= 3) {
if(centerConnectPoints) {
const startBindingEl = allElements.filter(el => el.id === (line.startBinding||{}).elementId)[0];
const endBindingEl = allElements.filter(el => el.id === (line.endBinding||{}).elementId)[0];
if(startBindingEl) {
const startPointX = line.x +line.points[0][0];
if(startPointX >= startBindingEl.x && startPointX <= startBindingEl.x + startBindingEl.width) {
line.points[0][0] = startBindingEl.x + startBindingEl.width / 2 - line.x;
}
const startPointY = line.y +line.points[0][1];
if(startPointY >= startBindingEl.y && startPointY <= startBindingEl.y + startBindingEl.height) {
line.points[0][1] = startBindingEl.y + startBindingEl.height / 2 - line.y;
}
}
if(endBindingEl) {
const startPointX = line.x +line.points[line.points.length-1][0];
if(startPointX >= endBindingEl.x && startPointX <= endBindingEl.x + endBindingEl.width) {
line.points[line.points.length-1][0] = endBindingEl.x + endBindingEl.width / 2 - line.x;
}
const startPointY = line.y +line.points[line.points.length-1][1];
if(startPointY >= endBindingEl.y && startPointY <= endBindingEl.y + endBindingEl.height) {
line.points[line.points.length-1][1] = endBindingEl.y + endBindingEl.height / 2 - line.y;
}
}
}
for (var i = 0; i < line.points.length - 2; i++) {
var p1;
var p3;

View File

@@ -0,0 +1,61 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-ellipse-elements.png)
This script will add an encapsulating ellipse around the currently selected elements in Excalidraw.
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.21")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
settings = ea.getScriptSettings();
//set default values on first run
if(!settings["Default padding"]) {
settings = {
"Prompt for padding?": true,
"Default padding" : {
value: 10,
description: "Padding between the bounding box of the selected elements, and the ellipse the script creates"
}
};
ea.setScriptSettings(settings);
}
let padding = settings["Default padding"].value;
if(settings["Prompt for padding?"]) {
padding = parseInt (await utils.inputPrompt("padding?","number",padding.toString()));
}
if(isNaN(padding)) {
new Notice("The padding value provided is not a number");
return;
}
elements = ea.getViewSelectedElements();
const box = ea.getBoundingBox(elements);
color = ea
.getExcalidrawAPI()
.getAppState()
.currentItemStrokeColor;
//uncomment for random color:
//color = '#'+(Math.random()*0xFFFFFF<<0).toString(16).padStart(6,"0");
ea.style.strokeColor = color;
const ellipseWidth = box.width/Math.sqrt(2);
const ellipseHeight = box.height/Math.sqrt(2);
const topX = box.topX - (ellipseWidth - box.width/2);
const topY = box.topY - (ellipseHeight - box.height/2);
id = ea.addEllipse(
topX - padding,
topY - padding,
2*ellipseWidth + 2*padding,
2*ellipseHeight + 2*padding
);
ea.copyViewElementsToEAforEditing(elements);
ea.addToGroup([id].concat(elements.map((el)=>el.id)));
ea.addElementsToView(false,false);

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

689
ea-scripts/ExcaliAI.md Normal file
View File

@@ -0,0 +1,689 @@
/*
<iframe width="560" height="315" src="https://www.youtube.com/embed/A1vrSGBbWgo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-draw-a-ui.jpg)
```js*/
let dirty=false;
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.0.12")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
const outputTypes = {
"html": {
instruction: "Turn this into a single html file using tailwind. Return a single message containing only the html file in a codeblock.",
blocktype: "html"
},
"mermaid": {
instruction: "Return a single message containing only the mermaid diagram in a codeblock.",
blocktype: "mermaid"
},
"svg": {
instruction: "Return a single message containing only the SVG code in an html codeblock.",
blocktype: "svg"
},
"image-gen": {
instruction: "Return a single message with the generated image prompt in a codeblock",
blocktype: "image"
},
"image-gen-silent": {
instruction: "Return a single message with the generated image prompt in a codeblock",
blocktype: "image-silent"
},
"image-edit": {
instruction: "",
blocktype: "image"
}
}
const systemPrompts = {
"Challenge my thinking": {
prompt: `Your task is to interpret a screenshot of a whiteboard, translating its ideas into a Mermaid graph. The whiteboard will encompass thoughts on a subject. Within the mind map, distinguish ideas that challenge, dispute, or contradict the whiteboard content. Additionally, include concepts that expand, complement, or advance the user's thinking. Utilize the Mermaid graph diagram type and present the resulting Mermaid diagram within a code block. Ensure the Mermaid script excludes the use of parentheses ().`,
type: "mermaid",
help: "Translate your image and optional text prompt into a Mermaid mindmap. If there are conversion errors, edit the Mermaid script under 'More Tools'."
},
"Convert sketch to shapes": {
prompt: `Given an image featuring various geometric shapes drawn by the user, your objective is to analyze the input and generate SVG code that accurately represents these shapes. Your output will be the SVG code enclosed in an HTML code block.`,
type: "svg",
help: "Convert selected scribbles into shapes; works better with fewer shapes. Experimental and may not produce good drawings."
},
"Create a simple Excalidraw icon": {
prompt: `Given a description of an SVG image from the user, your objective is to generate the corresponding SVG code. Avoid incorporating textual elements within the generated SVG. Your output should be the resulting SVG code enclosed in an HTML code block.`,
type: "svg",
help: "Convert text prompts into simple icons inserted as Excalidraw elements. Expect only a text prompt. Experimental and may not produce good drawings."
},
"Create a stick figure": {
prompt: "You will receive a prompt from the user. Your task involves drawing a simple stick figure or a scene involving a few stick figures based on the user's prompt. Create the stickfigure based on the following style description. DO NOT add any detail, just use it AS-IS: Create a simple stick figure character with a large round head and a face in the style of sketchy caricatures. The stick figure should have a rudimentary body composed of straight lines representing the arms and legs. Hands and toes should be should be represented with round shapes, do not add details such as fingers or toes. Use fine lines, smooth curves, rounded shapes. The stick figure should retain a playful and childlike simplicity, reminiscent of a doodle someone might draw on the corner of a notebook page. Create a black and white drawing, a hand-drawn figure on white background.",
type: "image-gen",
help: "Send only the text prompt to OpenAI. Provide a detailed description; OpenAI will enrich your prompt automatically. To avoid it, start your prompt like this 'DO NOT add any detail, just use it AS-IS:'"
},
"Edit an image": {
prompt: null,
type: "image-edit",
help: "Image elements will be used as the Image. Shapes on top of the image will be the Mask. Use the prompt to instruct Dall-e about the changes. Dall-e-2 model will be used."
},
"Generate an image from image and prompt": {
prompt: "Your task involves receiving an image and a textual prompt from the user. Your goal is to craft a detailed, accurate, and descriptive narrative of the image, tailored for effective image generation. Utilize the user-provided text prompt to inform and guide your depiction of the image. Ensure the resulting image remains text-free.",
type: "image-gen",
help: "Generate an image based on the drawing and prompt using ChatGPT-Vision and Dall-e. Provide a contextual text-prompt for accurate interpretation."
},
"Generate an image from prompt": {
prompt: null,
type: "image-gen",
help: "Send only the text prompt to OpenAI. Provide a detailed description; OpenAI will enrich your prompt automatically. To avoid it, start your prompt like this 'DO NOT add any detail, just use it AS-IS:'"
},
"Generate an image to illustrate a quote": {
prompt: "Your task involves transforming a user-provided quote into a detailed and imaginative illustration. Craft a visual representation that captures the essence of the quote and resonates well with a broad audience. If the Author's name is provided, aim to establish a connection between the illustration and the Author. This can be achieved by referencing a well-known story from the Author, situating the image in the Author's era or setting, or employing other creative methods of association. Additionally, provide preferences for styling, such as the chosen medium and artistic direction, to guide the image creation process. Ensure the resulting image remains text-free. Your task output should comprise a descriptive and detailed narrative aimed at facilitating the creation of a captivating illustration from the quote.",
type: "image-gen",
help: "ExcaliAI will create an image prompt to illustrate your text input - a quote - with GPT, then generate an image using Dall-e. In case you include the Author's name, GPT will try to generate an image that in some way references the Author."
},
"Generate 4 icon-variants based on input image": {
prompt: "Given a simple sketch and an optional text prompt from the user, your task is to generate a descriptive narrative tailored for effective image generation, capturing the style of the sketch. Utilize the text prompt to guide the description. Your objective is to instruct DALL-E to create a collage of four minimalist black and white hand-drawn pencil sketches in a 2x2 matrix format. Each sketch should convert the user's sketch into simple artistic SVG icons with transparent backgrounds. Ensure the resulting images remain text-free, maintaining a minimalist, easy-to-understand style, and omit framing borders. Only include a pencil in the drawing if it is explicitely metioned in the user prompt or included in the sketch.",
type: "image-gen-silent",
help: "Generate a collage of 4 icons based on the drawing using ChatGPT-Vision and Dall-e. You may provide a contextual text-prompt to improve accuracy of interpretation."
},
"Visual brainstorm": {
prompt: "Your objective is to interpret a screenshot of a whiteboard, creating an image aimed at sparking further thoughts on the subject. The whiteboard will present diverse ideas about a specific topic. Your generated image should achieve one of two purposes: highlighting concepts that challenge, dispute, or contradict the whiteboard content, or introducing ideas that expand, complement, or enrich the user's thinking. You have the option to include multiple tiles in the resulting image, resembling a sequence akin to a comic strip. Ensure that the image remains devoid of text.",
type: "image-gen",
help: "Use ChatGPT Visions and Dall-e to create an image based on your text prompt and image to spark new ideas."
},
"Wireframe to code": {
prompt: `You are an expert tailwind developer. A user will provide you with a low-fidelity wireframe of an application and you will return a single html file that uses tailwind to create the website. Use creative license to make the application more fleshed out. Write the necessary javascript code. If you need to insert an image, use placehold.co to create a placeholder image.`,
type: "html",
help: "Use GPT Visions to interpret the wireframe and generate a web application. YOu may copy the resulting code from the active embeddable's top left menu."
},
}
const IMAGE_WARNING = "The generated image is linked through a temporary OpenAI URL and will be removed in approximately 30 minutes. To save it permanently, choose 'Save image from URL to local file' from the Obsidian Command Palette."
// --------------------------------------
// Initialize values and settings
// --------------------------------------
let settings = ea.getScriptSettings();
if(!settings["Agent's Task"]) {
settings = {
"Agent's Task": "Wireframe to code",
"User Prompt": "",
};
await ea.setScriptSettings(settings);
}
const OPENAI_API_KEY = ea.plugin.settings.openAIAPIToken;
if(!OPENAI_API_KEY || OPENAI_API_KEY === "") {
new Notice("You must first configure your API key in Excalidraw Plugin Settings");
return;
}
let userPrompt = settings["User Prompt"] ?? "";
let agentTask = settings["Agent's Task"];
let imageSize = settings["Image Size"]??"1024x1024";
if(!systemPrompts.hasOwnProperty(agentTask)) {
agentTask = Object.keys(systemPrompts)[0];
}
let imageModel, valideSizes;
const setImageModelAndSizes = () => {
imageModel = systemPrompts[agentTask].type === "image-edit"
? "dall-e-2"
: ea.plugin.settings.openAIDefaultImageGenerationModel;
validSizes = imageModel === "dall-e-2"
? [`256x256`, `512x512`, `1024x1024`]
: (imageModel === "dall-e-3"
? [`1024x1024`, `1792x1024`, `1024x1792`]
: [`1024x1024`])
if(!validSizes.includes(imageSize)) {
imageSize = "1024x1024";
dirty = true;
}
}
setImageModelAndSizes();
// --------------------------------------
// Generate Image Blob From Selected Excalidraw Elements
// --------------------------------------
const calculateImageScale = (elements) => {
const bb = ea.getBoundingBox(elements);
const size = (bb.width*bb.height);
const minRatio = Math.sqrt(360000/size);
const maxRatio = Math.sqrt(size/16000000);
return minRatio > 1
? minRatio
: (
maxRatio > 1
? 1/maxRatio
: 1
);
}
const createMask = async (dataURL) => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
// If opaque (alpha > 0), make it transparent
if (data[i + 3] > 0) {
data[i + 3] = 0; // Set alpha to 0 (transparent)
} else if (data[i + 3] === 0) {
// If fully transparent, make it red
data[i] = 255; // Red
data[i + 1] = 0; // Green
data[i + 2] = 0; // Blue
data[i + 3] = 255; // make it opaque
}
}
ctx.putImageData(imageData, 0, 0);
const maskDataURL = canvas.toDataURL();
resolve(maskDataURL);
};
img.onerror = error => {
reject(error);
};
img.src = dataURL;
});
}
//https://platform.openai.com/docs/api-reference/images/createEdit
//dall-e-2 image edit only works on square images
//if targetDalleImageEdit === true then the image and the mask will be returned in two separate dataURLs
let squareBB;
const generateCanvasDataURL = async (view, targetDalleImageEdit=false) => {
let PADDING = 5;
await view.forceSave(true); //to ensure recently embedded PNG and other images are saved to file
const viewElements = ea.getViewSelectedElements();
if(viewElements.length === 0) {
return {imageDataURL: null, maskDataURL: null} ;
}
ea.copyViewElementsToEAforEditing(viewElements, true); //copying the images objects over to EA for PNG generation
let maskDataURL;
const loader = ea.getEmbeddedFilesLoader(false);
let scale = calculateImageScale(ea.getElements());
const bb = ea.getBoundingBox(viewElements);
if(ea.getElements()
.filter(el=>el.type==="image")
.some(el=>Math.round(el.width) === Math.round(bb.width) && Math.round(el.height) === Math.round(bb.height))
) { PADDING = 0; }
let exportSettings = {withBackground: true, withTheme: true};
if(targetDalleImageEdit) {
PADDING = 0;
const strokeColor = ea.style.strokeColor;
const backgroundColor = ea.style.backgroundColor;
ea.style.backgroundColor = "transparent";
ea.style.strokeColor = "transparent";
let rectID;
if(bb.height > bb.width) {
rectID = ea.addRect(bb.topX-(bb.height-bb.width)/2, bb.topY,bb.height, bb.height);
}
if(bb.width > bb.height) {
rectID = ea.addRect(bb.topX, bb.topY-(bb.width-bb.height)/2,bb.width, bb.width);
}
if(bb.height === bb.width) {
rectID = ea.addRect(bb.topX, bb.topY, bb.width, bb.height);
}
const rect = ea.getElement(rectID);
squareBB = {topX: rect.x-PADDING, topY: rect.y-PADDING, width: rect.width + 2*PADDING, height: rect.height + 2*PADDING};
ea.style.strokeColor = strokeColor;
ea.style.backgroundColor = backgroundColor;
ea.getElements().filter(el=>el.type === "image").forEach(el=>{el.isDeleted = true});
dalleWidth = parseInt(imageSize.split("x")[0]);
scale = dalleWidth/squareBB.width;
exportSettings = {withBackground: false, withTheme: true};
maskDataURL= await ea.createPNGBase64(
null, scale, exportSettings, loader, "light", PADDING
);
maskDataURL = await createMask(maskDataURL)
ea.getElements().filter(el=>el.type === "image").forEach(el=>{el.isDeleted = false});
ea.getElements().filter(el=>el.type !== "image" && el.id !== rectID).forEach(el=>{el.isDeleted = true});
}
const imageDataURL = await ea.createPNGBase64(
null, scale, exportSettings, loader, "light", PADDING
);
ea.clear();
return {imageDataURL, maskDataURL};
}
let {imageDataURL, maskDataURL} = await generateCanvasDataURL(ea.targetView, systemPrompts[agentTask].type === "image-edit");
// --------------------------------------
// Support functions - embeddable spinner and error
// --------------------------------------
const spinner = await ea.convertStringToDataURL(`
<html><head><style>
html, body {width: 100%; height: 100%; color: ${ea.getExcalidrawAPI().getAppState().theme === "dark" ? "white" : "black"};}
body {display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 1rem; overflow: hidden;}
.Spinner {display: flex; align-items: center; justify-content: center; margin-left: auto; margin-right: auto;}
.Spinner svg {animation: rotate 1.6s linear infinite; transform-origin: center center; width: 40px; height: 40px;}
.Spinner circle {stroke: currentColor; animation: dash 1.6s linear 0s infinite; stroke-linecap: round;}
@keyframes rotate {100% {transform: rotate(360deg);}}
@keyframes dash {
0% {stroke-dasharray: 1, 300; stroke-dashoffset: 0;}
50% {stroke-dasharray: 150, 300; stroke-dashoffset: -200;}
100% {stroke-dasharray: 1, 300; stroke-dashoffset: -280;}
}
</style></head><body>
<div class="Spinner">
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="46" stroke-width="8" fill="none" stroke-miter-limit="10"/>
</svg>
</div>
<div>Generating...</div>
</body></html>`);
const errorMessage = async (spinnerID, message) => {
const error = "Something went wrong! Check developer console for more.";
const details = message ? `<p>${message}</p>` : "";
const errorDataURL = await ea.convertStringToDataURL(`
<html><head><style>
html, body {height: 100%;}
body {display: flex; flex-direction: column; align-items: center; justify-content: center; color: red;}
h1, h3 {margin-top: 0;margin-bottom: 0.5rem;}
</style></head><body>
<h1>Error!</h1>
<h3>${error}</h3>${details}
</body></html>`);
new Notice (error);
ea.getElement(spinnerID).link = errorDataURL;
ea.addElementsToView(false,true);
}
// --------------------------------------
// Utility to write Mermaid to dialog
// --------------------------------------
const EDITOR_LS_KEYS = {
OAI_API_KEY: "excalidraw-oai-api-key",
MERMAID_TO_EXCALIDRAW: "mermaid-to-excalidraw",
PUBLISH_LIBRARY: "publish-library-data",
};
const setMermaidDataToStorage = (mermaidDefinition) => {
try {
window.localStorage.setItem(
EDITOR_LS_KEYS.MERMAID_TO_EXCALIDRAW,
JSON.stringify(mermaidDefinition)
);
return true;
} catch (error) {
console.warn(`localStorage.setItem error: ${error.message}`);
return false;
}
};
// --------------------------------------
// Submit Prompt
// --------------------------------------
const generateImage = async(text, spinnerID, bb, silent=false) => {
const requestObject = {
text,
imageGenerationProperties: {
size: imageSize,
//quality: "standard", //not supported by dall-e-2
n:1,
},
};
const result = await ea.postOpenAI(requestObject);
console.log({result, json:result?.json});
if(!result?.json?.data?.[0]?.url) {
await errorMessage(spinnerID, result?.json?.error?.message);
return;
}
const spinner = ea.getElement(spinnerID)
spinner.isDeleted = true;
const imageID = await ea.addImage(spinner.x, spinner.y, result.json.data[0].url);
const imageEl = ea.getElement(imageID);
const revisedPrompt = result.json.data[0].revised_prompt;
if(revisedPrompt && !silent) {
ea.style.fontSize = 16;
const rectID = ea.addText(imageEl.x+15, imageEl.y + imageEl.height + 50, revisedPrompt, {
width: imageEl.width-30,
textAlign: "center",
textVerticalAlign: "top",
box: true,
})
ea.getElement(rectID).strokeColor = "transparent";
ea.getElement(rectID).backgroundColor = "transparent";
ea.addToGroup(ea.getElements().filter(el=>el.id !== spinnerID).map(el=>el.id));
}
await ea.addElementsToView(false, true, true);
if(silent) return;
ea.getExcalidrawAPI().setToast({
message: IMAGE_WARNING,
duration: 15000,
closable: true
});
}
const run = async (text) => {
if(!text && !imageDataURL) {
new Notice("No prompt, aborting");
return;
}
const systemPrompt = systemPrompts[agentTask];
const outputType = outputTypes[systemPrompt.type];
const isImageGenRequest = outputType.blocktype === "image" || outputType.blocktype === "image-silent";
const isImageEditRequest = systemPrompt.type === "image-edit";
if(isImageEditRequest) {
if(!text) {
new Notice("You must provide a text prompt with instructions for how the image should be modified");
return;
}
if(!imageDataURL || !maskDataURL) {
new Notice("You must provide an image and a mask");
return;
}
}
//place spinner next to selected elements
const bb = ea.getBoundingBox(ea.getViewSelectedElements());
const spinnerID = ea.addEmbeddable(bb.topX+bb.width+100,bb.topY-(720-bb.height)/2,550,720,spinner);
//this block is in an async call using the isEACompleted flag because otherwise during debug Obsidian
//goes black (not freezes, but does not get a new frame for some reason)
//palcing this in an async call solves this issue
//If you know why this is happening and can offer a better solution, please reach out to @zsviczian
let isEACompleted = false;
setTimeout(async()=>{
await ea.addElementsToView(false,true);
ea.clear();
const embeddable = ea.getViewElements().filter(el=>el.id===spinnerID);
ea.copyViewElementsToEAforEditing(embeddable);
const els = ea.getViewSelectedElements();
ea.viewZoomToElements(false, els.concat(embeddable));
isEACompleted = true;
});
if(isImageGenRequest && !systemPrompt.prompt && !isImageEditRequest) {
generateImage(text,spinnerID,bb);
return;
}
const requestObject = isImageEditRequest
? {
...imageDataURL ? {image: imageDataURL} : {},
...(text && text.trim() !== "") ? {text} : {},
imageGenerationProperties: {
size: imageSize,
//quality: "standard", //not supported by dall-e-2
n:1,
mask: maskDataURL,
},
}
: {
...imageDataURL ? {image: imageDataURL} : {},
...(text && text.trim() !== "") ? {text} : {},
systemPrompt: systemPrompt.prompt,
instruction: outputType.instruction,
}
//Get result from GPT
const result = await ea.postOpenAI(requestObject);
console.log({result, json:result?.json});
//checking that EA has completed. Because the postOpenAI call is an async await
//I don't expect EA not to be completed by now. However the devil never sleeps.
//This (the insomnia of the Devil) is why I have a watchdog here as well
let counter = 0
while(!isEACompleted && counter++<10) sleep(50);
if(!isEACompleted) {
await errorMessage(spinnerID, "Unexpected issue with ExcalidrawAutomate");
return;
}
if(isImageEditRequest) {
if(!result?.json?.data?.[0]?.url) {
await errorMessage(spinnerID, result?.json?.error?.message);
return;
}
const spinner = ea.getElement(spinnerID)
spinner.isDeleted = true;
const imageID = await ea.addImage(spinner.x, spinner.y, result.json.data[0].url);
await ea.addElementsToView(false, true, true);
ea.getExcalidrawAPI().setToast({
message: IMAGE_WARNING,
duration: 15000,
closable: true
});
return;
}
if(!result?.json?.hasOwnProperty("choices")) {
await errorMessage(spinnerID, result?.json?.error?.message);
return;
}
//exctract codeblock and display result
let content = ea.extractCodeBlocks(result.json.choices[0]?.message?.content)[0]?.data;
if(!content) {
await errorMessage(spinnerID);
return;
}
if(isImageGenRequest) {
generateImage(content,spinnerID,bb,outputType.blocktype === "image-silent");
return;
}
switch(outputType.blocktype) {
case "html":
ea.getElement(spinnerID).link = await ea.convertStringToDataURL(content);
ea.addElementsToView(false,true);
break;
case "svg":
ea.getElement(spinnerID).isDeleted = true;
ea.importSVG(content);
ea.addToGroup(ea.getElements().map(el=>el.id));
if(ea.getViewSelectedElements().length>0) {
ea.targetView.currentPosition = {x: bb.topX+bb.width+100, y: bb.topY};
}
ea.addElementsToView(true, false);
break;
case "mermaid":
if(content.startsWith("mermaid")) {
content = content.replace(/^mermaid/,"").trim();
}
try {
result = await ea.addMermaid(content);
if(typeof result === "string") {
await errorMessage(spinnerID, "Open [More Tools / Mermaid to Excalidraw] to manually fix the received mermaid script<br><br>" + result);
return;
}
} catch (e) {
ea.addText(0,0,content);
}
ea.getElement(spinnerID).isDeleted = true;
ea.targetView.currentPosition = {x: bb.topX+bb.width+100, y: bb.topY-bb.height};
await ea.addElementsToView(true, false);
setMermaidDataToStorage(content);
new Notice("Open More Tools/Mermaid to Excalidraw in the top tools menu to edit the generated diagram",8000);
break;
}
}
// --------------------------------------
// User Interface
// --------------------------------------
let previewDiv;
const fragWithHTML = (html) => createFragment((frag) => (frag.createDiv().innerHTML = html));
const isImageGenerationTask = () => systemPrompts[agentTask].type === "image-gen" || systemPrompts[agentTask].type === "image-gen-silent" || systemPrompts[agentTask].type === "image-edit";
const addPreviewImage = () => {
if(!previewDiv) return;
previewDiv.empty();
previewDiv.createEl("img",{
attr: {
style: `max-width: 100%;max-height: 30vh;`,
src: imageDataURL,
}
});
if(maskDataURL) {
previewDiv.createEl("img",{
attr: {
style: `max-width: 100%;max-height: 30vh;`,
src: maskDataURL,
}
});
}
}
const configModal = new ea.obsidian.Modal(app);
configModal.modalEl.style.width="100%";
configModal.modalEl.style.maxWidth="1000px";
configModal.onOpen = async () => {
const contentEl = configModal.contentEl;
contentEl.createEl("h1", {text: "ExcaliAI"});
let systemPromptTextArea, systemPromptDiv, imageSizeSetting, imageSizeSettingDropdown, helpEl;
new ea.obsidian.Setting(contentEl)
.setName("What would you like to do?")
.addDropdown(dropdown=>{
Object.keys(systemPrompts).forEach(key=>dropdown.addOption(key,key));
dropdown
.setValue(agentTask)
.onChange(async (value) => {
dirty = true;
const prevTask = agentTask;
agentTask = value;
if(
(systemPrompts[prevTask].type === "image-edit" && systemPrompts[value].type !== "image-edit") ||
(systemPrompts[prevTask].type !== "image-edit" && systemPrompts[value].type === "image-edit")
) {
({imageDataURL, maskDataURL} = await generateCanvasDataURL(ea.targetView, systemPrompts[value].type === "image-edit"));
addPreviewImage();
setImageModelAndSizes();
while (imageSizeSettingDropdown.selectEl.options.length > 0) { imageSizeSettingDropdown.selectEl.remove(0); }
validSizes.forEach(size=>imageSizeSettingDropdown.addOption(size,size));
imageSizeSettingDropdown.setValue(imageSize);
}
imageSizeSetting.settingEl.style.display = isImageGenerationTask() ? "" : "none";
const prompt = systemPrompts[value].prompt;
helpEl.innerHTML = `<b>Help: </b>` + systemPrompts[value].help;
if(prompt) {
systemPromptDiv.style.display = "";
systemPromptTextArea.setValue(systemPrompts[value].prompt);
} else {
systemPromptDiv.style.display = "none";
}
});
})
helpEl = contentEl.createEl("p");
helpEl.innerHTML = `<b>Help: </b>` + systemPrompts[agentTask].help;
systemPromptDiv = contentEl.createDiv();
systemPromptDiv.createEl("h4", {text: "Customize System Prompt"});
systemPromptDiv.createEl("span", {text: "Unless you know what you are doing I do not recommend changing the system prompt"})
const systemPromptSetting = new ea.obsidian.Setting(systemPromptDiv)
.addTextArea(text => {
systemPromptTextArea = text;
const prompt = systemPrompts[agentTask].prompt;
text.inputEl.style.minHeight = "10em";
text.inputEl.style.width = "100%";
text.setValue(prompt);
text.onChange(value => {
systemPrompts[value].prompt = value;
});
if(!prompt) systemPromptDiv.style.display = "none";
})
systemPromptSetting.nameEl.style.display = "none";
systemPromptSetting.descEl.style.display = "none";
systemPromptSetting.infoEl.style.display = "none";
contentEl.createEl("h4", {text: "User Prompt"});
const userPromptSetting = new ea.obsidian.Setting(contentEl)
.addTextArea(text => {
text.inputEl.style.minHeight = "10em";
text.inputEl.style.width = "100%";
text.setValue(userPrompt);
text.onChange(value => {
userPrompt = value;
dirty = true;
})
})
userPromptSetting.nameEl.style.display = "none";
userPromptSetting.descEl.style.display = "none";
userPromptSetting.infoEl.style.display = "none";
imageSizeSetting = new ea.obsidian.Setting(contentEl)
.setName("Select image size")
.setDesc(fragWithHTML("<mark>⚠️ Important ⚠️</mark>: " + IMAGE_WARNING))
.addDropdown(dropdown=>{
validSizes.forEach(size=>dropdown.addOption(size,size));
imageSizeSettingDropdown = dropdown;
dropdown
.setValue(imageSize)
.onChange(async (value) => {
dirty = true;
imageSize = value;
if(systemPrompts[agentTask].type === "image-edit") {
({imageDataURL, maskDataURL} = await generateCanvasDataURL(ea.targetView, true));
addPreviewImage();
}
});
})
imageSizeSetting.settingEl.style.display = isImageGenerationTask() ? "" : "none";
if(imageDataURL) {
previewDiv = contentEl.createDiv({
attr: {
style: "text-align: center;",
}
});
addPreviewImage();
} else {
contentEl.createEl("h4", {text: "No elements are selected from your canvas"});
contentEl.createEl("span", {text: "Because there are no Excalidraw elements selected on the canvas, only the text prompt will be sent to OpenAI."});
}
new ea.obsidian.Setting(contentEl)
.addButton(button =>
button
.setButtonText("Run")
.onClick((event)=>{
run(userPrompt); //Obsidian crashes otherwise, likely has to do with requesting an new frame for react
configModal.close();
})
);
}
configModal.onClose = () => {
if(dirty) {
settings["User Prompt"] = userPrompt;
settings["Agent's Task"] = agentTask;
settings["Image Size"] = imageSize;
ea.setScriptSettings(settings);
}
}
configModal.open();

1
ea-scripts/ExcaliAI.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M320 0c17.7 0 32 14.3 32 32V96H472c39.8 0 72 32.2 72 72V440c0 39.8-32.2 72-72 72H168c-39.8 0-72-32.2-72-72V168c0-39.8 32.2-72 72-72H288V32c0-17.7 14.3-32 32-32zM208 384c-8.8 0-16 7.2-16 16s7.2 16 16 16h32c8.8 0 16-7.2 16-16s-7.2-16-16-16H208zm96 0c-8.8 0-16 7.2-16 16s7.2 16 16 16h32c8.8 0 16-7.2 16-16s-7.2-16-16-16H304zm96 0c-8.8 0-16 7.2-16 16s7.2 16 16 16h32c8.8 0 16-7.2 16-16s-7.2-16-16-16H400zM264 256a40 40 0 1 0 -80 0 40 40 0 1 0 80 0zm152 40a40 40 0 1 0 0-80 40 40 0 1 0 0 80zM48 224H64V416H48c-26.5 0-48-21.5-48-48V272c0-26.5 21.5-48 48-48zm544 0c26.5 0 48 21.5 48 48v96c0 26.5-21.5 48-48 48H576V224h16z"/></svg>

After

Width:  |  Height:  |  Size: 694 B

View File

@@ -0,0 +1,12 @@
/*
Creates a new Excalidraw.com collaboration room and places the link to the room on the clipboard.
```js*/
const room = Array.from(window.crypto.getRandomValues(new Uint8Array(10))).map((byte) => `0${byte.toString(16)}`.slice(-2)).join("");
const key = (await window.crypto.subtle.exportKey("jwk",await window.crypto.subtle.generateKey({name:"AES-GCM",length:128},true,["encrypt", "decrypt"]))).k;
const link = `https://excalidraw.com/#room=${room},${key}`;
ea.addIFrame(0,0,800,600,link);
ea.addElementsToView(true,true);
window.navigator.clipboard.writeText(link);
new Notice("The collaboration room link is available on the clipboard.",4000);

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke-width="2" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><g stroke-width="1.5"></path><circle cx="9" cy="7" r="4"></circle><path d="M3 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path><path d="M21 21v-2a4 4 0 0 0 -3 -3.85"></path></g></svg>

After

Width:  |  Height:  |  Size: 382 B

View File

@@ -0,0 +1,9 @@
/*
This script adds the `Folder Note Core: Make current document folder note` function to Excalidraw drawings. Running this script will convert the active Excalidraw drawing into a folder note. If you already have embedded images in your drawing, those attachments will not be moved when the folder note is created. You need to take care of those attachments separately, or convert the drawing to a folder note prior to adding the attachments. The script requires the [Folder Note Core](https://github.com/aidenlx/folder-note-core) plugin.
```javascript*/
const FNC = app.plugins.plugins['folder-note-core']?.resolver;
const file = ea.targetView.file;
if(!FNC) return;
if(!FNC.createFolderForNoteCheck(file)) return;
FNC.createFolderForNote(file);

View File

@@ -0,0 +1,12 @@
<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">
<path fill="none" stroke-width="2" d="M10.5 20H4a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2h3.93a2 2 0 0 1 1.66.9l.82 1.2a2 2 0 0 0 1.66.9H20a2 2 0 0 1 2 2v3"></path>
<circle fill="none" stroke-width="2" cx="18" cy="18" r="3"></circle>
<path fill="none" stroke-width="2" d="M18 14v1"></path>
<path fill="none" stroke-width="2" d="M18 21v1"></path>
<path fill="none" stroke-width="2" d="M22 18h-1"></path>
<path fill="none" stroke-width="2" d="M15 18h-1"></path>
<path fill="none" stroke-width="2" d="m21 15-.88.88"></path>
<path fill="none" stroke-width="2" d="M15.88 20.12 15 21"></path>
<path fill="none" stroke-width="2" d="m21 21-.88-.88"></path>
<path fill="none" stroke-width="2" d="M15.88 15.88 15 15"></path>
</svg>

After

Width:  |  Height:  |  Size: 912 B

673
ea-scripts/GPT-Draw-a-UI.md Normal file
View File

@@ -0,0 +1,673 @@
/*
<iframe width="560" height="315" src="https://www.youtube.com/embed/A1vrSGBbWgo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-draw-a-ui.jpg)
```js*/
let dirty=false;
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.0.12")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
const outputTypes = {
"html": {
instruction: "Turn this into a single html file using tailwind. Return a single message containing only the html file in a codeblock.",
blocktype: "html"
},
"mermaid": {
instruction: "Return a single message containing only the mermaid diagram in a codeblock.",
blocktype: "mermaid"
},
"svg": {
instruction: "Return a single message containing only the SVG code in an html codeblock.",
blocktype: "svg"
},
"image-gen": {
instruction: "Return a single message with the generated image prompt in a codeblock",
blocktype: "image"
},
"image-edit": {
instruction: "",
blocktype: "image"
}
}
const systemPrompts = {
"Challenge my thinking": {
prompt: `Your task is to interpret a screenshot of a whiteboard, translating its ideas into a Mermaid graph. The whiteboard will encompass thoughts on a subject. Within the mind map, distinguish ideas that challenge, dispute, or contradict the whiteboard content. Additionally, include concepts that expand, complement, or advance the user's thinking. Utilize the Mermaid graph diagram type and present the resulting Mermaid diagram within a code block. Ensure the Mermaid script excludes the use of parentheses ().`,
type: "mermaid",
help: "Translate your image and optional text prompt into a Mermaid mindmap. If there are conversion errors, edit the Mermaid script under 'More Tools'."
},
"Convert sketch to shapes": {
prompt: `Given an image featuring various geometric shapes drawn by the user, your objective is to analyze the input and generate SVG code that accurately represents these shapes. Your output will be the SVG code enclosed in an HTML code block.`,
type: "svg",
help: "Convert selected scribbles into shapes; works better with fewer shapes. Experimental and may not produce good drawings."
},
"Create a simple Excalidraw icon": {
prompt: `Given a description of an SVG image from the user, your objective is to generate the corresponding SVG code. Avoid incorporating textual elements within the generated SVG. Your output should be the resulting SVG code enclosed in an HTML code block.`,
type: "svg",
help: "Convert text prompts into simple icons inserted as Excalidraw elements. Expect only a text prompt. Experimental and may not produce good drawings."
},
"Edit an image": {
prompt: null,
type: "image-edit",
help: "Image elements will be used as the Image. Shapes on top of the image will be the Mask. Use the prompt to instruct Dall-e about the changes. Dall-e-2 model will be used."
},
"Generate an image from image and prompt": {
prompt: "Your task involves receiving an image and a textual prompt from the user. Your goal is to craft a detailed, accurate, and descriptive narrative of the image, tailored for effective image generation. Utilize the user-provided text prompt to inform and guide your depiction of the image. Ensure the resulting image remains text-free.",
type: "image-gen",
help: "Generate an image based on the drawing and prompt using ChatGPT-Vision and Dall-e. Provide a contextual text-prompt for accurate interpretation."
},
"Generate an image from prompt": {
prompt: null,
type: "image-gen",
help: "Send only the text prompt to OpenAI. Provide a detailed description; OpenAI will enrich your prompt automatically. To avoid it, start your prompt like this 'DO NOT add any detail, just use it AS-IS:'"
},
"Generate an image to illustrate a quote": {
prompt: "Your task involves transforming a user-provided quote into a detailed and imaginative illustration. Craft a visual representation that captures the essence of the quote and resonates well with a broad audience. If the Author's name is provided, aim to establish a connection between the illustration and the Author. This can be achieved by referencing a well-known story from the Author, situating the image in the Author's era or setting, or employing other creative methods of association. Additionally, provide preferences for styling, such as the chosen medium and artistic direction, to guide the image creation process. Ensure the resulting image remains text-free. Your task output should comprise a descriptive and detailed narrative aimed at facilitating the creation of a captivating illustration from the quote.",
type: "image-gen",
help: "ExcaliAI will create an image prompt to illustrate your text input - a quote - with GPT, then generate an image using Dall-e. In case you include the Author's name, GPT will try to generate an image that in some way references the Author."
},
"Visual brainstorm": {
prompt: "Your objective is to interpret a screenshot of a whiteboard, creating an image aimed at sparking further thoughts on the subject. The whiteboard will present diverse ideas about a specific topic. Your generated image should achieve one of two purposes: highlighting concepts that challenge, dispute, or contradict the whiteboard content, or introducing ideas that expand, complement, or enrich the user's thinking. You have the option to include multiple tiles in the resulting image, resembling a sequence akin to a comic strip. Ensure that the image remains devoid of text.",
type: "image-gen",
help: "Use ChatGPT Visions and Dall-e to create an image based on your text prompt and image to spark new ideas."
},
"Wireframe to code": {
prompt: `You are an expert tailwind developer. A user will provide you with a low-fidelity wireframe of an application and you will return a single html file that uses tailwind to create the website. Use creative license to make the application more fleshed out. Write the necessary javascript code. If you need to insert an image, use placehold.co to create a placeholder image.`,
type: "html",
help: "Use GPT Visions to interpret the wireframe and generate a web application. You may copy the resulting code from the active embeddable's top left menu."
},
}
const IMAGE_WARNING = "The generated image is linked through a temporary OpenAI URL and will be removed in approximately 30 minutes. To save it permanently, choose 'Save image from URL to local file' from the Obsidian Command Palette."
// --------------------------------------
// Initialize values and settings
// --------------------------------------
let settings = ea.getScriptSettings();
if(!settings["Agent's Task"]) {
settings = {
"Agent's Task": "Wireframe to code",
"User Prompt": "",
};
await ea.setScriptSettings(settings);
}
const OPENAI_API_KEY = ea.plugin.settings.openAIAPIToken;
if(!OPENAI_API_KEY || OPENAI_API_KEY === "") {
new Notice("You must first configure your API key in Excalidraw Plugin Settings");
return;
}
let userPrompt = settings["User Prompt"] ?? "";
let agentTask = settings["Agent's Task"];
let imageSize = settings["Image Size"]??"1024x1024";
if(!systemPrompts.hasOwnProperty(agentTask)) {
agentTask = Object.keys(systemPrompts)[0];
}
let imageModel, valideSizes;
const setImageModelAndSizes = () => {
imageModel = systemPrompts[agentTask].type === "image-edit"
? "dall-e-2"
: ea.plugin.settings.openAIDefaultImageGenerationModel;
validSizes = imageModel === "dall-e-2"
? [`256x256`, `512x512`, `1024x1024`]
: (imageModel === "dall-e-3"
? [`1024x1024`, `1792x1024`, `1024x1792`]
: [`1024x1024`])
if(!validSizes.includes(imageSize)) {
imageSize = "1024x1024";
dirty = true;
}
}
setImageModelAndSizes();
// --------------------------------------
// Generate Image Blob From Selected Excalidraw Elements
// --------------------------------------
const calculateImageScale = (elements) => {
const bb = ea.getBoundingBox(elements);
const size = (bb.width*bb.height);
const minRatio = Math.sqrt(360000/size);
const maxRatio = Math.sqrt(size/16000000);
return minRatio > 1
? minRatio
: (
maxRatio > 1
? 1/maxRatio
: 1
);
}
const createMask = async (dataURL) => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
// If opaque (alpha > 0), make it transparent
if (data[i + 3] > 0) {
data[i + 3] = 0; // Set alpha to 0 (transparent)
} else if (data[i + 3] === 0) {
// If fully transparent, make it red
data[i] = 255; // Red
data[i + 1] = 0; // Green
data[i + 2] = 0; // Blue
data[i + 3] = 255; // make it opaque
}
}
ctx.putImageData(imageData, 0, 0);
const maskDataURL = canvas.toDataURL();
resolve(maskDataURL);
};
img.onerror = error => {
reject(error);
};
img.src = dataURL;
});
}
//https://platform.openai.com/docs/api-reference/images/createEdit
//dall-e-2 image edit only works on square images
//if targetDalleImageEdit === true then the image and the mask will be returned in two separate dataURLs
let squareBB;
const generateCanvasDataURL = async (view, targetDalleImageEdit=false) => {
let PADDING = 5;
await view.forceSave(true); //to ensure recently embedded PNG and other images are saved to file
const viewElements = ea.getViewSelectedElements();
if(viewElements.length === 0) {
return {imageDataURL: null, maskDataURL: null} ;
}
ea.copyViewElementsToEAforEditing(viewElements, true); //copying the images objects over to EA for PNG generation
let maskDataURL;
const loader = ea.getEmbeddedFilesLoader(false);
let scale = calculateImageScale(ea.getElements());
const bb = ea.getBoundingBox(viewElements);
if(ea.getElements()
.filter(el=>el.type==="image")
.some(el=>Math.round(el.width) === Math.round(bb.width) && Math.round(el.height) === Math.round(bb.height))
) { PADDING = 0; }
let exportSettings = {withBackground: true, withTheme: true};
if(targetDalleImageEdit) {
PADDING = 0;
const strokeColor = ea.style.strokeColor;
const backgroundColor = ea.style.backgroundColor;
ea.style.backgroundColor = "transparent";
ea.style.strokeColor = "transparent";
let rectID;
if(bb.height > bb.width) {
rectID = ea.addRect(bb.topX-(bb.height-bb.width)/2, bb.topY,bb.height, bb.height);
}
if(bb.width > bb.height) {
rectID = ea.addRect(bb.topX, bb.topY-(bb.width-bb.height)/2,bb.width, bb.width);
}
if(bb.height === bb.width) {
rectID = ea.addRect(bb.topX, bb.topY, bb.width, bb.height);
}
const rect = ea.getElement(rectID);
squareBB = {topX: rect.x-PADDING, topY: rect.y-PADDING, width: rect.width + 2*PADDING, height: rect.height + 2*PADDING};
ea.style.strokeColor = strokeColor;
ea.style.backgroundColor = backgroundColor;
ea.getElements().filter(el=>el.type === "image").forEach(el=>{el.isDeleted = true});
dalleWidth = parseInt(imageSize.split("x")[0]);
scale = dalleWidth/squareBB.width;
exportSettings = {withBackground: false, withTheme: true};
maskDataURL= await ea.createPNGBase64(
null, scale, exportSettings, loader, "light", PADDING
);
maskDataURL = await createMask(maskDataURL)
ea.getElements().filter(el=>el.type === "image").forEach(el=>{el.isDeleted = false});
ea.getElements().filter(el=>el.type !== "image" && el.id !== rectID).forEach(el=>{el.isDeleted = true});
}
const imageDataURL = await ea.createPNGBase64(
null, scale, exportSettings, loader, "light", PADDING
);
ea.clear();
return {imageDataURL, maskDataURL};
}
let {imageDataURL, maskDataURL} = await generateCanvasDataURL(ea.targetView, systemPrompts[agentTask].type === "image-edit");
// --------------------------------------
// Support functions - embeddable spinner and error
// --------------------------------------
const spinner = await ea.convertStringToDataURL(`
<html><head><style>
html, body {width: 100%; height: 100%; color: ${ea.getExcalidrawAPI().getAppState().theme === "dark" ? "white" : "black"};}
body {display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 1rem; overflow: hidden;}
.Spinner {display: flex; align-items: center; justify-content: center; margin-left: auto; margin-right: auto;}
.Spinner svg {animation: rotate 1.6s linear infinite; transform-origin: center center; width: 40px; height: 40px;}
.Spinner circle {stroke: currentColor; animation: dash 1.6s linear 0s infinite; stroke-linecap: round;}
@keyframes rotate {100% {transform: rotate(360deg);}}
@keyframes dash {
0% {stroke-dasharray: 1, 300; stroke-dashoffset: 0;}
50% {stroke-dasharray: 150, 300; stroke-dashoffset: -200;}
100% {stroke-dasharray: 1, 300; stroke-dashoffset: -280;}
}
</style></head><body>
<div class="Spinner">
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="46" stroke-width="8" fill="none" stroke-miter-limit="10"/>
</svg>
</div>
<div>Generating...</div>
</body></html>`);
const errorMessage = async (spinnerID, message) => {
const error = "Something went wrong! Check developer console for more.";
const details = message ? `<p>${message}</p>` : "";
const errorDataURL = await ea.convertStringToDataURL(`
<html><head><style>
html, body {height: 100%;}
body {display: flex; flex-direction: column; align-items: center; justify-content: center; color: red;}
h1, h3 {margin-top: 0;margin-bottom: 0.5rem;}
</style></head><body>
<h1>Error!</h1>
<h3>${error}</h3>${details}
</body></html>`);
new Notice (error);
ea.getElement(spinnerID).link = errorDataURL;
ea.addElementsToView(false,true);
}
// --------------------------------------
// Utility to write Mermaid to dialog
// --------------------------------------
const EDITOR_LS_KEYS = {
OAI_API_KEY: "excalidraw-oai-api-key",
MERMAID_TO_EXCALIDRAW: "mermaid-to-excalidraw",
PUBLISH_LIBRARY: "publish-library-data",
};
const setMermaidDataToStorage = (mermaidDefinition) => {
try {
window.localStorage.setItem(
EDITOR_LS_KEYS.MERMAID_TO_EXCALIDRAW,
JSON.stringify(mermaidDefinition)
);
return true;
} catch (error) {
console.warn(`localStorage.setItem error: ${error.message}`);
return false;
}
};
// --------------------------------------
// Submit Prompt
// --------------------------------------
const generateImage = async(text, spinnerID, bb) => {
const requestObject = {
text,
imageGenerationProperties: {
size: imageSize,
//quality: "standard", //not supported by dall-e-2
n:1,
},
};
const result = await ea.postOpenAI(requestObject);
console.log({result, json:result?.json});
if(!result?.json?.data?.[0]?.url) {
await errorMessage(spinnerID, result?.json?.error?.message);
return;
}
const spinner = ea.getElement(spinnerID)
spinner.isDeleted = true;
const imageID = await ea.addImage(spinner.x, spinner.y, result.json.data[0].url);
const imageEl = ea.getElement(imageID);
const revisedPrompt = result.json.data[0].revised_prompt;
if(revisedPrompt) {
ea.style.fontSize = 16;
const rectID = ea.addText(imageEl.x+15, imageEl.y + imageEl.height + 50, revisedPrompt, {
width: imageEl.width-30,
textAlign: "center",
textVerticalAlign: "top",
box: true,
})
ea.getElement(rectID).strokeColor = "transparent";
ea.getElement(rectID).backgroundColor = "transparent";
ea.addToGroup(ea.getElements().filter(el=>el.id !== spinnerID).map(el=>el.id));
}
await ea.addElementsToView(false, true, true);
ea.getExcalidrawAPI().setToast({
message: IMAGE_WARNING,
duration: 15000,
closable: true
});
}
const run = async (text) => {
if(!text && !imageDataURL) {
new Notice("No prompt, aborting");
return;
}
const systemPrompt = systemPrompts[agentTask];
const outputType = outputTypes[systemPrompt.type];
const isImageGenRequest = outputType.blocktype === "image";
const isImageEditRequest = systemPrompt.type === "image-edit";
if(isImageEditRequest) {
if(!text) {
new Notice("You must provide a text prompt with instructions for how the image should be modified");
return;
}
if(!imageDataURL || !maskDataURL) {
new Notice("You must provide an image and a mask");
return;
}
}
//place spinner next to selected elements
const bb = ea.getBoundingBox(ea.getViewSelectedElements());
const spinnerID = ea.addEmbeddable(bb.topX+bb.width+100,bb.topY-(720-bb.height)/2,550,720,spinner);
//this block is in an async call using the isEACompleted flag because otherwise during debug Obsidian
//goes black (not freezes, but does not get a new frame for some reason)
//palcing this in an async call solves this issue
//If you know why this is happening and can offer a better solution, please reach out to @zsviczian
let isEACompleted = false;
setTimeout(async()=>{
await ea.addElementsToView(false,true);
ea.clear();
const embeddable = ea.getViewElements().filter(el=>el.id===spinnerID);
ea.copyViewElementsToEAforEditing(embeddable);
const els = ea.getViewSelectedElements();
ea.viewZoomToElements(false, els.concat(embeddable));
isEACompleted = true;
});
if(isImageGenRequest && !systemPrompt.prompt && !isImageEditRequest) {
generateImage(text,spinnerID,bb);
return;
}
const requestObject = isImageEditRequest
? {
...imageDataURL ? {image: imageDataURL} : {},
...(text && text.trim() !== "") ? {text} : {},
imageGenerationProperties: {
size: imageSize,
//quality: "standard", //not supported by dall-e-2
n:1,
mask: maskDataURL,
},
}
: {
...imageDataURL ? {image: imageDataURL} : {},
...(text && text.trim() !== "") ? {text} : {},
systemPrompt: systemPrompt.prompt,
instruction: outputType.instruction,
}
//Get result from GPT
const result = await ea.postOpenAI(requestObject);
console.log({result, json:result?.json});
//checking that EA has completed. Because the postOpenAI call is an async await
//I don't expect EA not to be completed by now. However the devil never sleeps.
//This (the insomnia of the Devil) is why I have a watchdog here as well
let counter = 0
while(!isEACompleted && counter++<10) sleep(50);
if(!isEACompleted) {
await errorMessage(spinnerID, "Unexpected issue with ExcalidrawAutomate");
return;
}
if(isImageEditRequest) {
if(!result?.json?.data?.[0]?.url) {
await errorMessage(spinnerID, result?.json?.error?.message);
return;
}
const spinner = ea.getElement(spinnerID)
spinner.isDeleted = true;
const imageID = await ea.addImage(spinner.x, spinner.y, result.json.data[0].url);
await ea.addElementsToView(false, true, true);
ea.getExcalidrawAPI().setToast({
message: IMAGE_WARNING,
duration: 15000,
closable: true
});
return;
}
if(!result?.json?.hasOwnProperty("choices")) {
await errorMessage(spinnerID, result?.json?.error?.message);
return;
}
//extract codeblock and display result
let content = ea.extractCodeBlocks(result.json.choices[0]?.message?.content)[0]?.data;
if(!content) {
await errorMessage(spinnerID);
return;
}
if(isImageGenRequest) {
generateImage(content,spinnerID,bb);
return;
}
switch(outputType.blocktype) {
case "html":
ea.getElement(spinnerID).link = await ea.convertStringToDataURL(content);
ea.addElementsToView(false,true);
break;
case "svg":
ea.getElement(spinnerID).isDeleted = true;
ea.importSVG(content);
ea.addToGroup(ea.getElements().map(el=>el.id));
if(ea.getViewSelectedElements().length>0) {
ea.targetView.currentPosition = {x: bb.topX+bb.width+100, y: bb.topY};
}
ea.addElementsToView(true, false);
break;
case "mermaid":
if(content.startsWith("mermaid")) {
content = content.replace(/^mermaid/,"").trim();
}
try {
result = await ea.addMermaid(content);
if(typeof result === "string") {
await errorMessage(spinnerID, "Open [More Tools / Mermaid to Excalidraw] to manually fix the received mermaid script<br><br>" + result);
return;
}
} catch (e) {
ea.addText(0,0,content);
}
ea.getElement(spinnerID).isDeleted = true;
ea.targetView.currentPosition = {x: bb.topX+bb.width+100, y: bb.topY-bb.height};
await ea.addElementsToView(true, false);
setMermaidDataToStorage(content);
new Notice("Open More Tools/Mermaid to Excalidraw in the top tools menu to edit the generated diagram",8000);
break;
}
}
// --------------------------------------
// User Interface
// --------------------------------------
let previewDiv;
const fragWithHTML = (html) => createFragment((frag) => (frag.createDiv().innerHTML = html));
const isImageGenerationTask = () => systemPrompts[agentTask].type === "image-gen" || systemPrompts[agentTask].type === "image-edit";
const addPreviewImage = () => {
if(!previewDiv) return;
previewDiv.empty();
previewDiv.createEl("img",{
attr: {
style: `max-width: 100%;max-height: 30vh;`,
src: imageDataURL,
}
});
if(maskDataURL) {
previewDiv.createEl("img",{
attr: {
style: `max-width: 100%;max-height: 30vh;`,
src: maskDataURL,
}
});
}
}
const configModal = new ea.obsidian.Modal(app);
configModal.modalEl.style.width="100%";
configModal.modalEl.style.maxWidth="1000px";
configModal.onOpen = async () => {
const contentEl = configModal.contentEl;
contentEl.createEl("h1", {text: "ExcaliAI"});
let systemPromptTextArea, systemPromptDiv, imageSizeSetting, imageSizeSettingDropdown, helpEl;
new ea.obsidian.Setting(contentEl)
.setName("What would you like to do?")
.addDropdown(dropdown=>{
Object.keys(systemPrompts).forEach(key=>dropdown.addOption(key,key));
dropdown
.setValue(agentTask)
.onChange(async (value) => {
dirty = true;
const prevTask = agentTask;
agentTask = value;
if(
(systemPrompts[prevTask].type === "image-edit" && systemPrompts[value].type !== "image-edit") ||
(systemPrompts[prevTask].type !== "image-edit" && systemPrompts[value].type === "image-edit")
) {
({imageDataURL, maskDataURL} = await generateCanvasDataURL(ea.targetView, systemPrompts[value].type === "image-edit"));
addPreviewImage();
setImageModelAndSizes();
while (imageSizeSettingDropdown.selectEl.options.length > 0) { imageSizeSettingDropdown.selectEl.remove(0); }
validSizes.forEach(size=>imageSizeSettingDropdown.addOption(size,size));
imageSizeSettingDropdown.setValue(imageSize);
}
imageSizeSetting.settingEl.style.display = isImageGenerationTask() ? "" : "none";
const prompt = systemPrompts[value].prompt;
helpEl.innerHTML = `<b>Help: </b>` + systemPrompts[value].help;
if(prompt) {
systemPromptDiv.style.display = "";
systemPromptTextArea.setValue(systemPrompts[value].prompt);
} else {
systemPromptDiv.style.display = "none";
}
});
})
helpEl = contentEl.createEl("p");
helpEl.innerHTML = `<b>Help: </b>` + systemPrompts[agentTask].help;
systemPromptDiv = contentEl.createDiv();
systemPromptDiv.createEl("h4", {text: "Customize System Prompt"});
systemPromptDiv.createEl("span", {text: "Unless you know what you are doing I do not recommend changing the system prompt"})
const systemPromptSetting = new ea.obsidian.Setting(systemPromptDiv)
.addTextArea(text => {
systemPromptTextArea = text;
const prompt = systemPrompts[agentTask].prompt;
text.inputEl.style.minHeight = "10em";
text.inputEl.style.width = "100%";
text.setValue(prompt);
text.onChange(value => {
systemPrompts[value].prompt = value;
});
if(!prompt) systemPromptDiv.style.display = "none";
})
systemPromptSetting.nameEl.style.display = "none";
systemPromptSetting.descEl.style.display = "none";
systemPromptSetting.infoEl.style.display = "none";
contentEl.createEl("h4", {text: "User Prompt"});
const userPromptSetting = new ea.obsidian.Setting(contentEl)
.addTextArea(text => {
text.inputEl.style.minHeight = "10em";
text.inputEl.style.width = "100%";
text.setValue(userPrompt);
text.onChange(value => {
userPrompt = value;
dirty = true;
})
})
userPromptSetting.nameEl.style.display = "none";
userPromptSetting.descEl.style.display = "none";
userPromptSetting.infoEl.style.display = "none";
imageSizeSetting = new ea.obsidian.Setting(contentEl)
.setName("Select image size")
.setDesc(fragWithHTML("<mark>⚠️ Important ⚠️</mark>: " + IMAGE_WARNING))
.addDropdown(dropdown=>{
validSizes.forEach(size=>dropdown.addOption(size,size));
imageSizeSettingDropdown = dropdown;
dropdown
.setValue(imageSize)
.onChange(async (value) => {
dirty = true;
imageSize = value;
if(systemPrompts[agentTask].type === "image-edit") {
({imageDataURL, maskDataURL} = await generateCanvasDataURL(ea.targetView, true));
addPreviewImage();
}
});
})
imageSizeSetting.settingEl.style.display = isImageGenerationTask() ? "" : "none";
if(imageDataURL) {
previewDiv = contentEl.createDiv({
attr: {
style: "text-align: center;",
}
});
addPreviewImage();
} else {
contentEl.createEl("h4", {text: "No elements are selected from your canvas"});
contentEl.createEl("span", {text: "Because there are no Excalidraw elements selected on the canvas, only the text prompt will be sent to OpenAI."});
}
new ea.obsidian.Setting(contentEl)
.addButton(button =>
button
.setButtonText("Run")
.onClick((event)=>{
run(userPrompt); //Obsidian crashes otherwise, likely has to do with requesting an new frame for react
configModal.close();
})
);
}
configModal.onClose = () => {
if(dirty) {
settings["User Prompt"] = userPrompt;
settings["Agent's Task"] = agentTask;
settings["Image Size"] = imageSize;
ea.setScriptSettings(settings);
}
}
configModal.open();

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M320 0c17.7 0 32 14.3 32 32V96H472c39.8 0 72 32.2 72 72V440c0 39.8-32.2 72-72 72H168c-39.8 0-72-32.2-72-72V168c0-39.8 32.2-72 72-72H288V32c0-17.7 14.3-32 32-32zM208 384c-8.8 0-16 7.2-16 16s7.2 16 16 16h32c8.8 0 16-7.2 16-16s-7.2-16-16-16H208zm96 0c-8.8 0-16 7.2-16 16s7.2 16 16 16h32c8.8 0 16-7.2 16-16s-7.2-16-16-16H304zm96 0c-8.8 0-16 7.2-16 16s7.2 16 16 16h32c8.8 0 16-7.2 16-16s-7.2-16-16-16H400zM264 256a40 40 0 1 0 -80 0 40 40 0 1 0 80 0zm152 40a40 40 0 1 0 0-80 40 40 0 1 0 0 80zM48 224H64V416H48c-26.5 0-48-21.5-48-48V272c0-26.5 21.5-48 48-48zm544 0c26.5 0 48 21.5 48 48v96c0 26.5-21.5 48-48 48H576V224h16z"/></svg>

After

Width:  |  Height:  |  Size: 694 B

673
ea-scripts/Golden Ratio.md Normal file
View File

@@ -0,0 +1,673 @@
/*
The script performs two different functions depending on the elements selected in the view.
1) In case you select text elements, the script will cycle through a set of font scales. First the 2 larger fonts following the Fibonacci sequence (fontsize * φ; fonsize * φ^2), then the 2 smaller fonts (fontsize / φ; fontsize / φ^2), finally the original size, followed again by the 2 larger fonts. If you wait 2 seconds, the sequence clears and starts from which ever font size you are on. So if you want the 3rd larges font, then toggle twice, wait 2 sec, then toggle again.
2) In case you select a single rectangle, the script will open the "Golden Grid", "Golden Spiral" window, where you can set up the type of grid or spiral you want to insert into the document.
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/golden-ratio.jpg)
<iframe width="560" height="315" src="https://www.youtube.com/embed/2SHn_ruax-s" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
Gravitational point of spiral: $$\left[x,y\right]=\left[ x + \frac{{\text{width} \cdot \phi^2}}{{\phi^2 + 1}}\;, \; y + \frac{{\text{height} \cdot \phi^2}}{{\phi^2 + 1}} \right]$$
Dimensions of inner rectangles in case of Double Spiral: $$[width, height] = \left[\frac{width\cdot(\phi^2+1)}{2\phi^2}\;, \;\frac{height\cdot(\phi^2+1)}{2\phi^2}\right]$$
```js*/
const phi = (1 + Math.sqrt(5)) / 2; // Golden Ratio (φ)
const inversePhi = (1-1/phi);
const pointsPerCurve = 20; // Number of points per curve segment
const ownerWindow = ea.targetView.ownerWindow;
const hostLeaf = ea.targetView.leaf;
let dirty = false;
const ids = [];
const textEls = ea.getViewSelectedElements().filter(el=>el.type === "text");
let rect = ea.getViewSelectedElements().length === 1 ? ea.getViewSelectedElement() : null;
if(!rect || rect.type !== "rectangle") {
//Fontsize cycle
if(textEls.length>0) {
if(window.excalidrawGoldenRatio) {
clearTimeout(window.excalidrawGoldenRatio?.timer);
} else {
window.excalidrawGoldenRatio = {timer: null, cycle:-1};
}
window.excalidrawGoldenRatio.timer = setTimeout(()=>{delete window.excalidrawGoldenRatio;},2000);
window.excalidrawGoldenRatio.cycle = (window.excalidrawGoldenRatio.cycle+1)%5;
ea.copyViewElementsToEAforEditing(textEls);
ea.getElements().forEach(el=> {
el.fontSize = window.excalidrawGoldenRatio.cycle === 2
? el.fontSize / Math.pow(phi,4)
: el.fontSize * phi;
const font = ExcalidrawLib.getFontString(el);
const lineHeight = ExcalidrawLib.getDefaultLineHeight(el.fontFamily);
const {width, height, baseline} = ExcalidrawLib.measureText(el.originalText, font, lineHeight);
el.width = width;
el.height = height;
el.baseline = baseline;
});
ea.addElementsToView();
return;
}
new Notice("Select text elements, or a select a single rectangle");
return;
}
ea.copyViewElementsToEAforEditing([rect]);
rect = ea.getElement(rect.id);
ea.style.strokeColor = rect.strokeColor;
ea.style.strokeWidth = rect.strokeWidth;
ea.style.roughness = rect.roughness;
ea.style.angle = rect.angle;
let {x,y,width,height} = rect;
// --------------------------------------------
// Load Settings
// --------------------------------------------
let settings = ea.getScriptSettings();
if(!settings["Horizontal Grid"]) {
settings = {
"Horizontal Grid" : {
value: "left-right",
valueset: ["none","letf-right","right-left","center-out","center-in"]
},
"Vertical Grid": {
value: "none",
valueset: ["none","top-down","bottom-up","center-out","center-in"]
},
"Size": {
value: "6",
valueset: ["2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20"]
},
"Aspect Choice": {
value: "none",
valueset: ["none","adjust-width","adjust-height"]
},
"Type": "grid",
"Spiral Orientation": {
value: "top-left",
valueset: ["double","top-left","top-right","bottom-right","bottom-left"]
},
"Lock Elements": false,
"Send to Back": false,
"Update Style": false,
"Bold Spiral": false,
};
await ea.setScriptSettings(settings);
}
let hDirection = settings["Horizontal Grid"].value;
let vDirection = settings["Vertical Grid"].value;
let aspectChoice = settings["Aspect Choice"].value;
let type = settings["Type"];
let spiralOrientation = settings["Spiral Orientation"].value;
let lockElements = settings["Lock Elements"];
let sendToBack = settings["Send to Back"];
let size = parseInt(settings["Size"].value);
let updateStyle = settings["Update Style"];
let boldSpiral = settings["Bold Spiral"];
// --------------------------------------------
// Rotation
// --------------------------------------------
let centerX, centerY;
const rotatePointAndAddToElementList = (elementID) => {
ids.push(elementID);
const line = ea.getElement(elementID);
// Calculate the initial position of the line's center
const lineCenterX = line.x + line.width / 2;
const lineCenterY = line.y + line.height / 2;
// Calculate the difference between the line's center and the rectangle's center
const diffX = lineCenterX - (rect.x + rect.width / 2);
const diffY = lineCenterY - (rect.y + rect.height / 2);
// Apply the rotation to the difference
const cosTheta = Math.cos(rect.angle);
const sinTheta = Math.sin(rect.angle);
const rotatedX = diffX * cosTheta - diffY * sinTheta;
const rotatedY = diffX * sinTheta + diffY * cosTheta;
// Calculate the new position of the line's center with respect to the rectangle's center
const newLineCenterX = rotatedX + (rect.x + rect.width / 2);
const newLineCenterY = rotatedY + (rect.y + rect.height / 2);
// Update the line's coordinates by adjusting for the change in the center
line.x += newLineCenterX - lineCenterX;
line.y += newLineCenterY - lineCenterY;
}
const rotatePointsWithinRectangle = (points) => {
const centerX = rect.x + rect.width / 2;
const centerY = rect.y + rect.height / 2;
const cosTheta = Math.cos(rect.angle);
const sinTheta = Math.sin(rect.angle);
const rotatedPoints = points.map(([x, y]) => {
// Translate the point relative to the rectangle's center
const translatedX = x - centerX;
const translatedY = y - centerY;
// Apply the rotation to the translated coordinates
const rotatedX = translatedX * cosTheta - translatedY * sinTheta;
const rotatedY = translatedX * sinTheta + translatedY * cosTheta;
// Translate back to the original coordinate system
const finalX = rotatedX + centerX;
const finalY = rotatedY + centerY;
return [finalX, finalY];
});
return rotatedPoints;
}
// --------------------------------------------
// Grid
// --------------------------------------------
const calculateGoldenSum = (baseOfGoldenGrid, pow) => {
const ratio = 1 / phi;
const geometricSum = baseOfGoldenGrid * ((1 - Math.pow(ratio, pow)) / (1 - ratio));
return geometricSum;
};
const findBaseForGoldenGrid = (targetValue, n, scenario) => {
const ratio = 1 / phi;
if (scenario === "center-out") {
return targetValue * (2-2*ratio) / (1 + ratio + 2*Math.pow(ratio,n));
} else if (scenario === "center-in") {
return targetValue*2*(1-ratio)*Math.pow(phi,n-1) /(2*Math.pow(phi,n-1)*(1-Math.pow(ratio,n))-1+ratio);
} else {
return targetValue * (1-ratio)/(1-Math.pow(ratio,n));
}
}
const calculateOffsetVertical = (scenario, base) => {
if (scenario === "center-out") return base / 2;
if (scenario === "center-in") return base / Math.pow(phi, size + 1) / 2;
return 0;
};
const horizontal = (direction, scenario) => {
const base = findBaseForGoldenGrid(width, size + 1, scenario);
const totalGridWidth = calculateGoldenSum(base, size + 1);
for (i = 1; i <= size; i++) {
const offset =
scenario === "center-out"
? totalGridWidth - calculateGoldenSum(base, i)
: calculateGoldenSum(base, size + 1 - i);
const x2 =
direction === "left"
? x + offset
: x + width - offset;
rotatePointAndAddToElementList(
ea.addLine([
[x2, y],
[x2, y + height],
])
);
}
};
const vertical = (direction, scenario) => {
const base = findBaseForGoldenGrid(height, size + 1, scenario);
const totalGridWidth = calculateGoldenSum(base, size + 1);
for (i = 1; i <= size; i++) {
const offset =
scenario === "center-out"
? totalGridWidth - calculateGoldenSum(base, i)
: calculateGoldenSum(base, size + 1 - i);
const y2 =
direction === "top"
? y + offset
: y + height - offset;
rotatePointAndAddToElementList(
ea.addLine([
[x, y2],
[x+width, y2],
])
);
}
};
const centerHorizontal = (scenario) => {
width = width / 2;
horizontal("left", scenario);
x += width;
horizontal("right", scenario);
x -= width;
width = 2*width;
};
const centerVertical = (scenario) => {
height = height / 2;
vertical("top", scenario);
y += height;
vertical("bottom", scenario);
y -= height;
height = 2*height;
};
const drawGrid = () => {
switch(hDirection) {
case "none": break;
case "left-right": horizontal("left"); break;
case "right-left": horizontal("right"); break;
case "center-out": centerHorizontal("center-out"); break;
case "center-in": centerHorizontal("center-in"); break;
}
switch(vDirection) {
case "none": break;
case "top-down": vertical("top"); break;
case "bottom-up": vertical("bottom"); break;
case "center-out": centerVertical("center-out"); break;
case "center-in": centerVertical("center-in"); break;
}
}
// --------------------------------------------
// Draw Spiral
// --------------------------------------------
const drawSpiral = () => {
let nextX, nextY, nextW, nextH;
let spiralPoints = [];
let curveEndX, curveEndY, curveX, curveY;
const phaseShift = {
"bottom-right": 0,
"bottom-left": 2,
"top-left": 2,
"top-right": 0,
}[spiralOrientation];
let curveStartX = {
"bottom-right": x,
"bottom-left": x+width,
"top-left": x+width,
"top-right": x,
}[spiralOrientation];
let curveStartY = {
"bottom-right": y+height,
"bottom-left": y+height,
"top-left": y,
"top-right": y,
}[spiralOrientation];
const mirror = spiralOrientation === "bottom-left" || spiralOrientation === "top-right";
for (let i = phaseShift; i < size+phaseShift; i++) {
const curvePhase = i%4;
const linePhase = mirror?[0,3,2,1][curvePhase]:curvePhase;
const longHorizontal = width/phi;
const shortHorizontal = width*inversePhi;
const longVertical = height/phi;
const shortVertical = height*inversePhi;
switch(linePhase) {
case 0: //right
nextX = x + longHorizontal;
nextY = y;
nextW = shortHorizontal;
nextH = height;
break;
case 1: //down
nextX = x;
nextY = y + longVertical;
nextW = width;
nextH = shortVertical;
break;
case 2: //left
nextX = x;
nextY = y;
nextW = shortHorizontal;
nextH = height;
break;
case 3: //up
nextX = x;
nextY = y;
nextW = width;
nextH = shortVertical;
break;
}
switch(curvePhase) {
case 0: //right
curveEndX = nextX;
curveEndY = mirror ? nextY + nextH : nextY;
break;
case 1: //down
curveEndX = nextX + nextW;
curveEndY = mirror ? nextY + nextH : nextY;
break;
case 2: //left
curveEndX = nextX + nextW;
curveEndY = mirror ? nextY : nextY + nextH;
break;
case 3: //up
curveEndX = nextX;
curveEndY = mirror ? nextY : nextY + nextH;
break;
}
// Add points for the curve segment
for (let j = 0; j <= pointsPerCurve; j++) {
const t = j / pointsPerCurve;
const angle = -Math.PI / 2 * t;
switch(curvePhase) {
case 0:
curveX = curveEndX + (curveStartX - curveEndX) * Math.cos(angle);
curveY = curveStartY + (curveStartY - curveEndY) * Math.sin(angle);
break;
case 1:
curveX = curveStartX + (curveStartX - curveEndX) * Math.sin(angle);
curveY = curveEndY + (curveStartY - curveEndY) * Math.cos(angle);
break;
case 2:
curveX = curveEndX + (curveStartX - curveEndX) * Math.cos(angle);
curveY = curveStartY + (curveStartY - curveEndY) * Math.sin(angle);
break;
case 3:
curveX = curveStartX + (curveStartX - curveEndX) * Math.sin(angle);
curveY = curveEndY + (curveStartY - curveEndY) * Math.cos(angle);
break;
}
spiralPoints.push([curveX, curveY]);
}
x = nextX;
y = nextY;
curveStartX = curveEndX;
curveStartY = curveEndY;
width = nextW;
height = nextH;
switch(linePhase) {
case 0: rotatePointAndAddToElementList(ea.addLine([[x,y],[x,y+height]]));break;
case 1: rotatePointAndAddToElementList(ea.addLine([[x,y],[x+width,y]]));break;
case 2: rotatePointAndAddToElementList(ea.addLine([[x+width,y],[x+width,y+height]]));break;
case 3: rotatePointAndAddToElementList(ea.addLine([[x,y+height],[x+width,y+height]]));break;
}
}
const strokeWidth = ea.style.strokeWidth;
ea.style.strokeWidth = strokeWidth * (boldSpiral ? 3 : 1);
const angle = ea.style.angle;
ea.style.angle = 0;
ids.push(ea.addLine(rotatePointsWithinRectangle(spiralPoints)));
ea.style.angle = angle;
ea.style.strokeWidth = strokeWidth;
}
// --------------------------------------------
// Update Aspect Ratio
// --------------------------------------------
const updateAspectRatio = () => {
switch(aspectChoice) {
case "none": break;
case "adjust-width": rect.width = rect.height/phi; break;
case "adjust-height": rect.height = rect.width/phi; break;
}
({x,y,width,height} = rect);
centerX = x + width/2;
centerY = y + height/2;
}
// --------------------------------------------
// UI
// --------------------------------------------
draw = async () => {
if(updateStyle) {
ea.style.strokeWidth = 0.5; rect.strokeWidth;
ea.style.roughness = 0; rect.roughness;
ea.style.roundness = null;
rect.strokeWidth = 0.5;
rect.roughness = 0;
rect.roundness = null;
}
updateAspectRatio();
switch(type) {
case "grid": drawGrid(); break;
case "spiral":
if(spiralOrientation === "double") {
wInner = width * (Math.pow(phi,2)+1)/(2*Math.pow(phi,2));
hInner = height * (Math.pow(phi,2)+1)/(2*Math.pow(phi,2));
x2 = width - wInner + x;
y2 = height - hInner + y;
width = wInner;
height = hInner;
rotatePointAndAddToElementList(ea.addRect(x,y,width,height));
spiralOrientation = "bottom-right";
drawSpiral();
x = x2;
y = y2;
width = wInner;
height = hInner;
rotatePointAndAddToElementList(ea.addRect(x,y,width,height));
spiralOrientation = "top-left";
drawSpiral();
spiralOrientation = "double";
} else {
drawSpiral();
}
break;
}
ea.addToGroup(ids);
ids.push(rect.id);
ea.addToGroup(ids);
lockElements && ea.getElements().forEach(el=>{el.locked = true;});
await ea.addElementsToView(false,false,!sendToBack);
!lockElements && ea.selectElementsInView(ea.getViewElements().filter(el => ids.includes(el.id)));
}
const modal = new ea.obsidian.Modal(app);
const fragWithHTML = (html) => createFragment((frag) => (frag.createDiv().innerHTML = html));
const keydownListener = (e) => {
if(hostLeaf !== app.workspace.activeLeaf) return;
if(hostLeaf.width === 0 && hostLeaf.height === 0) return;
if(e.key === "Enter" && (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey)) {
e.preventDefault();
modal.close();
draw()
}
}
ownerWindow.addEventListener('keydown',keydownListener);
modal.onOpen = async () => {
const contentEl = modal.contentEl;
contentEl.createEl("h1", {text: "Golden Ratio"});
new ea.obsidian.Setting(contentEl)
.setName("Adjust Rectangle Aspect Ratio to Golden Ratio")
.addDropdown(dropdown=>dropdown
.addOption("none","None")
.addOption("adjust-width","Adjust Width")
.addOption("adjust-height","Adjust Height")
.setValue(aspectChoice)
.onChange(value => {
aspectChoice = value;
dirty = true;
})
);
new ea.obsidian.Setting(contentEl)
.setName("Change Line Style To: thin, architect, sharp")
.addToggle(toggle=>
toggle
.setValue(updateStyle)
.onChange(value => {
dirty = true;
updateStyle = value;
})
)
let sizeEl;
new ea.obsidian.Setting(contentEl)
.setName("Number of lines")
.addSlider(slider => slider
.setLimits(2, 20, 1)
.setValue(size)
.onChange(value => {
sizeEl.innerText = ` ${value.toString()}`;
size = value;
dirty = true;
}),
)
.settingEl.createDiv("", el => {
sizeEl = el;
el.style.minWidth = "2.3em";
el.style.textAlign = "right";
el.innerText = ` ${size.toString()}`;
});
new ea.obsidian.Setting(contentEl)
.setName("Lock Rectangle and Gridlines")
.addToggle(toggle=>
toggle
.setValue(lockElements)
.onChange(value => {
dirty = true;
lockElements = value;
})
)
new ea.obsidian.Setting(contentEl)
.setName("Send to Back")
.addToggle(toggle=>
toggle
.setValue(sendToBack)
.onChange(value => {
dirty = true;
sendToBack = value;
})
)
let bGrid, bSpiral;
let sHGrid, sVGrid, sSpiral, sBoldSpiral;
const showGridSettings = (value) => {
value
? (bGrid.setCta(), bSpiral.removeCta())
: (bGrid.removeCta(), bSpiral.setCta());
sHGrid.settingEl.style.display = value ? "" : "none";
sVGrid.settingEl.style.display = value ? "" : "none";
sSpiral.settingEl.style.display = !value ? "" : "none";
sBoldSpiral.settingEl.style.display = !value ? "" : "none";
}
new ea.obsidian.Setting(contentEl)
.setName(fragWithHTML("<h3>Output Type</h3>"))
.addButton(button => {
bGrid = button;
button
.setButtonText("Grid")
.setCta(type === "grid")
.onClick(event => {
type = "grid";
showGridSettings(true);
dirty = true;
})
})
.addButton(button => {
bSpiral = button;
button
.setButtonText("Spiral")
.setCta(type === "spiral")
.onClick(event => {
type = "spiral";
showGridSettings(false);
dirty = true;
})
});
sSpiral = new ea.obsidian.Setting(contentEl)
.setName("Spiral Orientation")
.addDropdown(dropdown=>dropdown
.addOption("double","Double")
.addOption("top-left","Top left")
.addOption("top-right","Top right")
.addOption("bottom-right","Bottom right")
.addOption("bottom-left","Bottom left")
.setValue(spiralOrientation)
.onChange(value => {
spiralOrientation = value;
dirty = true;
})
);
sBoldSpiral = new ea.obsidian.Setting(contentEl)
.setName("Spiral with Bold Line")
.addToggle(toggle=>
toggle
.setValue(boldSpiral)
.onChange(value => {
dirty = true;
boldSpiral = value;
})
)
sHGrid = new ea.obsidian.Setting(contentEl)
.setName("Horizontal Grid")
.addDropdown(dropdown=>dropdown
.addOption("none","None")
.addOption("left-right","Left to right")
.addOption("right-left","Right to left")
.addOption("center-out","Center out")
.addOption("center-in","Center in")
.setValue(hDirection)
.onChange(value => {
hDirection = value;
dirty = true;
})
);
sVGrid = new ea.obsidian.Setting(contentEl)
.setName("Vertical Grid")
.addDropdown(dropdown=>dropdown
.addOption("none","None")
.addOption("top-down","Top down")
.addOption("bottom-up","Bottom up")
.addOption("center-out","Center out")
.addOption("center-in","Center in")
.setValue(vDirection)
.onChange(value => {
vDirection = value;
dirty = true;
})
);
showGridSettings(type === "grid");
new ea.obsidian.Setting(contentEl)
.addButton(button => button
.setButtonText("Run")
.setCta(true)
.onClick(async (event) => {
draw();
modal.close();
})
);
}
modal.onClose = () => {
if(dirty) {
settings["Horizontal Grid"].value = hDirection;
settings["Vertical Grid"].value = vDirection;
settings["Size"].value = size.toString();
settings["Aspect Choice"].value = aspectChoice;
settings["Type"] = type;
settings["Spiral Orientation"].value = spiralOrientation;
settings["Lock Elements"] = lockElements;
settings["Send to Back"] = sendToBack;
settings["Update Style"] = updateStyle;
settings["Bold Spiral"] = boldSpiral;
ea.setScriptSettings(settings);
}
ownerWindow.removeEventListener('keydown',keydownListener);
}
modal.open();

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 642.8 373.7" stroke="currentColor"><path stroke-linecap="round" stroke-width="4" d="M5 5h633M5 5h633m0 0v364m0-364v364m0 0H5m633 0H5m0 0V5m0 364V5m242 0v364m0-364v364M5 144h242M5 144h242M154 5v139m0-139v139m0-53h93m-93 0h93m-57 0v53m0-53v53m-36-33h36m-36 0h36m-14-20v20m0-20v20m0-8h14m-14 0h14"/><path stroke-linecap="round" stroke-width="12" d="m638 5-5 57m5-57-5 57m0 0-14 55m14-55-14 55m0 0-24 53m24-53-24 53m0 0-32 49m32-49-32 49m0 0-40 43m40-43-40 43m0 0-46 37m46-37-46 37m0 0-53 30m53-30-53 30m0 0-56 22m56-22-56 22m0 0-60 13m60-13-60 13m0 0-61 5m61-5-61 5m0 0s0 0 0 0m0 0s0 0 0 0m0 0-38-3m38 3-38-3m0 0-37-8m37 8-37-8m0 0-35-14m35 14-35-14m0 0-32-18m32 18-32-18m0 0-29-23m29 23-29-23m0 0-25-27m25 27-25-27m0 0-20-30m20 30-20-30m0 0-14-33m14 33-14-33m0 0-9-34m9 34-9-34m0 0-3-35m3 35-3-35m0 0s0 0 0 0m0 0s0 0 0 0m0 0 2-22m-2 22 2-22m0 0 5-21m-5 21 5-21m0 0 9-20m-9 20 9-20m0 0 13-19M21 81l13-19m0 0 15-16M34 62l15-16m0 0 18-14M49 46l18-14m0 0 20-12M67 32c4-3 8-6 20-12m0 0 21-8m-21 8 21-8m0 0 23-5m-23 5 23-5m0 0 23-2m-23 2 23-2m0 0s0 0 0 0m0 0s0 0 0 0m0 0 15 1m-15-1 15 1m0 0 14 3m-14-3 14 3m0 0 13 5m-13-5 13 5m0 0 13 7m-13-7 13 7m0 0 11 9m-11-9 11 9m0 0 9 10m-9-10 9 10m0 0 8 12m-8-12 8 12m0 0 5 12m-5-12 5 12m0 0 4 13m-4-13 4 13m0 0 1 14m-1-14 1 14m0 0s0 0 0 0m0 0s0 0 0 0m0 0-1 8m1-8-1 8m0 0-2 8m2-8-2 8m0 0-4 8m4-8-4 8m0 0-4 7m4-7-4 7m0 0-6 6m6-6-6 6m0 0-7 6m7-6-7 6m0 0-7 4m7-4-7 4m0 0-9 3m9-3-9 3m0 0-8 2m8-2-8 2m0 0-9 1m9-1-9 1m0 0s0 0 0 0m0 0s0 0 0 0m0 0h-6m6 0h-6m0 0-5-2m5 2-5-2m0 0-5-2m5 2-5-2m0 0-5-2m5 2-5-2m0 0-4-4m4 4-4-4m0 0-4-4m4 4-4-4m0 0-3-4m3 4-3-4m0 0-2-5m2 5-2-5m0 0-1-5m1 5-1-5m0 0-1-5m1 5-1-5m0 0s0 0 0 0m0 0s0 0 0 0m0 0 1-3m-1 3 1-3m0 0v-3m0 3v-3m0 0 2-3m-2 3 2-3m0 0 2-3m-2 3 2-3m0 0 2-2m-2 2 2-2m0 0 2-2m-2 2 2-2m0 0 3-2m-3 2 3-2m0 0 3-1m-3 1 3-1m0 0 4-1m-4 1 4-1m0 0h3m-3 0h3m0 0s0 0 0 0m0 0s0 0 0 0m0 0h2m-2 0h2m0 0h2m-2 0h2m0 0 2 1m-2-1 2 1m0 0 2 1m-2-1 2 1m0 0 2 2m-2-2 2 2m0 0 1 1m-1-1 1 1m0 0 1 2m-1-2 1 2m0 0 1 2m-1-2 1 2m0 0v1m0-1v1m0 0 1 2m-1-2 1 2"/></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -29,7 +29,7 @@ try {
elHeight /= 1.1;
}
} else if (elWidth * elHeight < areaAvailable) {
while (elWidth * elHeight > areaAvailable) {
while (elWidth * elHeight < areaAvailable) {
elWidth *= 1.1;
elHeight *= 1.1;
}
@@ -64,4 +64,4 @@ try {
ea.addElementsToView(false, true, true);
} catch (err) {
_ = new Notice(err.toString())
}
}

View File

@@ -0,0 +1,75 @@
/*
Adds support for pen inversion, a.k.a. the hardware eraser on the back of your pen.
Simply use the eraser on a supported pen, and it will erase. Your previous tool will be restored when the eraser leaves the screen.
(Tested with a surface pen, but should work with all windows ink devices, and probably others)
**Note:** This script will stay active until the *Obsidian* window is closed.
Compatible with my *Auto Draw for Pen* script
```javascript
*/
(function() {
'use strict';
let activated
let revert
function handlePointer(e) {
const activeTool = ea.getExcalidrawAPI().getAppState().activeTool;
const isEraser = e.pointerType === 'pen' && e.buttons & 32
function setActiveTool(t) {
ea.getExcalidrawAPI().setActiveTool(t)
}
if (!activated && isEraser) {
//Store previous tool
const btns = document.querySelectorAll('.App-toolbar input.ToolIcon_type_radio')
for (const i in btns) {
if (btns[i]?.checked) {
revert = btns[i]
}
}
revert = activeTool
// Activate eraser tool
setActiveTool({type: "eraser"})
activated = true
// Force Excalidraw to recognize this the same as pen tip
// https://github.com/excalidraw/excalidraw/blob/4a9fac2d1e5c4fac334201ef53c6f5d2b5f6f9f5/src/components/App.tsx#L2945-L2951
Object.defineProperty(e, 'button', {
value: 0,
writable: false
});
}
// Keep on eraser!
if (isEraser && activated) {
setActiveTool({type: "eraser"})
}
if (activated && !isEraser) {
// Revert tool on release
// revert.click()
setActiveTool(revert)
activated = false
// Force delete "limbo" elements
// This doesn't happen on the web app
// It's a bug caused by switching to eraser during a stroke
ea.setView("active");
var del = []
for (const i in ea.getViewElements()) {
const element = ea.getViewElements()[i];
if (element.opacity === 20) {
del.push(element)
}
}
ea.deleteViewElements(del)
setActiveTool(revert)
}
}
window.addEventListener('pointerdown', handlePointer, { capture: true })
window.addEventListener('pointermove', handlePointer, { capture: true })
})();

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 448 512" style="enable-background:new 0 0 448 512;" xml:space="preserve">
<style type="text/css">
.st0{stroke:#000000;stroke-width:2;stroke-miterlimit:10;}
</style>
<path class="st0" d="M355.8,234.1"/>
<g>
<path class="st0" d="M404.8,293.5L306.9,208l-120,137.4l97.9,85.5c13.6,11.9,34.4,10.5,46.3-3.1l76.8-88
C419.9,326.2,418.5,305.5,404.8,293.5z M389.4,322.2l-78.2,89.6c-3.8,4.3-10.4,4.8-14.8,1l-77.8-68l92-105.3l77.8,68
C392.8,311.2,393.2,317.8,389.4,322.2z"/>
<polygon class="st0" points="52.4,103.7 64.4,238.9 93,263.8 213,126.4 184.4,101.4 "/>
<rect x="108.3" y="185.1" transform="matrix(0.6578 -0.7532 0.7532 0.6578 -108.9276 230.7956)" class="st0" width="182.4" height="100.3"/>
<path class="st0" d="M109.7,381.6c-23.7-22.2-40-49.3-48.9-78.2c8.2-0.9,22.4-3.6,30.1-12.3c-12.6-12.5-25.3-25-37.9-37.5
c0-0.1,0-0.3,0-0.4l-23.6-22c-6,60.7,15.5,123.7,63.7,168.8s112.5,62.4,172.7,52.4l-24.1-22.6C194.8,432.3,146.9,416.4,109.7,381.6
z"/>
<path class="st0" d="M368.8,105.4c-56-52.4-133.5-67.2-201.3-45.5l21,19.6c56.1-13.2,117.7,1.1,163.1,43.7
c33,30.9,51.7,71.2,55.9,112.7c-0.2-0.4-0.3-0.6-0.3-0.6s-25.1-0.1-36.5,12.7c11.8,11.6,23.5,23.3,35.3,34.9c0,0,0,0.1,0,0.1
l2.4,2.3c0.4,0.4,0.9,0.9,1.3,1.3c0,0,0,0,0,0l17.7,16.6C444.7,234.1,424.8,157.7,368.8,105.4z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,65 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-invert-colors.jpg)
The script inverts the colors on the canvas including the color palette in Element Properties.
```javascript
*/
const defaultColorPalette = { // https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/1.6.8
elementStroke:["#000000","#343a40","#495057","#c92a2a","#a61e4d","#862e9c","#5f3dc4","#364fc7","#1864ab","#0b7285","#087f5b","#2b8a3e","#5c940d","#e67700","#d9480f"],
elementBackground:["transparent","#ced4da","#868e96","#fa5252","#e64980","#be4bdb","#7950f2","#4c6ef5","#228be6","#15aabf","#12b886","#40c057","#82c91e","#fab005","#fd7e14"],
canvasBackground:["#ffffff","#f8f9fa","#f1f3f5","#fff5f5","#fff0f6","#f8f0fc","#f3f0ff","#edf2ff","#e7f5ff","#e3fafc","#e6fcf5","#ebfbee","#f4fce3","#fff9db","#fff4e6"]
};
const api = ea.getExcalidrawAPI();
const st = api.getAppState();
let colorPalette = st.colorPalette ?? defaultColorPalette;
if (Object.entries(colorPalette).length === 0) colorPalette = defaultColorPalette;
if(!colorPalette.elementStroke || Object.entries(colorPalette.elementStroke).length === 0) colorPalette.elementStroke = defaultColorPalette.elementStroke;
if(!colorPalette.elementBackground || Object.entries(colorPalette.elementBackground).length === 0) colorPalette.elementBackground = defaultColorPalette.elementBackground;
if(!colorPalette.canvasBackground || Object.entries(colorPalette.canvasBackground).length === 0) colorPalette.canvasBackground = defaultColorPalette.canvasBackground;
const invertColor = (color) => {
if(color.toLowerCase()==="transparent") return color;
const cm = ea.getCM(color);
const lightness = cm.lightness;
cm.lightnessTo(Math.abs(lightness-100));
switch (cm.format) {
case "hsl": return cm.stringHSL();
case "rgb": return cm.stringRGB();
case "hsv": return cm.stringHSV();
default: return cm.stringHEX({alpha: false});
}
}
function invertColorsRecursively(obj) {
if (typeof obj === 'string') {
return invertColor(obj);
} else if (Array.isArray(obj)) {
return obj.map(item => invertColorsRecursively(item));
} else if (typeof obj === 'object' && obj !== null) {
const result = {};
Object.keys(obj).forEach(key => result[key] = invertColorsRecursively(obj[key]));
return result;
} else {
return obj;
}
}
colorPalette = invertColorsRecursively(colorPalette);
ea.copyViewElementsToEAforEditing(ea.getViewElements());
ea.getElements().forEach(el=>{
el.strokeColor = invertColor(el.strokeColor);
el.backgroundColor = invertColor(el.backgroundColor);
});
ea.viewUpdateScene({
appState:{
colorPalette,
viewBackgroundColor: invertColor(st.viewBackgroundColor),
currentItemStrokeColor: invertColor(st.currentItemStrokeColor),
currentItemBackgroundColor: invertColor(st.currentItemBackgroundColor)
},
elements: ea.getElements()
});

View File

@@ -0,0 +1,13 @@
<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">
<path stroke-width="2" fill="none" d="M12 16a4 4 0 1 0 0-8 4 4 0 0 0 0 8z"></path>
<path stroke-width="2" fill="none" d="M12 8a2.828 2.828 0 1 0 4 4"></path>
<path stroke-width="2" fill="none" d="M12 2v2"></path>
<path stroke-width="2" fill="none" d="M12 20v2"></path>
<path stroke-width="2" fill="none" d="m4.93 4.93 1.41 1.41"></path>
<path stroke-width="2" fill="none" d="m17.66 17.66 1.41 1.41"></path>
<path stroke-width="2" fill="none" d="M2 12h2"></path>
<path stroke-width="2" fill="none" d="M20 12h2"></path>
<path stroke-width="2" fill="none" d="m6.34 17.66-1.41 1.41"></path>
<path stroke-width="2" fill="none" d="m19.07 4.93-1.41 1.41"></path>
</svg>

After

Width:  |  Height:  |  Size: 865 B

View File

@@ -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);

View File

@@ -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

View File

@@ -26,8 +26,7 @@ if(!settings["Gap"]) {
let gapValue = settings["Gap"].value;
const selectedIndividualArrows = ea.getMaximumGroups(ea.getViewSelectedElements())
.reduce((result, group) => (group.length === 1 && (group[0].type === 'arrow' || group[0].type === 'line')) ?
[...result, group[0]] : result, []);
.reduce((result, g) => [...result, ...g.filter(el => el.type === 'arrow')], []);
const allElements = ea.getViewElements();
for(const arrow of selectedIndividualArrows) {

View File

@@ -0,0 +1,36 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-organic-line-legacy.jpg)
Converts selected freedraw lines such that pencil pressure will decrease from maximum to minimum from the beginning of the line to its end. The resulting line is placed at the back of the layers, under all other items. Helpful when drawing organic mindmaps.
This is the old script from this [video](https://youtu.be/JMcNDdj_lPs?t=479). Since it's release this has been superseded by custom pens that you can enable in plugin settings. For more on custom pens, watch [this](https://youtu.be/OjNhjaH2KjI)
The benefit of the approach in this implementation of custom pens is that it will look the same on excalidraw.com when you copy your drawing over for sharing with non-Obsidian users. Otherwise custom pens are faster to use and much more configurable.
```javascript
*/
let elements = ea.getViewSelectedElements().filter((el)=>["freedraw","line","arrow"].includes(el.type));
if(elements.length === 0) {
elements = ea.getViewSelectedElements();
const len = elements.length;
if(len === 0 || ["freedraw","line","arrow"].includes(elements[len].type)) {
return;
}
elements = [elements[len]];
}
ea.copyViewElementsToEAforEditing(elements);
ea.getElements().forEach((el)=>{
el.simulatePressure = false;
el.type = "freedraw";
el.pressures = [];
const len = el.points.length;
for(i=0;i<len;i++)
el.pressures.push((len-i)/len);
});
await ea.addElementsToView(false,true);
elements.forEach((el)=>ea.moveViewElementToZIndex(el.id,0));
const ids=ea.getElements().map(el=>el.id);
ea.selectElementsInView(ea.getViewElements().filter(el=>ids.contains(el.id)));

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

@@ -5,7 +5,15 @@ Converts selected freedraw lines such that pencil pressure will decrease from ma
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.8.8")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
let elements = ea.getViewSelectedElements().filter((el)=>["freedraw","line","arrow"].includes(el.type));
//if nothing is selected find the last element that was drawn and use it if it is the right element type
if(elements.length === 0) {
elements = ea.getViewSelectedElements();
const len = elements.length;
@@ -14,14 +22,59 @@ if(elements.length === 0) {
}
elements = [elements[len]];
}
elements.forEach((el)=>{
const lineType = await utils.suggester(["Thick to thin", "Thin to thick to thin"],["l1","l2"],"Select the type of line");
if(!lineType) return;
ea.copyViewElementsToEAforEditing(elements);
ea.getElements().forEach((el)=>{
el.simulatePressure = false;
el.type = "freedraw";
el.pressures = [];
const len = el.points.length;
for(i=0;i<len;i++)
el.pressures.push((len-i)/len);
el.pressures = Array(el.points.length).fill(1);
el.customData = {
strokeOptions: {
... lineType === "l1"
? {
options: {
thinning: 1,
smoothing: 0.5,
streamline: 0.5,
easing: "linear",
start: {
taper: 0,
cap: true
},
end: {
taper: true,
easing: "linear",
cap: false
}
}
}
: {
options: {
thinning: 4,
smoothing: 0.5,
streamline: 0.5,
easing: "linear",
start: {
taper: true,
easing: "linear",
cap: true
},
end: {
taper: true,
easing: "linear",
cap: false
}
}
}
}
};
});
ea.copyViewElementsToEAforEditing(elements);
await ea.addElementsToView(false,false);
elements.forEach((el)=>ea.moveViewElementToZIndex(el.id,0));
await ea.addElementsToView(false,true);
elements.forEach((el)=>ea.moveViewElementToZIndex(el.id,0));
const ids=ea.getElements().map(el=>el.id);
ea.selectElementsInView(ea.getViewElements().filter(el=>ids.contains(el.id)));

View File

@@ -0,0 +1,37 @@
/*
Copies the text from the selected PDF page on the Excalidraw canvas to the clipboard.
<iframe width="560" height="315" src="https://www.youtube.com/embed/Kwt_8WdOUT4" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
Link:: https://youtu.be/Kwt_8WdOUT4
```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");

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M64 0C28.7 0 0 28.7 0 64V448c0 35.3 28.7 64 64 64H320c35.3 0 64-28.7 64-64V160H256c-17.7 0-32-14.3-32-32V0H64zM256 0V128H384L256 0zM112 256H272c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16s7.2-16 16-16zm0 64H272c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16s7.2-16 16-16zm0 64H272c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16s7.2-16 16-16z"/></svg>

After

Width:  |  Height:  |  Size: 622 B

View File

@@ -1,19 +1,42 @@
/*
<iframe width="560" height="315" src="https://www.youtube.com/embed/epYNx2FSf2w" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
Link:: https://youtu.be/epYNx2FSf2w
<iframe width="560" height="315" src="https://www.youtube.com/embed/diBT5iaoAYo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
Link:: https://youtu.be/diBT5iaoAYo
Design your palette at http://paletton.com/
Once you are happy with your colors, click Tables/Export in the bottom right of the screen:
![|400](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-sketch-palette-loader-1.jpg)
Then click "Color swatches/as Sketch Palette"
![|400](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-sketch-palette-loader-2.jpg)
Copy the contents of the page to a markdown file in your vault. Place the file in the Excalidraw/Palettes folder (you can change this folder in settings).
![|400](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-sketch-palette-loader-3.jpg)
![|400](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-sketch-palette-loader-4.jpg)
```javascript
Excalidraw appState Custom Palette Data Object:
```js
colorPalette: {
canvasBackground: [string, string, string, string, string][] | string[],
elementBackground: [string, string, string, string, string][] | string[],
elementStroke: [string, string, string, string, string][] | string[],
topPicks: {
canvasBackground: [string, string, string, string, string],
elementStroke: [string, string, string, string, string],
elementBackground: [string, string, string, string, string]
},
}
*/
//--------------------------
// Load settings
//--------------------------
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.7.19")) {
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.9.2")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
@@ -51,138 +74,256 @@ if(paletteFolder === "" || paletteFolder === "/") {
if(!paletteFolder.endsWith("/")) paletteFolder += "/";
//--------------------------
// Select palette
//--------------------------
const palettes = app.vault.getFiles()
.filter(f=>f.extension === "md" && f.path.toLowerCase() === paletteFolder + f.name.toLowerCase())
.sort((a,b)=>a.basename.toLowerCase()<b.basename.toLowerCase()?-1:1);
const file = await utils.suggester(["Excalidraw Default"].concat(palettes.map(f=>f.name)),["Default"].concat(palettes), "Choose a palette, press ESC to abort");
if(!file) return;
if(file === "Default") {
api.updateScene({
appState: {
colorPalette: {}
//-----------------------
// UPDATE CustomPalette
//-----------------------
const updateColorPalette = (paletteFragment) => {
const st = ea.getExcalidrawAPI().getAppState();
colorPalette = st.colorPalette ?? {};
if(paletteFragment?.topPicks) {
if(!colorPalette.topPicks) {
colorPalette.topPicks = {
...paletteFragment.topPicks
};
} else {
colorPalette.topPicks = {
...colorPalette.topPicks,
...paletteFragment.topPicks
}
}
});
return;
} else {
colorPalette = {
...colorPalette,
...paletteFragment
}
}
ea.viewUpdateScene({appState: {colorPalette}});
ea.addElementsToView(true,true); //elements is empty, but this will save the file
}
//--------------------------
// Load palette
//--------------------------
const sketchPalette = await app.vault.read(file);
const parseJSON = (data) => {
try {
return JSON.parse(data);
} catch(e) {
//----------------
// LOAD PALETTE
//----------------
const loadPalette = async () => {
//--------------------------
// Select palette
//--------------------------
const palettes = app.vault.getFiles()
.filter(f=>f.extension === "md" && f.path.toLowerCase() === paletteFolder + f.name.toLowerCase())
.sort((a,b)=>a.basename.toLowerCase()<b.basename.toLowerCase()?-1:1);
const file = await utils.suggester(["Excalidraw Default"].concat(palettes.map(f=>f.name)),["Default"].concat(palettes), "Choose a palette, press ESC to abort");
if(!file) return;
if(file === "Default") {
api.updateScene({
appState: {
colorPalette: {}
}
});
return;
}
//--------------------------
// Load palette
//--------------------------
const sketchPalette = await app.vault.read(file);
const parseJSON = (data) => {
try {
return JSON.parse(data);
} catch(e) {
return;
}
}
const loadPaletteFromPlainText = (data) => {
const colors = [];
data.replaceAll("\r","").split("\n").forEach(c=>{
c = c.trim();
if(c==="") return;
if(c.match(/[^hslrga-fA-F\(\d\.\,\%\s)#]/)) return;
const cm = ea.getCM(c);
if(cm) colors.push(cm.stringHEX({alpha: false}));
})
return colors;
}
const paletteJSON = parseJSON(sketchPalette);
const colors = paletteJSON
? paletteJSON.colors.map(c=>ea.getCM({r:c.red*255,g:c.green*255,b:c.blue*255,a:c.alpha}).stringHEX({alpha: false}))
: loadPaletteFromPlainText(sketchPalette);
const baseColor = ea.getCM(colors[0]);
// Add black, white, transparent, gary
const palette = [[
"transparent",
"black",
baseColor.mix({color: lightGray, ratio:0.95}).stringHEX({alpha: false}),
baseColor.mix({color: darkGray, ratio:0.95}).stringHEX({alpha: false}),
"white"
]];
// Create Excalidraw palette
for(i=0;i<Math.floor(colors.length/5);i++) {
palette.push([
colors[i*5+1],
colors[i*5+2],
colors[i*5],
colors[i*5+3],
colors[i*5+4]
]);
}
const getShades = (c,type) => {
cm = ea.getCM(c);
const lightness = cm.lightness;
if(lightness === 0 || lightness === 100) return c;
switch(type) {
case "canvas":
return [
c,
ea.getCM(c).lightnessTo((100-lightness)*0.5+lightness).stringHEX({alpha: false}),
ea.getCM(c).lightnessTo((100-lightness)*0.25+lightness).stringHEX({alpha: false}),
ea.getCM(c).lightnessTo(lightness*0.5).stringHEX({alpha: false}),
ea.getCM(c).lightnessTo(lightness*0.25).stringHEX({alpha: false}),
];
case "stroke":
return [
ea.getCM(c).lightnessTo((100-lightness)*0.5+lightness).stringHEX({alpha: false}),
ea.getCM(c).lightnessTo((100-lightness)*0.25+lightness).stringHEX({alpha: false}),
ea.getCM(c).lightnessTo(lightness*0.5).stringHEX({alpha: false}),
ea.getCM(c).lightnessTo(lightness*0.25).stringHEX({alpha: false}),
c,
];
case "background":
return [
ea.getCM(c).lightnessTo((100-lightness)*0.5+lightness).stringHEX({alpha: false}),
c,
ea.getCM(c).lightnessTo((100-lightness)*0.25+lightness).stringHEX({alpha: false}),
ea.getCM(c).lightnessTo(lightness*0.5).stringHEX({alpha: false}),
ea.getCM(c).lightnessTo(lightness*0.25).stringHEX({alpha: false}),
];
}
}
const paletteSize = palette.flat().length;
const newPalette = {
canvasBackground: palette.flat().map(c=>getShades(c,"canvas")),
elementStroke: palette.flat().map(c=>getShades(c,"stroke")),
elementBackground: palette.flat().map(c=>getShades(c,"background"))
};
//--------------------------
// Check if palette has the same size as the current. Is re-paint possible?
//--------------------------
const oldPalette = api.getAppState().colorPalette;
//You can only switch and repaint equal size palettes
let canRepaint = Boolean(oldPalette) && Object.keys(oldPalette).length === 3 &&
oldPalette.canvasBackground.length === paletteSize &&
oldPalette.elementBackground.length === paletteSize &&
oldPalette.elementStroke.length === paletteSize;
//Check that the palette for canvas background, element stroke and element background are the same
for(i=0;canRepaint && i<paletteSize;i++) {
if(
oldPalette.canvasBackground[i] !== oldPalette.elementBackground[i] ||
oldPalette.canvasBackground[i] !== oldPalette.elementStroke[i]
) {
canRepaint = false;
break;
}
}
const shouldRepaint = canRepaint && await utils.suggester(["Try repainting the drawing with the new palette","Just load the new palette"], [true, false],"ESC will load the palette without repainting");
//--------------------------
// Apply palette
//--------------------------
if(shouldRepaint) {
const map = new Map();
for(i=0;i<paletteSize;i++) {
map.set(oldPalette.canvasBackground[i],newPalette.canvasBackground[i])
}
ea.copyViewElementsToEAforEditing(ea.getViewElements());
ea.getElements().forEach(el=>{
el.strokeColor = map.get(el.strokeColor)??el.strokeColor;
el.backgroundColor = map.get(el.backgroundColor)??el.backgroundColor;
})
const canvasColor = api.getAppState().viewBackgroundColor;
await api.updateScene({
appState: {
viewBackgroundColor: map.get(canvasColor)??canvasColor
}
});
ea.addElementsToView();
}
updateColorPalette(newPalette);
}
//-------------
// TOP PICKS
//-------------
const topPicks = async () => {
const elements = ea.getViewSelectedElements().filter(el=>["rectangle", "diamond", "ellipse", "line"].includes(el.type));
if(elements.length !== 5) {
new Notice("Select 5 elements, the script will use the background color of these elements",6000);
return;
}
const colorType = await utils.suggester(["View Background", "Element Background", "Stroke"],["view", "background", "stroke"], "Which top-picks would you like to set?");
if(!colorType) {
new Notice("You did not select which color to set");
return;
}
const topPicks = elements.map(el=>el.backgroundColor);
switch(colorType) {
case "view": updateColorPalette({topPicks: {canvasBackground: topPicks}}); break;
case "stroke": updateColorPalette({topPicks: {elementStroke: topPicks}}); break;
default: updateColorPalette({topPicks: {elementBackground: topPicks}}); break;
}
}
//-----------------------------------
// Copy palette from another file
//-----------------------------------
const copyPaletteFromFile = async () => {
const files = app.vault.getFiles().filter(f => ea.isExcalidrawFile(f)).sort((a,b)=>a.name > b.name ? 1 : -1);
const file = await utils.suggester(files.map(f=>f.path),files,"Select the file to copy from");
if(!file) {
return;
}
scene = await ea.getSceneFromFile(file);
if(!scene || !scene.appState) {
new Notice("unknown error");
return;
}
ea.viewUpdateScene({appState: {colorPalette: {...scene.appState.colorPalette}}});
ea.addElementsToView(true,true);
}
const loadPaletteFromPlainText = (data) => {
const colors = [];
data.replaceAll("\r","").split("\n").forEach(c=>{
c = c.trim();
if(c==="") return;
if(c.match(/[^hslrga-fA-F\(\d\.\,\%\s)#]/)) return;
const cm = ea.getCM(c);
if(cm) colors.push(cm.stringHEX({alpha: false}));
})
return colors;
}
//----------
// START
//----------
const action = await utils.suggester(
["Load palette from file", "Set top-picks based on the background color of 5 selected elements", "Copy palette from another Excalidraw File"],
["palette","top-picks","copy"]
);
if(!action) return;
const paletteJSON = parseJSON(sketchPalette);
const colors = paletteJSON
? paletteJSON.colors.map(c=>ea.getCM({r:c.red*255,g:c.green*255,b:c.blue*255,a:c.alpha}).stringHEX({alpha: false}))
: loadPaletteFromPlainText(sketchPalette);
const baseColor = ea.getCM(colors[0]);
// Add black, white, transparent, gary
const palette = [[
"transparent",
"black",
baseColor.mix({color: lightGray, ratio:0.95}).stringHEX({alpha: false}),
baseColor.mix({color: darkGray, ratio:0.95}).stringHEX({alpha: false}),
"white"
]];
// Create Excalidraw palette
for(i=0;i<Math.floor(colors.length/5);i++) {
palette.push([
colors[i*5+1],
colors[i*5+2],
colors[i*5],
colors[i*5+3],
colors[i*5+4]
]);
}
const paletteSize = palette.flat().length;
const newPalette = {
canvasBackground: palette.flat(),
elementStroke: palette.flat(),
elementBackground: palette.flat()
};
//--------------------------
// Check if palette has the same size as the current. Is re-paint possible?
//--------------------------
const oldPalette = api.getAppState().colorPalette;
//You can only switch and repaint equal size palettes
let canRepaint = Object.keys(oldPalette).length === 3 &&
oldPalette.canvasBackground.length === paletteSize &&
oldPalette.elementBackground.length === paletteSize &&
oldPalette.elementStroke.length === paletteSize;
//Check that the palette for canvas background, element stroke and element background are the same
for(i=0;canRepaint && i<paletteSize;i++) {
if(
oldPalette.canvasBackground[i] !== oldPalette.elementBackground[i] ||
oldPalette.canvasBackground[i] !== oldPalette.elementStroke[i]
) {
canRepaint = false;
break;
}
}
const shouldRepaint = canRepaint && await utils.suggester(["Try repainting the drawing with the new palette","Just load the new palette"], [true, false],"ESC will load the palette without repainting");
//--------------------------
// Apply palette
//--------------------------
if(shouldRepaint) {
const map = new Map();
for(i=0;i<paletteSize;i++) {
map.set(oldPalette.canvasBackground[i],newPalette.canvasBackground[i])
}
ea.copyViewElementsToEAforEditing(ea.getViewElements());
ea.getElements().forEach(el=>{
el.strokeColor = map.get(el.strokeColor)??el.strokeColor;
el.backgroundColor = map.get(el.backgroundColor)??el.backgroundColor;
})
const canvasColor = api.getAppState().viewBackgroundColor;
await api.updateScene({
appState: {
colorPalette: newPalette,
viewBackgroundColor: map.get(canvasColor)??canvasColor
}
});
ea.addElementsToView();
} else {
api.updateScene({
appState: {
colorPalette: newPalette
}
});
}
switch(action) {
case "palette": loadPalette(); break;
case "top-picks": topPicks(); break;
case "copy": copyPaletteFromFile(); break;
}

View File

@@ -33,6 +33,7 @@ Open the script you are interested in and save it to your Obsidian Vault includi
|[Add Link to Existing File and Open](Add%20Link%20to%20Existing%20File%20and%20Open.md)|Prompts for a file from the vault. Adds a link to the selected element pointing to the selected file. You can control in settings to open the file in the current active pane or an adjacent pane.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-link-and-open.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Add Link to New Page and Open](Add%20Link%20and%20Open%20Page.md)|Prompts for filename. Offers option to create and open a new Markdown or Excalidraw document. Adds link pointing to the new file, to the selected objects in the drawing. You can control in settings to open the file in the current active pane or an adjacent pane.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-link-to-new-page-and-pen.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Add Next Step in Process](Add%20Link%20to%20New%20Page%20and%20Open.md)|This script will prompt you for the title of the process step, then will create a stick note with the text. If an element is selected then the script will connect this new step with an arrow to the previous step (the selected element). If no element is selected, then the script assumes this is the first step in the process and will only output the sticky note with the text that was entered.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-process-step.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Split Ellipse](Boolean%20Operations.md)|With This Script it is possible to make boolean Operations on Shapes.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-boolean-operations-showcase.png)|[@GColoy](https://github.com/GColoy)|
|[Box Each Selected Groups](Box%20Each%20Selected%20Groups.md)|This script will add encapsulating boxes around each of the currently selected groups in Excalidraw.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-box-each-selected-groups.png)|[@1-2-3](https://github.com/1-2-3)|
|[Box Selected Elements](Box%20Selected%20Elements.md)|This script will add an encapsulating box around the currently selected elements in Excalidraw.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-box-elements.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Change shape of selected elements](Change%20shape%20of%20selected%20elements.md)|The script allows you to change the shape of selected Rectangles, Diamonds and Ellipses|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-change-shape.jpg)|[@zsviczian](https://github.com/zsviczian)|
@@ -57,7 +58,7 @@ Open the script you are interested in and save it to your Obsidian Vault includi
|[Mindmap connector](Mindmap%20connector.md)|This script creates mindmap like lines (only right side and down available currently) for selected elements. The line will start according to the creation time of the elements. So you should create the header element first.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/mindmap%20connector.png)|[@xllowl](https://github.com/xllowl)|
|[Modify background color opacity](Modify%20background%20color%20opacity.md)|This script changes the opacity of the background color of the selected boxes. The default background color in Excalidraw is so dark that the text is hard to read. You can lighten the color a bit by setting transparency. And you can tweak the transparency over and over again until you're happy with it. Although excalidraw has the opacity option in its native property Settings, it also changes the transparency of the border. Use this script to change only the opacity of the background color without affecting the border.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-modify-background-color-opacity.png)|[@1-2-3](https://github.com/1-2-3)|
|[Normalize Selected Arrows](Normalize%20Selected%20Arrows.md)|This script will reset the start and end positions of the selected arrows. The arrow will point to the center of the connected box and will have a gap of 8px from the box.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-normalize-selected-arrows.png)|[@1-2-3](https://github.com/1-2-3)|
|[OCR - Optical Character Recognition](OCR%20-%20Optical%20Character%20Recognition.md)|The script will 1) send the selected image file to [taskbone.com](https://taskbone.com) to exctract the text from the image, and 2) will add the text to your drawing as a text element.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-ocr.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[OCR - Optical Character Recognition](OCR%20-%20Optical%20Character%20Recognition.md)|The script will 1) send the selected image file to [taskbone.com](https://taskbone.com) to extract the text from the image, and 2) will add the text to your drawing as a text element.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-ocr.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Organic Line](Organic%20Line.md)|Converts selected freedraw lines such that pencil pressure will decrease from maximum to minimum from the beginning of the line to its end. The resulting line is placed at the back of the layers, under all other items. Helpful when drawing organic mindmaps.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-organic-line.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Repeat Elements](Repeat%20Elements.md)|This script will detect the difference between 2 selected elements, including position, size, angle, stroke and background color, and create several elements that repeat these differences based on the number of repetitions entered by the user.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-repeat-elements.png)|[@1-2-3](https://github.com/1-2-3)|
|[Reverse arrows](Reverse%20arrows.md)|Reverse the direction of **arrows** within the scope of selected elements.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-reverse-arrow.jpg)|[@zsviczian](https://github.com/zsviczian)|
@@ -71,7 +72,11 @@ Open the script you are interested in and save it to your Obsidian Vault includi
|[Set stroke width of selected elements](Set%20Stroke%20Width%20of%20Selected%20Elements.md)|This script will set the stroke width of selected elements. This is helpful, for example, when you scale freedraw sketches and want to reduce or increase their line width.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-stroke-width.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Split text by lines](Split%20text%20by%20lines.md)|Split lines of text into separate text elements for easier reorganization|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-split-lines.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Set Text Alignment](Set%20Text%20Alignment.md)|Sets text alignment of text block (cetner, right, left). Useful if you want to set a keyboard shortcut for selecting text alignment.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-text-align.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Split Ellipse](Split%20Ellipse.md)|This script splits an ellipse at any point where a line intersects it.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-splitEllipse-demo1.png)|[@GColoy](https://github.com/GColoy)|
|[TheBrain-navigation](TheBrain-navigation.md)|An Excalidraw based graph user interface for your Vault. Requires the [Dataview plugin](https://github.com/blacksmithgu/obsidian-dataview). Generates a graph view similar to that of [TheBrain](https://TheBrain.com) plex. Watch introduction to this script on [YouTube](https://youtu.be/plYobK-VufM).|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/TheBrain.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Toggle Fullscreen on Mobile](Toggle%20Fullscreen%20on%20Mobile.md)|Hides Obsidian workspace leaf padding and header (based on option in settings, default is "hide header" = false) which will take Excalidraw to full screen. ⚠ Note that if the header is not visible, it will be very difficult to invoke the command palette to end full screen. Only hide the header if you have a keyboard or you've practiced opening command palette!|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/ea-toggle-fullscreen.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Toggle Grid](Toggle%20Grid.md)|Toggles the grid.||[@GColoy](https://github.com/GColoy)|
|[Transfer TextElements to Excalidraw markdown metadata](Transfer%20TextElements%20to%20Excalidraw%20markdown%20metadata.md)|The script will delete the selected text elements from the canvas and will copy the text from these text elements into the Excalidraw markdown file as metadata. This means, that the text will no longer be visible in the drawing, however you will be able to search for the text in Obsidian and find the drawing containing this image.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-text-to-metadata.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Zoom to Fit Selected Elements](Zoom%20to%20Fit%20Selected%20Elements.md)|Similar to Excalidraw standard <kbd>SHIFT+2</kbd> feature: Zoom to fit selected elements, but with the ability to zoom to 1000%. Inspiration: [#272](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/272)||[@zsviczian](https://github.com/zsviczian)|
|[Hardware Eraser Suppoer](Hardware%20Eraser%20Support.md)|Allows the use of pen inversion/hardware erasers on supported pens.|[@threethan](https://github.com/threethan)|
|[Hardware Eraser Suppoer](Auto%20Draw%20for%20Pen.md)|Automatically switched from the Select tool to the Draw tool when a pen is hovered, and then back.|[@threethan](https://github.com/threethan)|

View File

@@ -0,0 +1,40 @@
/*
The script will cycle through S, M, L, XL font sizes scaled to the current canvas zoom.
```js*/
const FONTSIZES = [16, 20, 28, 36];
const api = ea.getExcalidrawAPI();
const st = api.getAppState();
const zoom = st.zoom.value;
const currentItemFontSize = st.currentItemFontSize;
const fontsizes = FONTSIZES.map(s=>s/zoom);
const els = ea.getViewSelectedElements().filter(el=>el.type === "text");
const findClosestIndex = (val, list) => {
let closestIndex = 0;
let closestDifference = Math.abs(list[0] - val);
for (let i = 1; i < list.length; i++) {
const difference = Math.abs(list[i] - val);
if (difference <= closestDifference) {
closestDifference = difference;
closestIndex = i;
}
}
return closestIndex;
}
ea.viewUpdateScene({appState:{currentItemFontSize: fontsizes[(findClosestIndex(currentItemFontSize, fontsizes)+1) % fontsizes.length] }});
if(els.length>0) {
ea.copyViewElementsToEAforEditing(els);
ea.getElements().forEach(el=> {
el.fontSize = fontsizes[(findClosestIndex(el.fontSize, fontsizes)+1) % fontsizes.length];
const font = ExcalidrawLib.getFontString(el);
const lineHeight = ExcalidrawLib.getDefaultLineHeight(el.fontFamily);
const {width, height, baseline} = ExcalidrawLib.measureText(el.originalText, font, lineHeight);
el.width = width;
el.height = height;
el.baseline = baseline;
});
ea.addElementsToView();
}

View File

@@ -0,0 +1,4 @@
<svg viewBox="0 0 50 30" xmlns="http://www.w3.org/2000/svg">
<text fill="currentColor" x="10" y="30" font-size="16px" font-weight="light">A</text>
<text fill="currentColor" x="22" y="30" font-size="36px" font-weight="light">A</text>
</svg>

After

Width:  |  Height:  |  Size: 243 B

View File

@@ -0,0 +1,96 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-repeat-texts.png)
In the following script, we address the concept of repetition through the lens of numerical progression. As visualized by the image, where multiple circles each labeled with an even task number are being condensed into a linear sequence, our script will similarly iterate through a set of numbers.
Inspired from [Repeat Elements](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Repeat%20Elements.md)
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.7.19")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
let repeatNum = parseInt(await utils.inputPrompt("repeat times?","number","5"));
if(!repeatNum) {
new Notice("Please enter a number.");
return;
}
const selectedElements = ea.getViewSelectedElements().sort((lha,rha) => lha.x === rha.x ? lha.y - rha.y : lha.x - rha.x);
const selectedBounds = selectedElements.filter(e => e.type !== "text");
const selectedTexts = selectedElements.filter(e => e.type === "text");
const selectedTextsById = selectedTexts.reduce((prev, next) => (prev[next.id] = next, prev), {})
if(selectedTexts.length !== 2 || ![0, 2].includes(selectedBounds.length)) {
new Notice("Please select only 2 text elements.");
return;
}
if(selectedBounds.length === 2) {
if(selectedBounds[0].type !== selectedBounds[1].type) {
new Notice("The selected elements must be of the same type.");
return;
}
if (!selectedBounds.every(e => e.boundElements?.length === 1)) {
new Notice("Only support the bound element with 1 text element.");
return;
}
if (!selectedBounds.every(e => !!selectedTextsById[e.boundElements?.[0]?.id])) {
new Notice("Bound element must refer to the text element.");
return;
}
}
const prevBoundEl = selectedBounds.length ? selectedBounds[0] : selectedTexts[0];
const nextBoundEl = selectedBounds.length ? selectedBounds[1] : selectedTexts[1];
const prevTextEl = prevBoundEl.type === 'text' ? prevBoundEl : selectedTextsById[prevBoundEl.boundElements[0].id]
const nextTextEl = nextBoundEl.type === 'text' ? nextBoundEl : selectedTextsById[nextBoundEl.boundElements[0].id]
const xDistance = nextBoundEl.x - prevBoundEl.x;
const yDistance = nextBoundEl.y - prevBoundEl.y;
const numReg = /\d+/
let textNumDiff
try {
const num0 = +prevTextEl.text.match(numReg)
const num1 = +nextTextEl.text.match(numReg)
textNumDiff = num1 - num0
} catch(e) {
new Notice("Text must include a number!")
return;
}
const repeatEl = (newEl, step) => {
ea.elementsDict[newEl.id] = newEl;
newEl.x += xDistance * (step + 1);
newEl.y += yDistance * (step + 1);
if(newEl.text) {
const text = newEl.text.replace(numReg, (match) => +match + (step + 1) * textNumDiff)
newEl.originalText = text
newEl.rawText = text
newEl.text = text
}
}
ea.copyViewElementsToEAforEditing(selectedBounds);
for(let i=0; i<repeatNum; i++) {
const newTextEl = ea.cloneElement(nextTextEl);
repeatEl(newTextEl, i)
if (selectedBounds.length) {
const newBoundEl = ea.cloneElement(selectedBounds[1]);
newBoundEl.boundElements[0].id = newTextEl.id
newTextEl.containerId = newBoundEl.id
repeatEl(newBoundEl, i)
}
}
await ea.addElementsToView(false, false, true);

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Svg Vector Icons : http://www.onlinewebfonts.com/icon -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 256 256" enable-background="new 0 0 256 256" xml:space="preserve">
<metadata> Svg Vector Icons : http://www.onlinewebfonts.com/icon </metadata>
<g><g><g><path fill="#000000" d="M101.2,18.7c-2,0.9-38,36.6-39.2,39c-1,1.9-1.1,4.2-0.4,6.2c0.6,1.5,37.3,38.6,39.3,39.6c1.7,0.9,5.2,0.8,7.1-0.1c1.9-0.9,3.7-3.5,4.1-5.8c0.6-3.5-0.2-4.5-12.5-16.8L88.2,69.2h44.3c38.7,0,44.8,0.1,48.6,0.7c24.2,4.2,43.2,22.6,48.1,46.8c3.3,16.4-1,34.2-11.5,47.6c-3.5,4.6-4.1,6.9-2.4,10.4c1.8,3.7,6.6,5.3,10.4,3.5c2.8-1.3,8.7-9.4,12.5-17.2c10.5-20.8,10.5-45.2,0-66.2c-10.3-20.6-28.6-34.8-51.8-40c-4.6-1.1-4.8-1.1-51.3-1.2c-25.7-0.1-46.7-0.3-46.7-0.5s5.2-5.5,11.5-11.9c9.4-9.4,11.6-11.9,12-13.3C113.6,21.6,107.3,16.1,101.2,18.7z"/><path fill="#000000" d="M34.9,73.4c-1.8,0.5-5.8,4.4-9.7,9.6c-7.9,10.4-12.8,22.4-14.5,35.4c-5.3,40.1,22.9,77.3,63.1,83.3c4,0.6,11.3,0.7,45.4,0.7c24,0,40.6,0.2,40.6,0.4c0,0.2-5,5.3-11.1,11.1c-11.3,11-12.9,13-12.9,16c0,4.1,3.8,7.9,7.9,7.9c0.7,0,2.1-0.3,3.1-0.7c2.3-1,38.1-36.6,39.3-39.2c1-2.1,1.1-4.2,0.4-6.1c-0.6-1.5-37.3-38.6-39.3-39.7c-1.9-1-6.1-0.6-8.1,0.8c-2.9,2-4,6.4-2.5,9.4c0.4,0.8,5.9,6.6,12.1,12.9l11.4,11.4h-40.4c-35,0-40.9-0.1-44.7-0.7c-24.2-4.1-43.4-22.9-48.2-47.1c-1.1-5.5-1.1-16.4,0-21.9c2.1-10.7,7-20.2,14.6-28.7c2.1-2.3,3.5-4.3,3.8-5.4c1-3.6-0.7-7.3-4.2-9C38.7,72.8,37.4,72.7,34.9,73.4z"/></g></g></g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,29 +1,388 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-scribble-helper.jpg)
iOS scribble helper for better handwriting experience with text elements. If no elements are selected then the script creates a text element at the pointer position and you can use the edit box to modify the text with scribble. If a text element is selected then the script opens the input prompt where you can modify this text with scribble.
Scribble Helper can improve handwriting and add links. It lets you create and edit text elements, including wrapped text and sticky notes, by double-tapping on the canvas. When you run the script, it creates an event handler that will activate the editor when you double-tap. If you select a text element on the canvas before running the script, it will open the editor for that element. If you use a pen, you can set it up to only activate Scribble Helper when you double-tap with the pen. The event handler is removed when you run the script a second time or switch to a different tab.
<iframe width="560" height="315" src="https://www.youtube.com/embed/BvYkOaly-QM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
```javascript
*/
elements = ea.getViewSelectedElements().filter(el=>el.type==="text");
if(elements.length > 1) {
new Notice ("Select only 1 or 0 text elements.")
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.8.25")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
const text = await utils.inputPrompt("Edit text","",(elements.length === 1)?elements[0].rawText:"");
if(!text) return;
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;
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
if(elements.length === 1) {
ea.copyViewElementsToEAforEditing(elements);
ea.getElements()[0].originalText = text;
ea.getElements()[0].text = text;
ea.getElements()[0].rawText = text;
// -------------
// 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;
}
//---------------------------------------
// Color Palette for stroke color setting
//---------------------------------------
// https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/1.6.8
const defaultStrokeColors = [
"#000000", "#343a40", "#495057", "#c92a2a", "#a61e4d",
"#862e9c", "#5f3dc4", "#364fc7", "#1864ab", "#0b7285",
"#087f5b", "#2b8a3e", "#5c940d", "#e67700", "#d9480f"
];
const loadColorPalette = () => {
const st = api.getAppState();
const strokeColors = new Set();
let strokeColorPalette = st.colorPalette?.elementStroke ?? defaultStrokeColors;
if(Object.entries(strokeColorPalette).length === 0) {
strokeColorPalette = defaultStrokeColors;
}
ea.getViewElements().forEach(el => {
if(el.strokeColor.toLowerCase()==="transparent") return;
strokeColors.add(el.strokeColor);
});
strokeColorPalette.forEach(color => {
strokeColors.add(color)
});
strokeColors.add(st.currentItemStrokeColor ?? ea.style.strokeColor);
return strokeColors;
}
//----------------------------------------------------------
// Define variables to cache element location on first click
//----------------------------------------------------------
// if a single element is selected when the action is started, update that existing text
let containerElements = ea.getViewSelectedElements()
.filter(el=>["arrow","rectangle","ellipse","line","diamond"].contains(el.type));
let selectedTextElements = ea.getViewSelectedElements().filter(el=>el.type==="text");
//-------------------------------------------
// Functions to add and remove event listners
//-------------------------------------------
const addEventHandler = (handler) => {
if(win.ExcalidrawScribbleHelper.eventHandler) {
win.removeEventListner("pointerdown", handler);
}
win.addEventListener("pointerdown",handler);
win.ExcalidrawScribbleHelper.eventHandler = handler;
win.ExcalidrawScribbleHelper.window = win;
}
const removeEventHandler = (handler) => {
win.removeEventListener("pointerdown",handler);
delete win.ExcalidrawScribbleHelper.eventHandler;
delete win.ExcalidrawScribbleHelper.window;
}
//Stop the script if scribble helper is clicked and no eligable element is selected
let silent = false;
if (win.ExcalidrawScribbleHelper?.eventHandler) {
removeEventHandler(win.ExcalidrawScribbleHelper.eventHandler);
delete win.ExcalidrawScribbleHelper.eventHandler;
delete win.ExcalidrawScribbleHelper.window;
if(!(containerElements.length === 1 || selectedTextElements.length === 1)) {
new Notice ("Scribble Helper was stopped",1000);
return;
}
silent = true;
}
// ----------------------
// Custom dialog controls
// ----------------------
if (typeof win.ExcalidrawScribbleHelper.penOnly === "undefined") {
win.ExcalidrawScribbleHelper.penOnly = undefined;
}
if (typeof win.ExcalidrawScribbleHelper.penDetected === "undefined") {
win.ExcalidrawScribbleHelper.penDetected = false;
}
let timer = Date.now();
let eventHandler = () => {};
const customControls = (container) => {
const helpDIV = container.createDiv();
helpDIV.innerHTML = `<a href="${helpLINK}" target="_blank">Click here for help</a>`;
const viewBackground = api.getAppState().viewBackgroundColor;
const el1 = new ea.obsidian.Setting(container)
.setName(`Text color`)
.addDropdown(dropdown => {
Array.from(loadColorPalette()).forEach(color => {
const options = dropdown.addOption(color, color).selectEl.options;
options[options.length-1].setAttribute("style",`color: ${color
}; background: ${viewBackground};`);
});
dropdown
.setValue(ea.style.strokeColor)
.onChange(value => {
ea.style.strokeColor = value;
el1.nameEl.style.color = value;
})
})
el1.nameEl.style.color = ea.style.strokeColor;
el1.nameEl.style.background = viewBackground;
el1.nameEl.style.fontWeight = "bold";
const el2 = new ea.obsidian.Setting(container)
.setName(`Trigger editor by pen double tap only`)
.addToggle((toggle) => toggle
.setValue(win.ExcalidrawScribbleHelper.penOnly)
.onChange(value => {
win.ExcalidrawScribbleHelper.penOnly = value;
})
)
el2.settingEl.style.border = "none";
el2.settingEl.style.display = win.ExcalidrawScribbleHelper.penDetected ? "" : "none";
}
// -------------------------------
// Click / dbl click event handler
// -------------------------------
eventHandler = async (evt) => {
if(windowOpen) return;
if(ea.targetView !== app.workspace.activeLeaf.view) removeEventHandler(eventHandler);
if(evt && evt.target && !evt.target.hasClass("excalidraw__canvas")) return;
if(evt && (evt.ctrlKey || evt.altKey || evt.metaKey || evt.shiftKey)) return;
const st = api.getAppState();
win.ExcalidrawScribbleHelper.penDetected = st.penDetected;
//don't trigger text editor when editing a line or arrow
if(st.editingElement && ["arrow","line"].contains(st.editingElment.type)) return;
if(typeof win.ExcalidrawScribbleHelper.penOnly === "undefined") {
win.ExcalidrawScribbleHelper.penOnly = false;
}
if (evt && win.ExcalidrawScribbleHelper.penOnly &&
win.ExcalidrawScribbleHelper.penDetected && evt.pointerType !== "pen") return;
const now = Date.now();
//the <50 condition is to avoid false double click when pinch zooming
if((now-timer > DBLCLICKTIMEOUT) || (now-timer < 50)) {
prevZoomValue = st.zoom.value;
timer = now;
containerElements = ea.getViewSelectedElements()
.filter(el=>["arrow","rectangle","ellipse","line","diamond"].contains(el.type));
selectedTextElements = ea.getViewSelectedElements().filter(el=>el.type==="text");
return;
}
//further safeguard against triggering when pinch zooming
if(st.zoom.value !== prevZoomValue) return;
//sleeping to allow keyboard to pop up on mobile devices
await sleep(200);
ea.clear();
//if a single element with text is selected, edit the text
//(this can be an arrow, a sticky note, or just a text element)
if(selectedTextElements.length === 1) {
editExistingTextElement(selectedTextElements);
return;
}
let containerID;
let container;
//if no text elements are selected (i.e. not multiple text elements selected),
//check if there is a single eligeable container selected
if(selectedTextElements.length === 0) {
if(containerElements.length === 1) {
ea.copyViewElementsToEAforEditing(containerElements);
containerID = containerElements[0].id
container = ea.getElement(containerID);
}
}
const {x,y} = ea.targetView.currentPosition;
if(ea.targetView !== app.workspace.activeLeaf.view) return;
const actionButtons = [
{
caption: `A`,
tooltip: "Add as Text Element",
action: () => {
win.ExcalidrawScribbleHelper.action="Text";
if(settings["Default action"].value!=="Text") {
settings["Default action"].value = "Text";
ea.setScriptSettings(settings);
};
return;
}
},
{
caption: "📝",
tooltip: "Add as Sticky Note (rectangle with border color and background color)",
action: () => {
win.ExcalidrawScribbleHelper.action="Sticky";
if(settings["Default action"].value!=="Sticky") {
settings["Default action"].value = "Sticky";
ea.setScriptSettings(settings);
};
return;
}
},
{
caption: "☱",
tooltip: "Add as Wrapped Text (rectangle with transparent border and background)",
action: () => {
win.ExcalidrawScribbleHelper.action="Wrap";
if(settings["Default action"].value!=="Wrap") {
settings["Default action"].value = "Wrap";
ea.setScriptSettings(settings);
};
return;
}
}
];
if(win.ExcalidrawScribbleHelper.action !== "Text") actionButtons.push(actionButtons.shift());
if(win.ExcalidrawScribbleHelper.action === "Wrap") actionButtons.push(actionButtons.shift());
ea.style.strokeColor = st.currentItemStrokeColor ?? ea.style.strokeColor;
ea.style.roughness = st.currentItemRoughness ?? ea.style.roughness;
ea.setStrokeSharpness(st.currentItemRoundness === "round" ? 0 : st.currentItemRoundness)
ea.style.backgroundColor = st.currentItemBackgroundColor ?? ea.style.backgroundColor;
ea.style.fillStyle = st.currentItemFillStyle ?? ea.style.fillStyle;
ea.style.fontFamily = st.currentItemFontFamily ?? ea.style.fontFamily;
ea.style.fontSize = st.currentItemFontSize ?? ea.style.fontSize;
ea.style.textAlign = (container && ["arrow","line"].contains(container.type))
? "center"
: (container && ["rectangle","diamond","ellipse"].contains(container.type))
? "center"
: st.currentItemTextAlign ?? "center";
ea.style.verticalAlign = "middle";
windowOpen = true;
const text = await utils.inputPrompt (
"Edit text", "", "", containerID?undefined:actionButtons, 5, true, customControls, true
);
windowOpen = false;
if(!text || text.trim() === "") return;
const textId = ea.addText(x,y, text);
if (!container && (win.ExcalidrawScribbleHelper.action === "Text")) {
ea.addElementsToView(false, false, true);
addEventHandler(eventHandler);
return;
}
const textEl = ea.getElement(textId);
if(!container && (win.ExcalidrawScribbleHelper.action === "Wrap")) {
ea.style.backgroundColor = "transparent";
ea.style.strokeColor = "transparent";
}
if(!container && (win.ExcalidrawScribbleHelper.action === "Sticky")) {
textEl.textAlign = "center";
}
const boxes = [];
if(container) {
boxes.push(containerID);
const linearElement = ["arrow","line"].contains(container.type);
const l = linearElement ? container.points.length-1 : 0;
const dx = linearElement && (container.points[l][0] < 0) ? -1 : 1;
const dy = linearElement && (container.points[l][1] < 0) ? -1 : 1;
cx = container.x + dx*container.width/2;
cy = container.y + dy*container.height/2;
textEl.x = cx - textEl.width/2;
textEl.y = cy - textEl.height/2;
}
if(!container) {
const width = textEl.width+2*padding;
const widthOK = width<=maxWidth;
containerID = ea.addRect(
textEl.x-padding,
textEl.y-padding,
widthOK ? width : maxWidth,
textEl.height + 2 * padding
);
container = ea.getElement(containerID);
}
boxes.push(containerID);
container.boundElements=[{type:"text",id: textId}];
textEl.containerId = containerID;
//ensuring the correct order of elements, first container, then text
delete ea.elementsDict[textEl.id];
ea.elementsDict[textEl.id] = textEl;
await ea.addElementsToView(false,false,true);
const containers = ea.getViewElements().filter(el=>boxes.includes(el.id));
if(["rectangle","diamond","ellipse"].includes(container.type)) api.updateContainerSize(containers);
ea.selectElementsInView(containers);
};
// ---------------------
// Edit Existing Element
// ---------------------
const editExistingTextElement = async (elements) => {
windowOpen = true;
ea.copyViewElementsToEAforEditing(elements);
const el = ea.getElements()[0];
ea.style.strokeColor = el.strokeColor;
const text = await utils.inputPrompt(
"Edit text","",elements[0].rawText,undefined,5,true,customControls,true
);
windowOpen = false;
if(!text) return;
el.strokeColor = ea.style.strokeColor;
el.originalText = text;
el.text = text;
el.rawText = text;
ea.refreshTextElementSize(el.id);
await ea.addElementsToView(false,false);
return;
if(el.containerId) {
const containers = ea.getViewElements().filter(e=>e.id === el.containerId);
api.updateContainerSize(containers);
ea.selectElementsInView(containers);
}
}
ea.addText(0,0,text);
await ea.addElementsToView(true, false, true);
//--------------
// Start actions
//--------------
if(!win.ExcalidrawScribbleHelper?.eventHandler) {
if(!silent) new Notice(
"To create a new text element,\ndouble-tap the screen.\n\n" +
"To edit text,\ndouble-tap an existing element.\n\n" +
"To stop the script,\ntap it again or switch to a different tab.",
5000
);
addEventHandler(eventHandler);
}
if(containerElements.length === 1 || selectedTextElements.length === 1) {
timer = timer - 100;
eventHandler();
}

View File

@@ -0,0 +1,206 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-select-similar-elements.png)
This script enables the selection of elements based on matching properties. Select the attributes (such as stroke color, fill style, font family, etc) that should match for selection. It's perfect for large scenes where manual selection of elements would be cumbersome. You can either run the script to select matching elements across the entire scene, or define a specific group of elements to apply the selection criteria to.
```js */
let config = window.ExcalidrawSelectConfig;
config = Boolean(config) && (Date.now() - config.timestamp < 60000) ? config : null;
let elements = ea.getViewSelectedElements();
if(!config && (elements.length !==1)) {
new Notice("Select a single element");
return;
} else {
if(elements.length === 0) {
elements = ea.getViewElements();
}
}
const {angle, backgroundColor, fillStyle, fontFamily, fontSize, height, width, opacity, roughness, roundness, strokeColor, strokeStyle, strokeWidth, type, startArrowhead, endArrowhead, fileId} = ea.getViewSelectedElement();
const fragWithHTML = (html) => createFragment((frag) => (frag.createDiv().innerHTML = html));
//--------------------------
// RUN
//--------------------------
const run = () => {
selectedElements = elements.filter(el=>
((typeof config.angle === "undefined") || (el.angle === config.angle)) &&
((typeof config.backgroundColor === "undefined") || (el.backgroundColor === config.backgroundColor)) &&
((typeof config.fillStyle === "undefined") || (el.fillStyle === config.fillStyle)) &&
((typeof config.fontFamily === "undefined") || (el.fontFamily === config.fontFamily)) &&
((typeof config.fontSize === "undefined") || (el.fontSize === config.fontSize)) &&
((typeof config.height === "undefined") || Math.abs(el.height - config.height) < 0.01) &&
((typeof config.width === "undefined") || Math.abs(el.width - config.width) < 0.01) &&
((typeof config.opacity === "undefined") || (el.opacity === config.opacity)) &&
((typeof config.roughness === "undefined") || (el.roughness === config.roughness)) &&
((typeof config.roundness === "undefined") || (el.roundness === config.roundness)) &&
((typeof config.strokeColor === "undefined") || (el.strokeColor === config.strokeColor)) &&
((typeof config.strokeStyle === "undefined") || (el.strokeStyle === config.strokeStyle)) &&
((typeof config.strokeWidth === "undefined") || (el.strokeWidth === config.strokeWidth)) &&
((typeof config.type === "undefined") || (el.type === config.type)) &&
((typeof config.startArrowhead === "undefined") || (el.startArrowhead === config.startArrowhead)) &&
((typeof config.endArrowhead === "undefined") || (el.endArrowhead === config.endArrowhead)) &&
((typeof config.fileId === "undefined") || (el.fileId === config.fileId))
)
ea.selectElementsInView(selectedElements);
delete window.ExcalidrawSelectConfig;
}
//--------------------------
// Modal
//--------------------------
const showInstructions = () => {
const instructionsModal = new ea.obsidian.Modal(app);
instructionsModal.onOpen = () => {
instructionsModal.contentEl.createEl("h2", {text: "Instructions"});
instructionsModal.contentEl.createEl("p", {text: "Step 1: Choose the attributes that you want the selected elements to match."});
instructionsModal.contentEl.createEl("p", {text: "Step 2: Select an action:"});
instructionsModal.contentEl.createEl("ul", {}, el => {
el.createEl("li", {text: "Click 'RUN' to find matching elements throughout the entire scene."});
el.createEl("li", {text: "Click 'SELECT' to 1) first choose a specific group of elements in the scene, then 2) run the 'Select Similar Elements' once more within 1 minute to apply the filter criteria only to that group of elements."});
});
instructionsModal.contentEl.createEl("p", {text: "Note: If you choose 'SELECT', make sure to click the 'Select Similar Elements' script again within 1 minute to apply your selection criteria to the group of elements you chose."});
};
instructionsModal.open();
};
const selectAttributesToCopy = () => {
const configModal = new ea.obsidian.Modal(app);
configModal.onOpen = () => {
config = {};
configModal.contentEl.createEl("h1", {text: "Select Similar Elements"});
new ea.obsidian.Setting(configModal.contentEl)
.setDesc("Choose the attributes you want the selected elements to match, then select an action.")
.addButton(button => button
.setButtonText("Instructions")
.onClick(showInstructions)
);
// Add Toggles for the rest of the attributes
let attributes = [
{name: "Element type", key: "type"},
{name: "Stroke color", key: "strokeColor"},
{name: "Background color", key: "backgroundColor"},
{name: "Opacity", key: "opacity"},
{name: "Fill style", key: "fillStyle"},
{name: "Stroke style", key: "strokeStyle"},
{name: "Stroke width", key: "strokeWidth"},
{name: "Roughness", key: "roughness"},
{name: "Roundness", key: "roundness"},
{name: "Font family", key: "fontFamily"},
{name: "Font size", key: "fontSize"},
{name: "Start arrowhead", key: "startArrowhead"},
{name: "End arrowhead", key: "endArrowhead"},
{name: "Height", key: "height"},
{name: "Width", key: "width"},
{name: "ImageID", key: "fileId"},
];
attributes.forEach(attr => {
const attrValue = elements[0][attr.key];
if(attrValue || (attr.key === "startArrowhead" && elements[0].type === "arrow") || (attr.key === "endArrowhead" && elements[0].type === "arrow")) {
let description = '';
switch(attr.key) {
case 'backgroundColor':
case 'strokeColor':
description = `<div style='background-color:${attrValue};'>${attrValue}</div>`;
break;
case 'roundness':
description = attrValue === null ? 'Sharp' : 'Round';
break;
case 'roughness':
description = attrValue === 0 ? 'Architect' : attrValue === 1 ? 'Artist' : 'Cartoonist';
break;
case 'strokeWidth':
description = attrValue <= 0.5 ? 'Extra thin' :
attrValue <= 1 ? 'Thin' :
attrValue <= 2 ? 'Bold' :
'Extra bold';
break;
case 'opacity':
description = `${attrValue}%`;
break;
case 'width':
case 'height':
description = `${attrValue.toFixed(2)}`;
break;
case 'startArrowhead':
case 'endArrowhead':
description = attrValue === null ? 'None' : `${attrValue.charAt(0).toUpperCase() + attrValue.slice(1)}`;
break;
case 'fontFamily':
description = attrValue === 1 ? 'Hand-drawn' :
attrValue === 2 ? 'Normal' :
attrValue === 3 ? 'Code' :
'Custom 4th font';
break;
case 'fontSize':
description = `${attrValue}`;
break;
default:
console.log(attr.key);
console.log(attrValue);
description = `${attrValue.charAt(0).toUpperCase() + attrValue.slice(1)}`;
break;
}
new ea.obsidian.Setting(configModal.contentEl)
.setName(`${attr.name}`)
.setDesc(fragWithHTML(`${description}`))
.addToggle(toggle => toggle
.setValue(false)
.onChange(value => {
if(value) {
config[attr.key] = attrValue;
} else {
delete config[attr.key];
}
})
)
}
});
//Add Toggle for the rest of the attributes. Organize attributes into a logical sequence or groups by adding
//configModal.contentEl.createEl("h") or similar to the code
new ea.obsidian.Setting(configModal.contentEl)
.addButton(button => button
.setButtonText("SELECT")
.onClick(()=>{
config.timestamp = Date.now();
window.ExcalidrawSelectConfig = config;
configModal.close();
})
)
.addButton(button => button
.setButtonText("RUN")
.setCta(true)
.onClick(()=>{
elements = ea.getViewElements();
run();
configModal.close();
})
)
}
configModal.onClose = () => {
setTimeout(()=>delete configModal);
}
configModal.open();
}
if(config) {
run();
} else {
selectAttributesToCopy();
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-filter"><polygon fill="none" stroke-width="2" points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/></svg>

After

Width:  |  Height:  |  Size: 285 B

View File

@@ -8,12 +8,44 @@ https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.h
```javascript
*/
const grid = parseInt(await utils.inputPrompt("Grid size?",null,"20"));
if(isNaN(grid)) return; //this is to avoid passing an illegal value to Excalidraw
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.9.19")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
const api = ea.getExcalidrawAPI();
let appState = api.getAppState();
const gridColor = appState.gridColor;
let gridFrequency = gridColor?.MajorGridFrequency ?? 5;
const customControls = (container) => {
new ea.obsidian.Setting(container)
.setName(`Major grid frequency`)
.addDropdown(dropdown => {
[2,3,4,5,6,7,8,9,10].forEach(grid=>dropdown.addOption(grid,grid));
dropdown
.setValue(gridFrequency)
.onChange(value => {
gridFrequency = value;
})
})
}
const grid = parseInt(await utils.inputPrompt(
"Grid size?",
null,
appState.previousGridSize?.toString()??"20",
null,
1,
false,
customControls
));
if(isNaN(grid)) return; //this is to avoid passing an illegal value to Excalidraw
appState.gridSize = grid;
appState.previousGridSize = grid;
if(gridColor) gridColor.MajorGridFrequency = parseInt(gridFrequency);
api.updateScene({
appState,
appState : {gridSize: grid, previousGridSize: grid, gridColor},
commitToHistory:false
});

View File

@@ -1,7 +1,7 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-set-background-color-of-unclosed-line.jpg)
Use this script to set the background color of unclosed (i.e. open) line and freedraw objects by creating a clone of the object. The script will set the stroke color of the clone to transparent and will add a straight line to close the object. Use settings to define the default background color, the fill style, and the strokeWidth of the clone. By default the clone will be grouped with the original object, you can disable this also in settings.
Use this script to set the background color of unclosed (i.e. open) line, arrow and freedraw objects by creating a clone of the object. The script will set the stroke color of the clone to transparent and will add a straight line to close the object. Use settings to define the default background color, the fill style, and the strokeWidth of the clone. By default the clone will be grouped with the original object, you can disable this also in settings.
```javascript
*/
@@ -41,9 +41,9 @@ const backgroundColor = settings["Background Color"].value;
const fillStyle = settings["Fill Style"].value;
const shouldGroup = settings["Group 'shadow' with original"].value;
const elements = ea.getViewSelectedElements().filter(el=>el.type==="line" || el.type==="freedraw");
const elements = ea.getViewSelectedElements().filter(el=>el.type==="line" || el.type==="freedraw" || el.type==="arrow");
if(elements.length === 0) {
new Notice("No line or freedraw object is selected");
new Notice("No line or freedraw object is selected");
}
ea.copyViewElementsToEAforEditing(elements);
@@ -52,19 +52,20 @@ elementsToMove = [];
elements.forEach((el)=>{
const newEl = ea.cloneElement(el);
ea.elementsDict[newEl.id] = newEl;
newEl.roughness = 1;
if(!inheritStrokeWidth) newEl.strokeWidth = 2;
newEl.roughness = 1;
if(!inheritStrokeWidth) newEl.strokeWidth = 2;
newEl.strokeColor = "transparent";
newEl.backgroundColor = backgroundColor;
newEl.fillStyle = fillStyle;
const i = el.points.length-1;
newEl.points.push([
//adding an extra point close to the last point in case distance is long from last point to origin and there is a sharp bend. This will avoid a spike due to a tight curve.
el.points[i][0]*0.9,
newEl.fillStyle = fillStyle;
if (newEl.type === "arrow") newEl.type = "line";
const i = el.points.length-1;
newEl.points.push([
//adding an extra point close to the last point in case distance is long from last point to origin and there is a sharp bend. This will avoid a spike due to a tight curve.
el.points[i][0]*0.9,
el.points[i][1]*0.9,
]);
]);
newEl.points.push([0,0]);
if(shouldGroup) ea.addToGroup([el.id,newEl.id]);
if(shouldGroup) ea.addToGroup([el.id,newEl.id]);
elementsToMove.push({fillId: newEl.id, shapeId: el.id});
});
@@ -72,9 +73,9 @@ await ea.addElementsToView(false,false);
elementsToMove.forEach((x)=>{
const viewElements = ea.getViewElements();
ea.moveViewElementToZIndex(
x.fillId,
x.fillId,
viewElements.indexOf(viewElements.filter(el=>el.id === x.shapeId)[0])-1
)
)
});
ea.selectElementsInView(ea.getElements());

View File

@@ -1,198 +1,559 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-slideshow-1.jpg)
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-slideshow-2.jpg)
# About the slideshow script
The script will convert your drawing into a slideshow presentation.
![Slideshow 3.0](https://www.youtube.com/JwgtCrIVeEU)
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-slideshow-2.jpg)
## Presentation options
- If you select an arrow or line element, the script will use that as the presentation path.
- If you select nothing, but the file has a hidden presentation path, the script will use that for determining the slide sequence.
- If there are frames, the script will use the frames for the presentation. Frames are played in alphabetical order of their titles.
# Keyboard shortcuts and modifier keys
**Forward**: Arrow Down, Arrow Right, or SPACE
**Backward**: Arrow Up, Arrow Left
**Finish presentation**: Backspace, ESC (I had issues with ESC not working in full screen presentation mode on Mac)
**Run presentation in a window**: Hold down the ALT/OPT modifier key when clicking the presentation script button
**Continue presentation**: Hold down SHIFT when clicking the presentation script button. (The feature also works in combination with the ALT/OPT modifier to start the presentation in a window). The feature will only resume while you are within the same Obsidian session (i.e. if you restart Obsidian, slideshow will no longer remember where you were). I have two use cases in mind for this feature:
1) When you are designing your presentation you may want to test how a slide looks. Using this feature you can get back to where you left off by starting the presentation with SHIFT.
2) During presentation you may want to exit presentation mode to show something additional to your audience. You stop the presentation, show the additional thing you wanted, now you want to continue from where you left off. Hold down SHIFT when clicking the slideshow button.
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.8.2")) {
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.1.7")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
const hostLeaf = ea.targetView.leaf;
const hostView = hostLeaf.view;
const statusBarElement = document.querySelector("div.status-bar");
const ctrlKey = ea.targetView.modifierKeyDown.ctrlKey || ea.targetView.modifierKeyDown.metaKey;
const altKey = ea.targetView.modifierKeyDown.altKey || ctrlKey;
const shiftKey = ea.targetView.modifierKeyDown.shiftKey;
const shouldStartWithLastSlide = shiftKey && window.ExcalidrawSlideshow &&
(window.ExcalidrawSlideshow.script === utils.scriptFile.path) && (typeof window.ExcalidrawSlideshow.slide === "number")
//-------------------------------
//constants
const STEPCOUNT = 100;
//-------------------------------
const TRANSITION_STEP_COUNT = 100;
const TRANSITION_DELAY = 1000; //maximum time for transition between slides in milliseconds
const FRAME_SLEEP = 1; //milliseconds
const EDIT_ZOOMOUT = 0.7; //70% of original slide zoom, set to a value between 1 and 0
const FADE_LEVEL = 0.1; //opacity of the slideshow controls after fade delay (value between 0 and 1)
//using outerHTML because the SVG object returned by Obsidin is in the main workspace window
//but excalidraw might be open in a popout window which has a different document object
const SVG_COG = ea.obsidian.getIcon("lucide-settings").outerHTML;
const SVG_FINISH = ea.obsidian.getIcon("lucide-x").outerHTML;
const SVG_RIGHT_ARROW = ea.obsidian.getIcon("lucide-arrow-right").outerHTML;
const SVG_LEFT_ARROW = ea.obsidian.getIcon("lucide-arrow-left").outerHTML;
const SVG_EDIT = ea.obsidian.getIcon("lucide-pencil").outerHTML;
const SVG_MAXIMIZE = ea.obsidian.getIcon("lucide-maximize").outerHTML;
const SVG_MINIMIZE = ea.obsidian.getIcon("lucide-minimize").outerHTML;
const SVG_LASER_ON = ea.obsidian.getIcon("lucide-hand").outerHTML;
const SVG_LASER_OFF = ea.obsidian.getIcon("lucide-wand").outerHTML;
//-------------------------------
//utility & convenience functions
const doc = ea.targetView.ownerDocument;
const win = ea.targetView.ownerWindow;
const api = ea.getExcalidrawAPI();
const contentEl = ea.targetView.contentEl;
const sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
//-------------------------------
let isLaserOn = false;
let slide = shouldStartWithLastSlide ? window.ExcalidrawSlideshow.slide : 0;
let isFullscreen = false;
const ownerDocument = ea.targetView.ownerDocument;
const startFullscreen = !altKey;
//The plugin and Obsidian App run in the window object
//When Excalidraw is open in a popout window, the Excalidraw component will run in the ownerWindow
//and in this case ownerWindow !== window
//For this reason event handlers are distributed between window and owner window depending on their role
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')}`;
//-------------------------------
//clean up potential clutter from previous run
//-------------------------------
window.removePresentationEventHandlers?.();
//check if line or arrow is selected, if not inform the user and terminate presentation
const lineEl = ea.getViewSelectedElement();
if(!lineEl || !["line","arrow"].contains(lineEl.type)) {
new Notice("Please select the line or arrow for the presentation path");
return;
//1. check if line or arrow is selected, if not check if frames are available, if not inform the user and terminate presentation
let presentationPathLineEl = ea.getViewElements()
.filter(el=>["line","arrow"].contains(el.type) && el.customData?.slideshow)[0];
const frameClones = [];
ea.getViewElements().filter(el=>el.type==="frame").forEach(f=>frameClones.push(ea.cloneElement(f)));
for(i=0;i<frameClones.length;i++) {
frameClones[i].name = getFrameName(frameClones[i].name,i);
}
let frames = frameClones
.sort((el1,el2)=> el1.name > el2.name ? 1:-1);
let presentationPathType = "line"; // "frame"
const selectedEl = ea.getViewSelectedElement();
let shouldHideArrowAfterPresentation = true; //this controls if the hide arrow button is available in settings
if(presentationPathLineEl && selectedEl && ["line","arrow"].contains(selectedEl.type)) {
excalidrawAPI.setToast({
message:"Using selected line instead of hidden line. Note that there is a hidden presentation path for this drawing. Run the slideshow script without selecting any elements to access the hidden presentation path",
duration: 5000,
closable: true
})
shouldHideArrowAfterPresentation = false;
presentationPathLineEl = selectedEl;
}
if(!presentationPathLineEl) presentationPathLineEl = selectedEl;
if(!presentationPathLineEl || !["line","arrow"].contains(presentationPathLineEl.type)) {
if(frames.length > 0) {
presentationPathType = "frame";
} else {
excalidrawAPI.setToast({
message:"Please select the line or arrow for the presentation path or add frames.",
duration: 3000,
closable: true
})
return;
}
}
//goto fullscreen
if(app.isMobile) {
ea.viewToggleFullScreen(true);
} else {
await contentEl.requestFullscreen();
await sleep(50);
ea.setViewModeEnabled(true);
}
const deltaWidth = () => contentEl.clientWidth-api.getAppState().width;
let watchdog = 0;
while (deltaWidth()>50 && watchdog++<20) await sleep(100); //wait for Excalidraw to resize to fullscreen
contentEl.querySelector(".layer-ui__wrapper").addClass("excalidraw-hidden");
//---------------------------------------------
// generate slides[] array
//---------------------------------------------
let slides = [];
//hide the arrow and save the arrow color before doing so
const originalColor = {
strokeColor: lineEl.strokeColor,
backgroundColor: lineEl.backgroundColor
}
ea.copyViewElementsToEAforEditing([lineEl]);
ea.getElement(lineEl.id).strokeColor = "transparent";
ea.getElement(lineEl.id).backgroundColor = "transparent";
await ea.addElementsToView();
//----------------------------
//scroll-to-location functions
//----------------------------
let slide = -1;
const slideCount = Math.floor(lineEl.points.length/2)-1;
const getNextSlide = (forward) => {
slide = forward
? slide < slideCount ? slide + 1 : 0
: slide <= 0 ? slideCount : slide - 1;
return {pointA:lineEl.points[slide*2], pointB:lineEl.points[slide*2+1]}
if(presentationPathType === "line") {
const getLineSlideRect = ({pointA, pointB}) => {
const x1 = presentationPathLineEl.x+pointA[0];
const y1 = presentationPathLineEl.y+pointA[1];
const x2 = presentationPathLineEl.x+pointB[0];
const y2 = presentationPathLineEl.y+pointB[1];
return { x1, y1, x2, y2};
}
const slideCount = Math.floor(presentationPathLineEl.points.length/2)-1;
for(i=0;i<=slideCount;i++) {
slides.push(getLineSlideRect({
pointA:presentationPathLineEl.points[i*2],
pointB:presentationPathLineEl.points[i*2+1]
}))
}
}
const getSlideRect = ({pointA, pointB}) => {
const {width, height} = api.getAppState();
const x1 = lineEl.x+pointA[0];
const y1 = lineEl.y+pointA[1];
const x2 = lineEl.x+pointB[0];
const y2 = lineEl.y+pointB[1];
const ratioX = width/Math.abs(x1-x2);
const ratioY = height/Math.abs(y1-y2);
let ratio = ratioX<ratioY?ratioX:ratioY;
if (ratio < 0.1) ratio = 0.1;
if (ratio > 10) ratio = 10;
const deltaX = (ratio===ratioY)?(width/ratio - Math.abs(x1-x2))/2:0;
const deltaY = (ratio===ratioX)?(height/ratio - Math.abs(y1-y2))/2:0;
if(presentationPathType === "frame") {
for(frame of frames) {
slides.push({
x1: frame.x,
y1: frame.y,
x2: frame.x + frame.width,
y2: frame.y + frame.height
});
}
if(frameRenderingOriginalState.enabled) {
excalidrawAPI.updateScene({
appState: {
frameRendering: {
...frameRenderingOriginalState,
enabled: false
}
}
});
}
}
//---------------------------------------
// Toggle fullscreen
//---------------------------------------
let toggleFullscreenButton;
let controlPanelEl;
let selectSlideDropdown;
const resetControlPanelElPosition = () => {
if(!controlPanelEl) return;
const top = contentEl.innerHeight;
const left = contentEl.innerWidth/2;
controlPanelEl.style.top = `calc(${top}px - var(--default-button-size)*2)`;
controlPanelEl.style.left = `calc(${left}px - var(--default-button-size)*5)`;
slide--;
navigate("fwd");
}
const waitForExcalidrawResize = async () => {
await sleep(100);
const deltaWidth = () => Math.abs(contentEl.clientWidth-excalidrawAPI.getAppState().width);
const deltaHeight = () => Math.abs(contentEl.clientHeight-excalidrawAPI.getAppState().height);
let watchdog = 0;
while ((deltaWidth()>50 || deltaHeight()>50) && watchdog++<20) await sleep(50); //wait for Excalidraw to resize to fullscreen
}
let preventFullscreenExit = true;
const gotoFullscreen = async () => {
if(isFullscreen) return;
preventFullscreenExit = true;
if(ea.DEVICE.isMobile) {
ea.viewToggleFullScreen();
} else {
await contentEl.webkitRequestFullscreen();
}
await waitForExcalidrawResize();
const layerUIWrapper = contentEl.querySelector(".layer-ui__wrapper");
if(!layerUIWrapper.hasClass("excalidraw-hidden")) layerUIWrapper.addClass("excalidraw-hidden");
if(toggleFullscreenButton) toggleFullscreenButton.innerHTML = SVG_MINIMIZE;
resetControlPanelElPosition();
isFullscreen = true;
}
const exitFullscreen = async () => {
if(!isFullscreen) return;
preventFullscreenExit = true;
if(!ea.DEVICE.isMobile && ownerDocument?.fullscreenElement) await ownerDocument.exitFullscreen();
if(ea.DEVICE.isMobile) ea.viewToggleFullScreen();
if(toggleFullscreenButton) toggleFullscreenButton.innerHTML = SVG_MAXIMIZE;
await waitForExcalidrawResize();
resetControlPanelElPosition();
isFullscreen = false;
}
const toggleFullscreen = async () => {
if (isFullscreen) {
await exitFullscreen();
} else {
await gotoFullscreen();
}
}
//-----------------------------------------------------
// hide the arrow for the duration of the presentation
// and save the arrow color before doing so
//-----------------------------------------------------
let isHidden;
let originalProps;
const toggleArrowVisibility = async (setToHidden) => {
ea.clear();
ea.copyViewElementsToEAforEditing(ea.getViewElements().filter(el=>el.id === presentationPathLineEl.id));
const el = ea.getElement(presentationPathLineEl.id);
el.strokeColor = "transparent";
el.backgroundColor = "transparent";
const customData = el.customData;
if(setToHidden && shouldHideArrowAfterPresentation) {
el.locked = true;
el.customData = {
...customData,
slideshow: {
originalProps,
hidden: true
}
}
isHidden = true;
} else {
if(customData) delete el.customData.slideshow;
isHidden = false;
}
await ea.addElementsToView();
}
if(presentationPathType==="line") {
originalProps = presentationPathLineEl.customData?.slideshow?.hidden
? presentationPathLineEl.customData.slideshow.originalProps
: {
strokeColor: presentationPathLineEl.strokeColor,
backgroundColor: presentationPathLineEl.backgroundColor,
locked: presentationPathLineEl.locked,
};
isHidden = presentationPathLineEl.customData?.slideshow?.hidden ?? false;
}
//-----------------------------
// scroll-to-location functions
//-----------------------------
const getNavigationRect = ({ x1, y1, x2, y2 }) => {
const { width, height } = excalidrawAPI.getAppState();
const ratioX = width / Math.abs(x1 - x2);
const ratioY = height / Math.abs(y1 - y2);
let ratio = Math.min(Math.max(ratioX, ratioY), 30);
const scaledWidth = Math.abs(x1 - x2) * ratio;
const scaledHeight = Math.abs(y1 - y2) * ratio;
if (scaledWidth > width || scaledHeight > height) {
ratio = Math.min(width / Math.abs(x1 - x2), height / Math.abs(y1 - y2));
}
const deltaX = (width / ratio - Math.abs(x1 - x2)) / 2;
const deltaY = (height / ratio - Math.abs(y1 - y2)) / 2;
return {
left: (x1<x2?x1:x2)-deltaX,
top: (y1<y2?y1:y2)-deltaY,
right: (x1<x2?x2:x1)+deltaX,
bottom: (y1<y2?y2:y1)+deltaY,
nextZoom: ratio
left: (x1 < x2 ? x1 : x2) - deltaX,
top: (y1 < y2 ? y1 : y2) - deltaY,
right: (x1 < x2 ? x2 : x1) + deltaX,
bottom: (y1 < y2 ? y2 : y1) + deltaY,
nextZoom: ratio,
};
};
const getNextSlideRect = (forward) => {
slide = forward
? slide < slides.length-1 ? slide + 1 : 0
: slide <= 0 ? slides.length-1 : slide - 1;
return getNavigationRect(slides[slide]);
}
let busy = false;
const scrollToNextRect = async ({left,top,right,bottom,nextZoom}) => {
const scrollToNextRect = async ({left,top,right,bottom,nextZoom},steps = TRANSITION_STEP_COUNT) => {
const startTimer = Date.now();
let watchdog = 0;
while(busy && watchdog++<15) await(100);
while(busy && watchdog++<15) await sleep(100);
if(busy && watchdog >= 15) return;
busy = true;
const {scrollX, scrollY, zoom} = api.getAppState();
const zoomStep = (zoom.value-nextZoom)/STEPCOUNT;
const xStep = (left+scrollX)/STEPCOUNT;
const yStep = (top+scrollY)/STEPCOUNT;
for(i=1;i<=STEPCOUNT;i++) {
api.updateScene({
excalidrawAPI.updateScene({appState:{shouldCacheIgnoreZoom:true}});
const {scrollX, scrollY, zoom} = excalidrawAPI.getAppState();
const zoomStep = (zoom.value-nextZoom)/steps;
const xStep = (left+scrollX)/steps;
const yStep = (top+scrollY)/steps;
let i=1;
while(i<=steps) {
excalidrawAPI.updateScene({
appState: {
scrollX:scrollX-(xStep*i),
scrollY:scrollY-(yStep*i),
zoom:{value:zoom.value-zoomStep*i},
shouldCacheIgnoreZoom:true,
}
});
await sleep(FRAME_SLEEP);
const ellapsed = Date.now()-startTimer;
if(ellapsed > TRANSITION_DELAY) {
i = i<steps ? steps : steps+1;
} else {
const timeProgress = ellapsed / TRANSITION_DELAY;
i=Math.min(Math.round(steps*timeProgress),steps)
await sleep(FRAME_SLEEP);
}
}
excalidrawAPI.updateScene({appState:{shouldCacheIgnoreZoom:false}});
if(isLaserOn) {
excalidrawAPI.setActiveTool({type: "laser"});
}
api.updateScene({appState:{shouldCacheIgnoreZoom:false}});
busy = false;
}
const navigate = async (dir) => {
const forward = dir === "fwd";
const prevSlide = slide;
const nextSlide = getNextSlide(forward);
const nextRect = getNextSlideRect(forward);
//exit if user navigates from last slide forward or first slide backward
const shouldExit = forward
? slide<=prevSlide
: slide>=prevSlide;
if(shouldExit) {
if(!app.isMobile) await doc.exitFullscreen();
exitPresentation();
return;
}
if(slideNumberEl) slideNumberEl.innerText = (slide+1).toString();
const nextRect = getSlideRect(nextSlide);
if(selectSlideDropdown) selectSlideDropdown.value = slide+1;
await scrollToNextRect(nextRect);
if(window.ExcalidrawSlideshow && (typeof window.ExcalidrawSlideshow.slide === "number")) {
window.ExcalidrawSlideshow.slide = slide;
}
}
const navigateToSlide = (slideNumber) => {
if(slideNumber > slides.length) slideNumber = slides.length;
if(slideNumber < 1) slideNumber = 1;
slide = slideNumber - 2;
navigate("fwd");
}
//--------------------------------------
//Slideshow control
// Slideshow control panel
//--------------------------------------
//create slideshow controlpanel container
const top = contentEl.innerHeight;
const left = contentEl.innerWidth;
const containerEl = contentEl.createDiv({
cls: ["excalidraw","excalidraw-presentation-panel"],
attr: {
style: `
width: calc(var(--default-button-size)*3);
z-index:5;
position: absolute;
top:calc(${top}px - var(--default-button-size)*2);
left:calc(${left}px - var(--default-button-size)*3.5);`
}
});
const panelColumn = containerEl.createDiv({
cls: "panelColumn",
});
let slideNumberEl;
panelColumn.createDiv({
cls: ["Island", "buttonList"],
attr: {
style: `
height: calc(var(--default-button-size)*1.5);
width: 100%;
background: var(--island-bg-color);`,
}
}, el=>{
el.createEl("button",{
text: "<",
attr: {
style: `
margin-top: calc(var(--default-button-size)*0.25);
margin-left: calc(var(--default-button-size)*0.25);`
let controlPanelFadeTimout = 0;
const setFadeTimeout = (delay) => {
delay = delay ?? TRANSITION_DELAY;
controlPanelFadeTimeout = ownerWindow.setTimeout(()=>{
controlPanelFadeTimout = 0;
if(ownerDocument.activeElement === selectSlideDropdown) {
setFadeTimeout(delay);
return;
}
}, button => button .onclick = () => navigate("bkwd"));
el.createEl("button",{
text: ">",
attr: {
style: `
margin-top: calc(var(--default-button-size)*0.25);
margin-right: calc(var(--default-button-size)*0.25);`
}
}, button => button.onclick = () => navigate("fwd"));
slideNumberEl = el.createEl("span",{
text: "1",
cls: ["ToolIcon__keybinding"],
})
});
controlPanelEl.style.opacity = FADE_LEVEL;
},delay);
}
const clearFadeTimeout = () => {
if(controlPanelFadeTimeout) {
ownerWindow.clearTimeout(controlPanelFadeTimeout);
controlPanelFadeTimeout = 0;
}
controlPanelEl.style.opacity = 1;
}
//keyboard navigation
const createPresentationNavigationPanel = () => {
//create slideshow controlpanel container
const top = contentEl.innerHeight;
const left = contentEl.innerWidth/2;
controlPanelEl = contentEl.querySelector(".excalidraw").createDiv({
cls: ["excalidraw-presentation-panel"],
attr: {
style: `
width: fit-content;
z-index:5;
position: absolute;
top:calc(${top}px - var(--default-button-size)*2);
left:calc(${left}px - var(--default-button-size)*5);`
}
});
setFadeTimeout(TRANSITION_DELAY*3);
const panelColumn = controlPanelEl.createDiv({
cls: "panelColumn",
});
panelColumn.createDiv({
cls: ["Island", "buttonList"],
attr: {
style: `
max-width: unset;
justify-content: space-between;
height: calc(var(--default-button-size)*1.5);
width: 100%;
background: var(--island-bg-color);
display: flex;
align-items: center;`,
}
}, el=>{
el.createEl("style",
{ text: ` select:focus { box-shadow: var(--input-shadow);} `});
el.createEl("button",{
attr: {
style: `
margin-left: calc(var(--default-button-size)*0.25);`,
"aria-label": "Previous slide",
title: "Previous slide"
}
}, button => {
button.innerHTML = SVG_LEFT_ARROW;
button.onclick = () => navigate("bkwd")
});
selectSlideDropdown = el.createEl("select", {
attr: {
style: `
font-size: inherit;
background-color: var(--island-bg-color);
border: none;
color: var(--color-gray-100);
cursor: pointer;
}`,
title: "Navigate to slide"
}
}, selectEl => {
for (let i = 0; i < slides.length; i++) {
const option = document.createElement("option");
option.text = (presentationPathType === "frame")
? `${frames[i].name}/${slides.length}`
: option.text = `Slide ${i + 1}/${slides.length}`;
option.value = i + 1;
selectEl.add(option);
}
selectEl.addEventListener("change", () => {
const selectedSlideNumber = parseInt(selectEl.value);
selectEl.blur();
navigateToSlide(selectedSlideNumber);
});
});
el.createEl("button",{
attr: {
title: "Next slide"
},
}, button => {
button.innerHTML = SVG_RIGHT_ARROW;
button.onclick = () => navigate("fwd");
});
el.createDiv({
attr: {
style: `
width: 1px;
height: var(--default-button-size);
background-color: var(--default-border-color);
margin: 0px auto;`
}
});
el.createEl("button",{
attr: {
title: "Toggle Laser Pointer and Panning Mode"
}
}, button => {
button.innerHTML = isLaserOn ? SVG_LASER_ON : SVG_LASER_OFF;
button.onclick = () => {
isLaserOn = !isLaserOn;
excalidrawAPI.setActiveTool({
type: isLaserOn ? "laser" : "selection"
})
button.innerHTML = isLaserOn ? SVG_LASER_ON : SVG_LASER_OFF;
}
});
el.createEl("button",{
attr: {
title: "Toggle fullscreen. If you hold ALT/OPT when starting the presentation it will not go fullscreen."
},
}, button => {
toggleFullscreenButton = button;
button.innerHTML = isFullscreen ? SVG_MINIMIZE : SVG_MAXIMIZE;
button.onclick = () => toggleFullscreen();
});
if(presentationPathType === "line") {
if(shouldHideArrowAfterPresentation) {
new ea.obsidian.ToggleComponent(el)
.setValue(isHidden)
.onChange(value => {
if(value) {
excalidrawAPI.setToast({
message:"The presentation path remain hidden after the presentation. No need to select the line again. Just click the slideshow button to start the next presentation.",
duration: 5000,
closable: true
})
}
toggleArrowVisibility(value);
})
.toggleEl.setAttribute("title","Arrow visibility. ON: hidden after presentation, OFF: visible after presentation");
}
el.createEl("button",{
attr: {
title: "Edit slide"
},
}, button => {
button.innerHTML = SVG_EDIT;
button.onclick = () => {
if(shouldHideArrowAfterPresentation) toggleArrowVisibility(false);
exitPresentation(true);
}
});
}
el.createEl("button",{
attr: {
style: `
margin-right: calc(var(--default-button-size)*0.25);`,
title: "End presentation"
}
}, button => {
button.innerHTML = SVG_FINISH;
button.onclick = () => exitPresentation()
});
});
}
//--------------------
// keyboard navigation
//--------------------
const keydownListener = (e) => {
if(hostLeaf !== app.workspace.activeLeaf) return;
if(hostLeaf.width === 0 && hostLeaf.height === 0) return;
e.preventDefault();
switch(e.key) {
case "escape":
if(app.isMobile) exitPresentation();
case "Backspace":
case "Escape":
exitPresentation();
break;
case "Space":
case "ArrowRight":
case "ArrowDown":
navigate("fwd");
@@ -201,12 +562,28 @@ const keydownListener = (e) => {
case "ArrowUp":
navigate("bkwd");
break;
}
case "End":
slide = slides.length - 2;
navigate("fwd");
break;
case "Home":
slide = -1;
navigate("fwd");
break;
case "e":
if(presentationPathType !== "line") return;
(async ()=>{
await toggleArrowVisibility(false);
exitPresentation(true);
})()
break;
}
}
doc.addEventListener('keydown',keydownListener);
//slideshow panel drag
let pos1 = pos2 = pos3 = pos4 = 0;
//---------------------
// slideshow panel drag
//---------------------
let posX1 = posY1 = posX2 = posY2 = 0;
const updatePosition = (deltaY = 0, deltaX = 0) => {
const {
@@ -214,69 +591,188 @@ const updatePosition = (deltaY = 0, deltaX = 0) => {
offsetLeft,
clientWidth: width,
clientHeight: height,
} = containerEl;
containerEl.style.top = (offsetTop - deltaY) + 'px';
containerEl.style.left = (offsetLeft - deltaX) + 'px';
} = controlPanelEl;
controlPanelEl.style.top = (offsetTop - deltaY) + 'px';
controlPanelEl.style.left = (offsetLeft - deltaX) + 'px';
}
const pointerUp = () => {
win.removeEventListener('pointermove', onDrag, true);
const onPointerUp = () => {
ownerWindow.removeEventListener('pointermove', onDrag, true);
}
const pointerDown = (e) => {
pos3 = e.clientX;
pos4 = e.clientY;
win.addEventListener('pointermove', onDrag, true);
const onPointerDown = (e) => {
clearFadeTimeout();
setFadeTimeout();
const now = Date.now();
posX2 = e.clientX;
posY2 = e.clientY;
ownerWindow.addEventListener('pointermove', onDrag, true);
}
const onDrag = (e) => {
e.preventDefault();
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
updatePosition(pos2, pos1);
posX1 = posX2 - e.clientX;
posY1 = posY2 - e.clientY;
posX2 = e.clientX;
posY2 = e.clientY;
updatePosition(posY1, posX1);
}
containerEl.addEventListener('pointerdown', pointerDown, false);
win.addEventListener('pointerup', pointerUp, false);
//event listners for terminating the presentation
window.removePresentationEventHandlers = () => {
ea.onLinkClickHook = null;
containerEl.parentElement?.removeChild(containerEl);
if(!app.isMobile) win.removeEventListener('fullscreenchange', fullscreenListener);
doc.removeEventListener('keydown',keydownListener);
win.removeEventListener('pointerup',pointerUp);
contentEl.querySelector(".layer-ui__wrapper").removeClass("excalidraw-hidden");
delete window.removePresentationEventHandlers;
const onMouseEnter = () => {
clearFadeTimeout();
}
const exitPresentation = () => {
window.removePresentationEventHandlers?.();
if(app.isMobile) ea.viewToggleFullScreen(true);
else ea.setViewModeEnabled(false);
ea.clear();
ea.copyViewElementsToEAforEditing(ea.getViewElements().filter(el=>el.id === lineEl.id));
ea.getElement(lineEl.id).strokeColor = originalColor.strokeColor;
ea.getElement(lineEl.id).backgroundColor = originalColor.backgroundColor;
ea.addElementsToView();
ea.selectElementsInView(ea.getElements());
const onMouseLeave = () => {
setFadeTimeout();
}
ea.onLinkClickHook = () => {
exitPresentation();
return true;
};
const fullscreenListener = (e) => {
if(preventFullscreenExit) {
preventFullscreenExit = false;
return;
}
e.preventDefault();
exitPresentation();
}
if(!app.isMobile) {
win.addEventListener('fullscreenchange', fullscreenListener);
const initializeEventListners = () => {
ownerWindow.addEventListener('keydown',keydownListener);
controlPanelEl.addEventListener('pointerdown', onPointerDown, false);
controlPanelEl.addEventListener('mouseenter', onMouseEnter, false);
controlPanelEl.addEventListener('mouseleave', onMouseLeave, false);
ownerWindow.addEventListener('pointerup', onPointerUp, false);
//event listners for terminating the presentation
window.removePresentationEventHandlers = () => {
ea.onLinkClickHook = null;
controlPanelEl.removeEventListener('pointerdown', onPointerDown, false);
controlPanelEl.removeEventListener('mouseenter', onMouseEnter, false);
controlPanelEl.removeEventListener('mouseleave', onMouseLeave, false);
controlPanelEl.parentElement?.removeChild(controlPanelEl);
if(!ea.DEVICE.isMobile) {
contentEl.removeEventListener('webkitfullscreenchange', fullscreenListener);
contentEl.removeEventListener('fullscreenchange', fullscreenListener);
}
ownerWindow.removeEventListener('keydown',keydownListener);
ownerWindow.removeEventListener('pointerup',onPointerUp);
contentEl.querySelector(".layer-ui__wrapper")?.removeClass("excalidraw-hidden");
delete window.removePresentationEventHandlers;
}
ea.onLinkClickHook = () => {
exitPresentation();
return true;
};
if(!ea.DEVICE.isMobile) {
contentEl.addEventListener('webkitfullscreenchange', fullscreenListener);
contentEl.addEventListener('fullscreenchange', fullscreenListener);
}
}
//navigate to the first slide on start
setTimeout(()=>navigate("fwd"));
//----------------------------
// Exit presentation
//----------------------------
const exitPresentation = async (openForEdit = false) => {
//this is a hack, not sure why ea loses target view when other scripts are executed while the presentation is running
ea.targetView = hostView;
isLaserOn = false;
statusBarElement.style.display = "inherit";
if(openForEdit) ea.targetView.preventAutozoom();
await exitFullscreen();
await waitForExcalidrawResize();
ea.setViewModeEnabled(false);
if(presentationPathType === "line") {
ea.clear();
ea.copyViewElementsToEAforEditing(ea.getViewElements().filter(el=>el.id === presentationPathLineEl.id));
const el = ea.getElement(presentationPathLineEl.id);
if(!isHidden) {
el.strokeColor = originalProps.strokeColor;
el.backgroundProps = originalProps.backgroundColor;
el.locked = openForEdit ? false : originalProps.locked;
}
await ea.addElementsToView();
if(!isHidden) ea.selectElementsInView([el]);
if(openForEdit) {
let nextRect = getNextSlideRect(--slide);
const offsetW = (nextRect.right-nextRect.left)*(1-EDIT_ZOOMOUT)/2;
const offsetH = (nextRect.bottom-nextRect.top)*(1-EDIT_ZOOMOUT)/2
nextRect = {
left: nextRect.left-offsetW,
right: nextRect.right+offsetW,
top: nextRect.top-offsetH,
bottom: nextRect.bottom+offsetH,
nextZoom: nextRect.nextZoom*EDIT_ZOOMOUT > 0.1 ? nextRect.nextZoom*EDIT_ZOOMOUT : 0.1 //0.1 is the minimu zoom value
};
await scrollToNextRect(nextRect,1);
excalidrawAPI.startLineEditor(
ea.getViewSelectedElement(),
[slide*2,slide*2+1]
);
}
} else {
if(frameRenderingOriginalState.enabled) {
excalidrawAPI.updateScene({
appState: {
frameRendering: {
...frameRenderingOriginalState,
enabled: true
}
}
});
}
}
window.removePresentationEventHandlers?.();
ownerWindow.setTimeout(()=>{
//Resets pointer offsets. Ugly solution.
//During testing offsets were wrong after presentation, but don't know why.
//This should solve it even if they are wrong.
hostView.refreshCanvasOffset();
excalidrawAPI.setActiveTool({type: "selection"});
})
}
//--------------------------
// Start presentation or open presentation settings on double click
//--------------------------
const start = async () => {
statusBarElement.style.display = "none";
ea.setViewModeEnabled(true);
const helpButton = ea.targetView.excalidrawContainer?.querySelector(".ToolIcon__icon.help-icon");
if(helpButton) {
helpButton.style.display = "none";
}
const zoomButton = ea.targetView.excalidrawContainer?.querySelector(".Stack.Stack_vertical.zoom-actions");
if(zoomButton) {
zoomButton.style.display = "none";
}
createPresentationNavigationPanel();
initializeEventListners();
if(startFullscreen) {
await gotoFullscreen();
} else {
resetControlPanelElPosition();
}
if(presentationPathType === "line") await toggleArrowVisibility(isHidden);
}
const timestamp = Date.now();
if(window.ExcalidrawSlideshow && (window.ExcalidrawSlideshow.script === utils.scriptFile.path) && (timestamp - window.ExcalidrawSlideshow.timestamp <400) ) {
if(window.ExcalidrawSlideshowStartTimer) {
window.clearTimeout(window.ExcalidrawSlideshowStartTimer);
delete window.ExcalidrawSlideshowStartTimer;
}
await start();
} else {
if(window.ExcalidrawSlideshowStartTimer) {
window.clearTimeout(window.ExcalidrawSlideshowStartTimer);
delete window.ExcalidrawSlideshowStartTimer;
}
window.ExcalidrawSlideshow = {
script: utils.scriptFile.path,
timestamp,
slide: 0
};
window.ExcalidrawSlideshowStartTimer = window.setTimeout(start,500);
}

208
ea-scripts/Split Ellipse.md Normal file
View File

@@ -0,0 +1,208 @@
/*
This script splits an ellipse at any point where a line intersects it. If no lines are selected, it will use every line that intersects the ellipse. Otherwise, it will only use the selected lines. If there is no intersecting line, the ellipse will be converted into a line object.
There is also the option to close the object along the cut, which will close the cut in the shape of the line.
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-splitEllipse-demo1.jpg)
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-splitEllipse-demo2.jpg)
Tip: To use an ellipse as the cutting object, you first have to use this script on it, since it will convert the ellipse into a line.
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
const elements = ea.getViewSelectedElements();
const ellipse = elements.filter(el => el.type == "ellipse")[0];
if (!ellipse) return;
let lines = elements.filter(el => el.type == "line" || el.type == "arrow");
if (lines.length == 0) lines = ea.getViewElements().filter(el => el.type == "line" || el.type == "arrow");
const subLines = getSubLines(lines);
const angles = subLines.flatMap(line => {
return intersectionAngleOfEllipseAndLine(ellipse, line.a, line.b).map(result => ({
angle: result,
cuttingLine: line
}));
});
if (angles.length === 0) angles.push({ angle: 0, cuttingLine: null });
angles.sort((a, b) => a.angle - b.angle);
const closeObject = await utils.suggester(["Yes", "No"], [true, false], "Close object along cutedge?")
ea.style.strokeSharpness = closeObject ? "sharp" : "round";
ea.style.strokeColor = ellipse.strokeColor;
ea.style.strokeWidth = ellipse.strokeWidth;
ea.style.backgroundColor = ellipse.backgroundColor;
ea.style.fillStyle = ellipse.fillStyle;
ea.style.roughness = ellipse.roughness;
angles.forEach((angle, key) => {
const cuttingLine = angle.cuttingLine;
angle = angle.angle;
const nextAngleKey = (key + 1) < angles.length ? key + 1 : 0;
const nextAngle = angles[nextAngleKey].angle;
const AngleDelta = nextAngle - angle ? nextAngle - angle : Math.PI*2;
const pointAmount = Math.ceil((AngleDelta*64)/(Math.PI*2));
const stepSize = AngleDelta/pointAmount;
let points = drawEllipse(ellipse.x, ellipse.y, ellipse.width, ellipse.height, ellipse.angle, angle, nextAngle, stepSize);
if (closeObject && cuttingLine) points = points.concat(getCutLine(points[0], angles[key], angles[nextAngleKey], ellipse));
const lineId = ea.addLine(points);
const line = ea.getElement(lineId);
line.frameId = ellipse.frameId;
line.groupIds = ellipse.groupIds;
});
ea.deleteViewElements([ellipse]);
ea.addElementsToView(false,false,true);
return;
function getSubLines(lines) {
return lines.flatMap((line, key) => {
return line.points.slice(1).map((pointB, i) => ({
a: addVectors([line.points[i], [line.x, line.y]]),
b: addVectors([pointB, [line.x, line.y]]),
originLineIndex: key,
indexPointA: i,
}));
});
}
function intersectionAngleOfEllipseAndLine(ellipse, pointA, pointB) {
/*
To understand the code in this function and subfunctions it might help to take a look at this geogebra file
https://www.geogebra.org/m/apbm3hs6
*/
const c = multiplyVectorByScalar([ellipse.width, ellipse.height], (1/2));
const a = rotateVector(
addVectors([
pointA,
invVec([ellipse.x, ellipse.y]),
invVec(multiplyVectorByScalar([ellipse.width, ellipse.height], (1/2)))
]),
-ellipse.angle
)
const l_b = rotateVector(
addVectors([
pointB,
invVec([ellipse.x, ellipse.y]),
invVec(multiplyVectorByScalar([ellipse.width, ellipse.height], (1/2)))
]),
-ellipse.angle
);
const b = addVectors([
l_b,
invVec(a)
]);
const solutions = calculateLineSegment(a[0], a[1], b[0], b[1], c[0], c[1]);
return solutions
.filter(num => isBetween(num, 0, 1))
.map(num => {
const point = [
(a[0] + b[0] * num) / ellipse.width,
(a[1] + b[1] * num) / ellipse.height
];
return angleBetweenVectors([1, 0], point);
});
}
function drawEllipse(x, y, width, height, angle = 0, start = 0, end = Math.PI*2, step = Math.PI/32) {
const ellipse = (t) => {
const spanningVector = rotateVector([width/2*Math.cos(t), height/2*Math.sin(t)], angle);
const baseVector = [x+width/2, y+height/2];
return addVectors([baseVector, spanningVector]);
}
if(end <= start) end = end + Math.PI*2;
let points = [];
const almostEnd = end - step/2;
for (let t = start; t < almostEnd; t = t + step) {
points.push(ellipse(t));
}
points.push(ellipse(end))
return points;
}
function getCutLine(startpoint, currentAngle, nextAngle, ellipse) {
if (currentAngle.cuttingLine.originLineIndex != nextAngle.cuttingLine.originLineIndex) return [];
const originLineIndex = currentAngle.cuttingLine.originLineIndex;
if (lines[originLineIndex] == 2) return startpoint;
const originLine = [];
lines[originLineIndex].points.forEach(p => originLine.push(addVectors([
p,
[lines[originLineIndex].x, lines[originLineIndex].y]
])));
const edgepoints = [];
const direction = isInEllipse(originLine[clamp(nextAngle.cuttingLine.indexPointA - 1, 0, originLine.length - 1)], ellipse) ? -1 : 1
let i = isInEllipse(originLine[nextAngle.cuttingLine.indexPointA], ellipse) ? nextAngle.cuttingLine.indexPointA : nextAngle.cuttingLine.indexPointA + direction;
while (isInEllipse(originLine[i], ellipse)) {
edgepoints.push(originLine[i]);
i = (i + direction) % originLine.length;
}
edgepoints.push(startpoint);
return edgepoints;
}
function calculateLineSegment(ax, ay, bx, by, cx, cy) {
const sqrt = Math.sqrt((cx ** 2) * (cy ** 2) * (-(ay ** 2) * (bx ** 2) + 2 * ax * ay * bx * by - (ax ** 2) * (by ** 2) + (bx ** 2) * (cy ** 2) + (by ** 2) * (cx ** 2)));
const numerator = -(ay * by * (cx ** 2) + ax * bx * (cy ** 2));
const denominator = ((by ** 2) * (cx ** 2) + (bx ** 2) * (cy ** 2));
const t1 = (numerator + sqrt) / denominator;
const t2 = (numerator - sqrt) / denominator;
return [t1, t2];
}
function isInEllipse(point, ellipse) {
point = addVectors([point, invVec([ellipse.x, ellipse.y]), invVec(multiplyVectorByScalar([ellipse.width, ellipse.height], 1/2))]);
point = [point[0]*2/ellipse.width, point[1]*2/ellipse.height];
const distance = Math.sqrt(point[0]**2 + point[1]**2);
return distance < 1;
}
function angleBetweenVectors(v1, v2) {
let dotProduct = v1[0] * v2[0] + v1[1] * v2[1];
let determinant = v1[0] * v2[1] - v1[1] * v2[0];
let angle = Math.atan2(determinant, dotProduct);
return angle < 0 ? angle + 2 * Math.PI : angle;
}
function rotateVector (vec, ang) {
var cos = Math.cos(ang);
var sin = Math.sin(ang);
return [vec[0] * cos - vec[1] * sin, vec[0] * sin + vec[1] * cos];
}
function addVectors(vectors) {
return vectors.reduce((acc, vec) => [acc[0] + vec[0], acc[1] + vec[1]], [0, 0]);
}
function invVec(vector) {
return [-vector[0], -vector[1]];
}
function multiplyVectorByScalar(vector, scalar) {
return [vector[0] * scalar, vector[1] * scalar];
}
function round(number, precision) {
var factor = Math.pow(10, precision);
return Math.round(number * factor) / factor;
}
function isBetween(num, min, max) {
return (num >= min && num <= max);
}
function clamp(number, min, max) {
return Math.max(min, Math.min(number, max));
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -18,10 +18,10 @@ elements.forEach((el)=>{
ea.style.strokeColor = el.strokeColor;
ea.style.fontFamily = el.fontFamily;
ea.style.fontSize = el.fontSize;
const text = el.text.split("\n");
const text = el.rawText.split("\n");
for(i=0;i<text.length;i++) {
ea.addText(el.x,el.y+i*el.height/text.length,text[i]);
ea.addText(el.x,el.y+i*el.height/text.length,text[i].trim());
}
});
ea.addElementsToView(false,false);
ea.addElementsToView(false,false,true);
ea.deleteViewElements(elements);

View File

@@ -46,4 +46,4 @@ for(i=0;i<el.text.length;i++) {
objectIDs.push(ea.addText(x,y,character));
}
ea.addToGroup(objectIDs);
ea.addElementsToView(true);
ea.addElementsToView(true, false, true);

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" stroke="#000" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><g fill="none"><circle cx="12" cy="12" r="10"></circle><path d="M17 12h.01"></path><path d="M12 12h.01"></path><path d="M7 12h.01"></path></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" stroke="#000" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><g stroke-width="2" fill="none"><circle cx="12" cy="12" r="10"></circle><path d="M17 12h.01"></path><path d="M12 12h.01"></path><path d="M7 12h.01"></path></g></svg>

Before

Width:  |  Height:  |  Size: 309 B

After

Width:  |  Height:  |  Size: 327 B

70
ea-scripts/Text Aura.md Normal file
View File

@@ -0,0 +1,70 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-text-aura.jpg)
Select a single text element, or a text element in a container. The container must have a transparent background.
The script will add an aura to the text by adding 4 copies of the text each with the inverted stroke color of the original text element and with a very small X and Y offset. The resulting 4 + 1 (original) text elements or containers will be grouped.
If you copy a color string on the clipboard before running the script, the script will use that color instead of the inverted color.
```js*/
els = ea.getViewSelectedElements();
const isText = (els.length === 1) && els[0].type === "text";
const isContainer = (els.length === 2) &&
((els[0].type === "text" && els[1].id === els[0].containerId && els[1].backgroundColor.toLowerCase() === "transparent") ||
(els[1].type === "text" && els[0].id === els[1].containerId && els[0].backgroundColor.toLowerCase() === "transparent"));
if (!(isText || isContainer)) {
new Notice ("Select a single text element, or a container with a text element and with transparent background color",10000);
return;
}
let strokeColor = ea
.getCM(els.filter(el=>el.type === "text")[0].strokeColor)
.invert({alpha: false})
.stringHEX({alpha: false});
clipboardText = await navigator.clipboard.readText();
if(clipboardText) {
const cm1 = ea.getCM(clipboardText);
if(cm1.format !== "invalid") {
strokeColor = cm1.stringHEX();
} else {
const cm2 = ea.getCM("#"+clipboardText);
if(cm2.format !== "invalid") {
strokeColor = cm2.stringHEX();
}
}
}
const offset = els.filter(el=>el.type === "text")[0].fontSize/24;
let ids = [];
const addClone = (offsetX, offsetY) => {
els.forEach(el=>{
const clone = ea.cloneElement(el);
ids.push(clone.id);
clone.x += offsetX;
clone.y += offsetY;
if(offsetX!==0 || offsetY!==0) {
switch (clone.type) {
case "text":
clone.strokeColor = strokeColor;
break;
default:
clone.strokeColor = "transparent";
break;
}
}
ea.elementsDict[clone.id] = clone;
})
}
addClone(-offset,0);
addClone(offset,0);
addClone(0,offset);
addClone(0,-offset);
addClone(0,0);
ea.copyViewElementsToEAforEditing(els);
els.forEach(el=>ea.elementsDict[el.id].isDeleted = true);
ea.addToGroup(ids);
ea.addElementsToView(false, true, true);

17
ea-scripts/Text Aura.svg Normal file
View File

@@ -0,0 +1,17 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
<!-- svg-source:excalidraw -->
<defs>
<style class="style-fonts">
@font-face {
font-family: "Virgil";
src: url("https://excalidraw.com/Virgil.woff2");
}
@font-face {
font-family: "Cascadia";
src: url("https://excalidraw.com/Cascadia.woff2");
}
</style>
</defs>
<g stroke-linecap="round"><g transform="translate(0 0) rotate(0 60 60)" fill-rule="evenodd"><path d="M0 0 L120 0 L120 40 L80 40 L80 120 L40 120 L40 40 L0 40 L0 0" stroke="none" stroke-width="0" fill="red" fill-rule="evenodd"></path><path d="M0 0 C41.51 0, 83.02 0, 120 0 M0 0 C30.58 0, 61.16 0, 120 0 M120 0 C120 12.11, 120 24.22, 120 40 M120 0 C120 13.92, 120 27.84, 120 40 M120 40 C108.75 40, 97.49 40, 80 40 M120 40 C107.65 40, 95.29 40, 80 40 M80 40 C80 66.51, 80 93.01, 80 120 M80 40 C80 70.33, 80 100.66, 80 120 M80 120 C66.08 120, 52.16 120, 40 120 M80 120 C70.07 120, 60.13 120, 40 120 M40 120 C40 89.21, 40 58.42, 40 40 M40 120 C40 92.66, 40 65.33, 40 40 M40 40 C25.35 40, 10.71 40, 0 40 M40 40 C27.7 40, 15.4 40, 0 40 M0 40 C0 24.03, 0 8.05, 0 0 M0 40 C0 27.82, 0 15.65, 0 0 M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0" stroke="transparent" stroke-width="0.5" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(110 10) rotate(0 -50 50)" fill-rule="evenodd"><path d="M0 0 L-100 0 L-100 20 L-60 20 L-60 100 L-40 100 L-40 20 L0 20 L0 0" stroke="none" stroke-width="0" fill="currentColor" fill-rule="evenodd"></path><path d="M0 0 C-23.27 0, -46.54 0, -100 0 M0 0 C-23.31 0, -46.62 0, -100 0 M-100 0 C-100 6.13, -100 12.26, -100 20 M-100 0 C-100 5.84, -100 11.69, -100 20 M-100 20 C-87.37 20, -74.74 20, -60 20 M-100 20 C-88.34 20, -76.68 20, -60 20 M-60 20 C-60 37.78, -60 55.56, -60 100 M-60 20 C-60 39.34, -60 58.68, -60 100 M-60 100 C-52.58 100, -45.17 100, -40 100 M-60 100 C-54.72 100, -49.43 100, -40 100 M-40 100 C-40 83.83, -40 67.67, -40 20 M-40 100 C-40 77.76, -40 55.51, -40 20 M-40 20 C-25.4 20, -10.8 20, 0 20 M-40 20 C-28.47 20, -16.93 20, 0 20 M0 20 C0 15.42, 0 10.84, 0 0 M0 20 C0 14.54, 0 9.08, 0 0 M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0" stroke="currentColor" stroke-width="0.5" fill="none"></path></g></g><mask></mask></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,126 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-sticky-note-matrix.jpg)
Converts selected plain text element to sticky notes by dividing the text element line by line into separate sticky notes. The color of the stikcy note as well as the arrangement of the grid can be configured in plugin settings.
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.21")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
let settings = ea.getScriptSettings();
//set default values on first run
if(!settings["Border color"]) {
settings = {
"Border color" : {
value: "black",
description: "Any legal HTML color (#000000, rgb, color-name, etc.). Set to 'transparent' for transparent color."
},
"Background color" : {
value: "gold",
description: "Background color of the sticky note. Set to 'transparent' for transparent color."
},
"Background fill style" : {
value: "solid",
description: "Fill style of the sticky note",
valueset: ["hachure","cross-hatch","solid"]
}
};
await ea.setScriptSettings(settings);
}
if(!settings["Max sticky note width"]) {
settings["Max sticky note width"] = {
value: "600",
description: "Maximum width of new sticky note. If text is longer, it will be wrapped",
valueset: ["400","600","800","1000","1200","1400","2000"]
}
await ea.setScriptSettings(settings);
}
if(!settings["Sticky note width"]) {
settings["Sticky note width"] = {
value: "100",
description: "Preferred width of the sticky note. Set to 0 if unset.",
}
settings["Sticky note height"] = {
value: "120",
description: "Preferred height of the sticky note. Set to 0 if unset.",
}
settings["Rows per column"] = {
value: "3",
description: "If multiple text elements are converted to sticky notes in one step, how many rows before a next column is created. Only effective if fixed width & height are given. 0 for unset.",
}
settings["Gap"] = {
value: "10",
description: "Gap between rows and columns",
}
await ea.setScriptSettings(settings);
}
const pref_width = parseInt(settings["Sticky note width"].value);
const pref_height = parseInt(settings["Sticky note height"].value);
const pref_rows = parseInt(settings["Rows per column"].value);
const pref_gap = parseInt(settings["Gap"].value);
const maxWidth = parseInt(settings["Max sticky note width"].value);
const strokeColor = settings["Border color"].value;
const backgroundColor = settings["Background color"].value;
const fillStyle = settings["Background fill style"].value;
elements = ea.getViewSelectedElements().filter((el)=>el.type==="text");
elements.forEach((el)=>{
ea.style.strokeColor = el.strokeColor;
ea.style.fontFamily = el.fontFamily;
ea.style.fontSize = el.fontSize;
const text = el.text.split("\n");
for(i=0;i<text.length;i++) {
ea.addText(el.x,el.y+i*el.height/text.length,text[i].trim());
}
});
ea.deleteViewElements(elements);
ea.style.strokeColor = strokeColor;
ea.style.backgroundColor = backgroundColor;
ea.style.fillStyle = fillStyle;
const padding = 6;
const boxes = [];
const doMatrix = pref_width > 0 && pref_height > 0 && pref_rows > 0 && pref_gap > 0;
let row = 0;
let col = doMatrix ? -1 : 0;
ea.getElements().forEach((el, idx)=>{
if(doMatrix) {
if(idx % pref_rows === 0) {
row=0;
col++;
} else {
row++;
}
}
const width = pref_width > 0 ? pref_width : el.width+2*padding;
const widthOK = pref_width > 0 || width<=maxWidth;
const id = ea.addRect(
(doMatrix?col*pref_width+col*pref_gap:0)+el.x-padding,
(doMatrix?row*pref_height+row*pref_gap:0),
widthOK?width:maxWidth,pref_height > 0 ? pref_height : el.height+2*padding
);
boxes.push(id);
ea.getElement(id).boundElements=[{type:"text",id:el.id}];
el.containerId = id;
});
const els = Object.entries(ea.elementsDict);
let newEls = [];
for(i=0;i<els.length/2;i++) {
newEls.push(els[els.length/2+i]);
newEls.push(els[i])
}
ea.elementsDict = Object.fromEntries(newEls);
await ea.addElementsToView(false,true);
const containers = ea.getViewElements().filter(el=>boxes.includes(el.id));
ea.getExcalidrawAPI().updateContainerSize(containers);
ea.selectElementsInView(containers);

View File

@@ -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"><circle stroke-width="2" cx="12" cy="5" r="1"></circle><circle stroke-width="2" cx="19" cy="5" r="1"></circle><circle stroke-width="2" cx="5" cy="5" r="1"></circle><circle stroke-width="2" cx="12" cy="12" r="1"></circle><circle stroke-width="2" cx="19" cy="12" r="1"></circle><circle stroke-width="2" cx="5" cy="12" r="1"></circle><circle stroke-width="2" cx="12" cy="19" r="1"></circle><circle stroke-width="2" cx="19" cy="19" r="1"></circle><circle stroke-width="2" cx="5" cy="19" r="1"></circle></svg>

After

Width:  |  Height:  |  Size: 685 B

33
ea-scripts/Toggle Grid.md Normal file
View File

@@ -0,0 +1,33 @@
/*
Toggles the grid on and off. Especially useful when drawing with just a pen without a mouse or keyboard, as toggling the grid by left-clicking with the pen is sometimes quite tedious.
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.8.11")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
const api = ea.getExcalidrawAPI();
let {gridSize, previousGridSize} = api.getAppState();
if (!previousGridSize) {
previousGridSize = 20
}
if (!gridSize) {
gridSize = previousGridSize;
}
else
{
previousGridSize = gridSize;
gridSize = null;
}
ea.viewUpdateScene({
appState:{
gridSize,
previousGridSize
},
commitToHistory:false
});

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 567 489">
<path
d="M 20.803582,0.35478208 A 25,25 0 0 0 5.9442069,8.8176728 25,25 0 0 0 8.8172543,44.055954 L 31.254754,63.108689 c -0.121266,0.849954 -0.301322,1.680716 -0.388672,2.541015 -0.218469,2.151668 -0.330078,4.335551 -0.330078,6.544922 V 392.19462 c 0,2.20625 0.111609,4.38587 0.330078,6.53516 0.218468,2.14929 0.544482,4.26807 0.970703,6.34961 0.426219,2.08154 0.952918,4.12591 1.576172,6.12891 0.623252,2.00299 1.342775,3.96328 2.152343,5.87695 0.80957,1.91367 1.708193,3.7802 2.69336,5.59375 0.985166,1.81355 2.056984,3.57472 3.207031,5.27734 1.150047,1.70264 2.379385,3.34681 3.683594,4.92774 1.304208,1.58093 2.683206,3.09844 4.130859,4.54687 1.447654,1.44844 2.964542,2.82767 4.544922,4.13282 1.58038,1.30514 3.223392,2.53642 4.925781,3.6875 1.70239,1.15106 3.463664,2.22277 5.277344,3.20898 1.81368,0.98621 3.679496,1.88672 5.59375,2.69727 1.914254,0.81053 3.87675,1.5302 5.880859,2.15429 2.00411,0.6241 4.049565,1.15323 6.132813,1.58008 2.083248,0.42686 4.203801,0.75188 6.355469,0.9707 2.151667,0.21883 4.335552,0.33204 6.544922,0.33203 H 478.53601 c 2.20625,0 4.38587,-0.1132 6.53515,-0.33203 2.14929,-0.21882 4.26808,-0.54384 6.34961,-0.9707 0.30707,-0.063 0.59887,-0.16503 0.9043,-0.23242 l 33.48047,28.43164 a 25,25 0 0 0 35.23828,-2.87305 25,25 0 0 0 -2.87305,-35.23828 L 41.182488,5.9446259 A 25,25 0 0 0 29.485222,0.40556338 25,25 0 0 0 20.803582,0.35478208 Z M 94.536004,8.1946259 c -2.209366,0 -4.39326,0.1116097 -6.544922,0.3300781 -2.151664,0.2184684 -4.272226,0.5425319 -6.355469,0.9687499 -2.083244,0.42622 -4.128707,0.9548741 -6.132813,1.5781251 -2.004105,0.623253 -3.966609,1.340824 -5.880859,2.150391 -0.337447,0.142712 -0.651869,0.326303 -0.986328,0.474609 l 68.884767,58.498047 h 93.49024 23.52539 v 19.978516 79.392578 l 109.07422,92.6289 h 93.49218 21.4336 v 18.20313 79.39258 l 60.42383,51.31445 c 0.22119,-0.63745 0.49391,-1.25011 0.69531,-1.89648 0.62409,-2.00299 1.15127,-4.04738 1.57812,-6.12891 0.42686,-2.08153 0.75188,-4.20033 0.97071,-6.34961 0.21882,-2.14928 0.33203,-4.32892 0.33203,-6.53516 V 336.74736 271.15166 72.194626 c 0,-2.209349 -0.11321,-4.393275 -0.33203,-6.544922 -0.21882,-2.151647 -0.54386,-4.272242 -0.97071,-6.355469 -0.42685,-2.083227 -0.95403,-4.128722 -1.57812,-6.132812 -0.62409,-2.00409 -1.34376,-3.966624 -2.1543,-5.88086 -0.81054,-1.914234 -1.71302,-3.780086 -2.69922,-5.59375 -0.98619,-1.813662 -2.05792,-3.574971 -3.20898,-5.277343 -1.15106,-1.702373 -2.38041,-3.34737 -3.68555,-4.927735 -1.30514,-1.580364 -2.68439,-3.097282 -4.13281,-4.544922 -1.44842,-1.447638 -2.96596,-2.82471 -4.54688,-4.128906 -1.5809,-1.304195 -3.22708,-2.535511 -4.92968,-3.685547 -1.70262,-1.150034 -3.46187,-2.219921 -5.27539,-3.205078 -1.81353,-0.985156 -3.68011,-1.885752 -5.59375,-2.695312 -1.91366,-0.809561 -3.87593,-1.527143 -5.87891,-2.150391 -2.00298,-0.623246 -4.04739,-1.1519091 -6.12891,-1.5781251 -2.08151,-0.426215 -4.20034,-0.7502832 -6.34961,-0.9687499 -2.14925,-0.2184666 -4.32893,-0.3300781 -6.53515,-0.3300781 H 232.88952 155.64538 Z M 318.53601,72.194626 h 160 V 200.19462 H 458.97937 381.73718 318.53601 V 146.52275 80.927048 Z M 94.536004,116.84892 192.67859,200.19462 H 94.536004 Z m 0,147.3457 H 254.53601 v 128 H 94.536004 Z m 224.000006,42.87891 100.23437,85.12109 H 318.53601 Z" />
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -0,0 +1,52 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-uniform-size.jpg)
The script will standardize the sizes of rectangles, diamonds and ellipses adjusting all the elements to match the largest width and height within the group.
```javascript
*/
const boxShapesDispaly=["○ ellipse","□ rectangle","◇ diamond"];
const boxShapes=["ellipse","rectangle","diamond"];
let editedElements = [];
const elements = ea.getViewSelectedElements().filter(el=>boxShapes.contains(el.type));
if(elements.length===0) {
new Notice("No rectangle, or diamond or ellipse elements are selected. Please select some elements");
return;
}
const typeSet = new Set();
elements.forEach(el=>typeSet.add(el.type));
const elementType = await utils.suggester(
Array.from(typeSet).map((item) => {
switch(item) {
case "ellipse": return "○ ellipse";
case "rectangle": return "□ rectangle";
case "diamond": return "◇ diamond";
default: return item;
}
}),
Array.from(typeSet),
"Select element types to resize"
);
if(!elementType) return;
ea.copyViewElementsToEAforEditing(elements.filter(el=>el.type===elementType));
let width = height = 0;
ea.getElements().forEach(el=>{
if(el.width>width) width = el.width;
if(el.height>height) height = el.height;
})
ea.getElements().forEach(el=>{
el.width = width;
el.height = height;
})
const ids = ea.getElements().map(el=>el.id);
await ea.addElementsToView(false,true);
ea.getExcalidrawAPI().updateContainerSize(ea.getViewElements().filter(el=>ids.contains(el.id)));

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M40 352c-22.1 0-40 17.9-40 40l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48c0-22.1-17.9-40-40-40l-48 0zm192 0c-22.1 0-40 17.9-40 40l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48c0-22.1-17.9-40-40-40l-48 0zM40 320l48 0c22.1 0 40-17.9 40-40l0-48c0-22.1-17.9-40-40-40l-48 0c-22.1 0-40 17.9-40 40l0 48c0 22.1 17.9 40 40 40zM232 192c-22.1 0-40 17.9-40 40l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48c0-22.1-17.9-40-40-40l-48 0zM40 160l48 0c22.1 0 40-17.9 40-40l0-48c0-22.1-17.9-40-40-40L40 32C17.9 32 0 49.9 0 72l0 48c0 22.1 17.9 40 40 40zM232 32c-22.1 0-40 17.9-40 40l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48c0-22.1-17.9-40-40-40l-48 0z"/></svg>

After

Width:  |  Height:  |  Size: 930 B

File diff suppressed because one or more lines are too long

View File

@@ -15,7 +15,7 @@ In the `Command Palette` installed scripts are prefixed with `Downloaded/`, thus
## Attention developers and hobby hackers
<img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/hobby-programmer.svg' align='left' style='background-color:whitesmoke; width:80px; margin-right:15px; margin-bottom:10px;'/>
If you want to modify scripts, I recommend moving them to the `Excalidraw Automate script folder` or a different subfolder under the script folder. Scripts in the `Downloaded` folder will be overwritten when you click the `Update this script` button. Note also, that at this time, I do not check if the script file has been updated on GitHub, thus the `Update this script` button is always visible once you have installed a script, not only when an update is availble (hope to build this feature in the future).
If you want to modify scripts, I recommend moving them to the `Excalidraw Automate script folder` or a different subfolder under the script folder. Scripts in the `Downloaded` folder will be overwritten when you click the `Update this script` button. Note also, that at this time, I do not check if the script file has been updated on GitHub, thus the `Update this script` button is always visible once you have installed a script, not only when an update is available (hope to build this feature in the future).
I would love to include your contribution in the script library. If you have a script of your own that you would like to share with the community, please open a [PR](https://github.com/zsviczian/obsidian-excalidraw-plugin/pulls) on GitHub. Be sure to include the following in your pull request
- The [script file](https://github.com/zsviczian/obsidian-excalidraw-plugin/tree/master/ea-scripts) with a self explanetory name. The name of the file will be the name of the script in the Command Palette.
@@ -25,24 +25,16 @@ I would love to include your contribution in the script library. If you have a s
---
# List of available scripts
## Layout and Organization
**Keywords**: Design, Placement, Arrangement, Structure, Formatting, Alignment
| | |
|----|-----|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Connector%20Point.svg"></div>|[[#Add Connector Point]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Link%20to%20Existing%20File%20and%20Open.svg"/></div>|[[#Add Link to Existing File and Open]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Link%20to%20New%20Page%20and%20Open.svg"/></div>|[[#Add Link to New Page and Open]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Next%20Step%20in%20Process.svg"/></div>|[[#Add Next Step in Process]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Auto%20Layout.svg"/></div>|[[#Auto Layout]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Box%20Each%20Selected%20Groups.svg"/></div>|[[#Box Each Selected Groups]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Box%20Selected%20Elements.svg"/></div>|[[#Box Selected Elements]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Change%20shape%20of%20selected%20elements.svg"/></div>|[[#Change shape of selected elements]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Connect%20elements.svg"/></div>|[[#Connect elements]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Convert%20freedraw%20to%20line.svg"/></div>|[[#Convert freedraw to line]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Convert%20selected%20text%20elements%20to%20sticky%20notes.svg"/></div>|[[#Convert selected text elements to sticky notes]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Convert%20text%20to%20link%20with%20folder%20and%20alias.svg"/></div>|[[#Convert text to link with folder and alias]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Copy%20Selected%20Element%20Styles%20to%20Global.svg"/></div>|[[#Copy Selected Element Styles to Global]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Create%20new%20markdown%20file%20and%20embed%20into%20active%20drawing.svg"/></div>|[[#Create new markdown file and embed into active drawing]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Darken%20background%20color.svg"/></div>|[[#Darken background color]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Deconstruct%20selected%20elements%20into%20new%20drawing.svg"/></div>|[[#Deconstruct selected elements into new drawing]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Elbow%20connectors.svg"/></div>|[[#Elbow connectors]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Ellipse%20Selected%20Elements.svg"/></div>|[[#Ellipse Selected Elements]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Expand%20rectangles%20horizontally%20keep%20text%20centered.svg"/></div>|[[#Expand rectangles horizontally keep text centered]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Expand%20rectangles%20horizontally.svg"/></div>|[[#Expand rectangles horizontally]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Expand%20rectangles%20vertically%20keep%20text%20centered.svg"/></div>|[[#Expand rectangles vertically keep text centered]]|
@@ -52,31 +44,119 @@ I would love to include your contribution in the script library. If you have a s
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20spacing.svg"/></div>|[[#Fixed spacing]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20vertical%20distance%20between%20centers.svg"/></div>|[[#Fixed vertical distance between centers]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20vertical%20distance.svg"/></div>|[[#Fixed vertical distance]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Golden%20Ratio.svg"/></div>|[[#Golden Ratio]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Grid%20Selected%20Images.svg"/></div>|[[#Grid selected images]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Lighten%20background%20color.svg"/></div>|[[#Lighten background color]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Mindmap%20format.svg"/></div>|[[#Mindmap format]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Zoom%20to%20Fit%20Selected%20Elements.svg"/></div>|[[#Zoom to Fit Selected Elements]]|
## Connectors and Arrows
**Keywords**: Links, Relations, Paths, Direction, Flow, Connections
| | |
|----|-----|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Connector%20Point.svg"></div>|[[#Add Connector Point]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Concatenate%20lines.svg"></div>|[[#Concatenate lines]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Connect%20elements.svg"/></div>|[[#Connect elements]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Elbow%20connectors.svg"/></div>|[[#Elbow connectors]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Mindmap%20connector.svg"/></div>|[[#Mindmap connector]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Modify%20background%20color%20opacity.svg"/></div>|[[#Modify background color opacity]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Normalize%20Selected%20Arrows.svg"/></div>|[[#Normalize Selected Arrows]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/OCR%20-%20Optical%20Character%20Recognition.svg"/></div>|[[#OCR - Optical Character Recognition]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Organic%20Line.svg"/></div>|[[#Organic Line]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Palette%20loader.svg"/></div>|[[#Palette Loader]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Rename%20Image.svg"/></div>|[[#Rename Image]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Repeat%20Elements.svg"/></div>|[[#Repeat Elements]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Reverse%20arrows.svg"/></div>|[[#Reverse arrows]]|
## Text Manipulation
**Keywords**: Editing, Font Control, Wording, Typography, Annotation, Modification
| | |
|----|-----|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Convert%20selected%20text%20elements%20to%20sticky%20notes.svg"/></div>|[[#Convert selected text elements to sticky notes]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Relative%20Font%20Size%20Cycle.svg"/></div>|[[#Relative Font Size Cycle]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Scribble%20Helper.svg"/></div>|[[#Scribble Helper]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Select%20Elements%20of%20Type.svg"/></div>|[[#Select Elements of Type]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20background%20color%20of%20unclosed%20line%20object%20by%20adding%20a%20shadow%20clone.svg"/></div>|[[#Set background color of unclosed line object by adding a shadow clone]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Dimensions.svg"/></div>|[[#Set Dimensions]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Font%20Family.svg"/></div>|[[#Set Font Family]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Grid.svg"/></div>|[[#Set Grid]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Link%20Alias.svg"/></div>|[[#Set Link Alias]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Stroke%20Width%20of%20Selected%20Elements.svg"/></div>|[[#Set Stroke Width of Selected Elements]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Text%20Alignment.svg"/></div>|[[#Set Text Alignment]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Slideshow.svg"/></div>|[[#Slideshow]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Split%20text%20by%20lines.svg"/></div>|[[#Split text by lines]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Text%20Arch.svg"/></div>|[[#Text Arch]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Transfer%20TextElements%20to%20Excalidraw%20markdown%20metadata.svg"/></div>|[[#Transfer TextElements to Excalidraw markdown metadata]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Zoom%20to%20Fit%20Selected%20Elements.svg"/></div>|[[#Zoom to Fit Selected Elements]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Text%20Aura.svg"/></div>|[[#Text Aura]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Text%20to%20Sticky%20Notes.svg"/></div>|[[#Text to Sticky Notes]]|
## Styling and Appearance
**Keywords**: Design, Look, Visuals, Graphics, Aesthetics, Presentation
| | |
|----|-----|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Change%20shape%20of%20selected%20elements.svg"/></div>|[[#Change shape of selected elements]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Darken%20background%20color.svg"/></div>|[[#Darken background color]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Invert%20colors.svg"/></div>|[[#Invert colors]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Lighten%20background%20color.svg"/></div>|[[#Lighten background color]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Modify%20background%20color%20opacity.svg"/></div>|[[#Modify background color opacity]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Organic%20Line.svg"/></div>|[[#Organic Line]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Organic%20Line%20Legacy.svg"/></div>|[[#Organic Line Legacy]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20background%20color%20of%20unclosed%20line%20object%20by%20adding%20a%20shadow%20clone.svg"/></div>|[[#Set background color of unclosed line object by adding a shadow clone]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Dimensions.svg"/></div>|[[#Set Dimensions]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Grid.svg"/></div>|[[#Set Grid]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Stroke%20Width%20of%20Selected%20Elements.svg"/></div>|[[#Set Stroke Width of Selected Elements]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Toggle%20Grid.svg"/></div>|[[#Toggle Grid]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Uniform%20size.svg"/></div>|[[#Uniform Size]]|
## Linking and Embedding
**Keywords**: Attach, Incorporate, Integrate, Associate, Insert, Reference
| | |
|----|-----|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Link%20to%20Existing%20File%20and%20Open.svg"/></div>|[[#Add Link to Existing File and Open]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Link%20to%20New%20Page%20and%20Open.svg"/></div>|[[#Add Link to New Page and Open]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Convert%20text%20to%20link%20with%20folder%20and%20alias.svg"/></div>|[[#Convert text to link with folder and alias]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Create%20DrawIO%20file.svg"/></div>|[[#Create DrawIO file]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Create%20new%20markdown%20file%20and%20embed%20into%20active%20drawing.svg"/></div>|[[#Create new markdown file and embed into active drawing]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Folder%20Note%20Core%20-%20Make%20Current%20Drawing%20a%20Folder.svg"/></div>|[[#Folder Note Core - Make Current Drawing a Folder]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Link%20Alias.svg"/></div>|[[#Set Link Alias]]|
## Utilities and Tools
**Keywords**: Functionalities, Instruments, Helpers, Aids, Features, Enhancements
| | |
|----|-----|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Auto%20Draw%20for%20Pen.svg"/></div>|[[#Auto Draw for Pen]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Boolean%20Operations.svg"/></div>|[[#Boolean Operations]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Custom%20Zoom.svg"/></div>|[[#Custom Zoom]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Copy%20Selected%20Element%20Styles%20to%20Global.svg"/></div>|[[#Copy Selected Element Styles to Global]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/ExcaliAI.svg"/></div>|[[#ExcaliAI]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/GPT-Draw-a-UI.svg"/></div>|[[#GPT Draw-a-UI]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Hardware%20Eraser%20Support.svg"/></div>|[[#Hardware Eraser Support]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Palette%20loader.svg"/></div>|[[#Palette Loader]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/PDF%20Page%20Text%20to%20Clipboard.svg"/></div>|[[#PDF Page Text to Clipboard]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Rename%20Image.svg"/></div>|[[#Rename Image]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Repeat%20Elements.svg"/></div>|[[#Repeat Elements]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Repeat%20Texts.svg"/></div>|[[#Repeat Texts]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Select%20Elements%20of%20Type.svg"/></div>|[[#Select Elements of Type]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Select%20Similar%20Elements.svg"/></div>|[[#Select Similar Elements]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Slideshow.svg"/></div>|[[#Slideshow]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Split%20Ellipse.svg"/></div>|[[#Split Ellipse]]|
## Collaboration and Export
**Keywords**: Sharing, Teamwork, Exporting, Distribution, Cooperative, Publish
| | |
|----|-----|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Excalidraw%20Collaboration%20Frame.svg"/></div>|[[#Excalidraw Collaboration Frame]]|
## Conversation and Creation
**Keywords**: Transform, Generate, Craft, Produce, Change, Originate
| | |
|----|-----|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Next%20Step%20in%20Process.svg"/></div>|[[#Add Next Step in Process]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Convert%20freedraw%20to%20line.svg"/></div>|[[#Convert freedraw to line]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Deconstruct%20selected%20elements%20into%20new%20drawing.svg"/></div>|[[#Deconstruct selected elements into new drawing]]|
## Masking and cropping
**Keywords**: Crop, Mask, Transform images
| | |
|----|-----|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Crop%20Vintage%20Mask.svg"/></div>|[[#Crop Vintage Mask]]|
---
# Description and Installation
## Add Connector Point
```excalidraw-script-install
@@ -102,6 +182,25 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Add%20Next%20Step%20in%20Process.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will prompt you for the title of the process step, then will create a stick note with the text. If an element is selected then the script will connect this new step with an arrow to the previous step (the selected element). If no element is selected, then the script assumes this is the first step in the process and will only output the sticky note with the text that was entered.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-process-step.jpg'></td></tr></table>
## Auto Draw for Pen
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Auto%20Draw%20for%20Pen.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/threethan'>@threethan</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Auto%20Draw%20for%20Pen.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Automatically switches from select mode to drawing mode when hovering a pen, and then back.</td></tr></table>
## Auto Layout
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Auto%20Layout.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Auto%20Layout.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script performs automatic layout for the selected top-level grouping objects. It is powered by <a href='https://github.com/kieler/elkjs'>elkjs</a> and needs to be connected to the Internet.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-auto-layout.png'></td></tr></table>
## Boolean Operations
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Boolean%20Operations.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/GColoy'>@GColoy</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Boolean%20Operations.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">With This Script it is possible to make boolean Operations on Shapes.<br>The style of the resulting shape will be the style of the highest ranking Element that was used.<br>The ranking of the elements is based on their background. The "denser" the background, the higher the ranking (the order of backgroundstyles is shown below). If they have the same background the opacity will decide. If thats also the same its decided by the order they were created.<br>The ranking is also important for the difference operation, so a transparent object for example will cut a hole into a solid object.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-boolean-operations-showcase.png'><br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-boolean-operations-element-ranking.png'></td></tr></table>
## Box Each Selected Groups
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Box%20Each%20Selected%20Groups.md
@@ -120,6 +219,12 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Change%20shape%20of%20selected%20elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script allows you to change the shape and fill style of selected Rectangles, Diamonds, Ellipses, Lines, Arrows and Freedraw.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-change-shape.jpg'></td></tr></table>
## Concatenate lines
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Concatenate%20lines.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Concatenate%20lines.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will connect two objects with an arrow. If either of the objects are a set of grouped elements (e.g. a text element grouped with an encapsulating rectangle), the script will identify these groups, and connect the arrow to the largest object in the group (assuming you want to connect the arrow to the box around the text element).<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-concatenate-lines.png'></td></tr></table>
## Connect elements
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Connect%20elements.md
@@ -150,12 +255,30 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Copy%20Selected%20Element%20Styles%20to%20Global.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will copy styles of any selected element into Excalidraw's global styles.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-copy-selected-element-styles-to-global.png'></td></tr></table>
## Create DrawIO file
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Create%20DrawIO%20file.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Create%20DrawIO%20file.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script will prompt you for a filename, then create a new draw.io diagram file and open the file in the <a href='https://github.com/zapthedingbat/drawio-obsidian'>Diagram plugin</a>, in a new tab.<br><iframe width="400" height="225" src="https://www.youtube.com/embed/DJcosmN-q2s" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></td></tr></table>
## Create new markdown file and embed into active drawing
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Create%20new%20markdown%20file%20and%20embed%20into%20active%20drawing.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Create%20new%20markdown%20file%20and%20embed%20into%20active%20drawing.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script will prompt you for a filename, then create a new markdown document with the file name provided, open the new markdown document in an adjacent pane, and embed the markdown document into the active Excalidraw drawing.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-create-and-embed-new-markdown-file.jpg'></td></tr></table>
## Crop Vintage Mask
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Crop%20Vintage%20Mask.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Crop%20Vintage%20Mask.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Adds a rounded mask to the image by adding a full cover black mask and a rounded rectangle white mask. The script is also useful for adding just a black mask. In this case, run the script, then delete the white mask and add your custom white mask.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-crop-vintage.jpg'></td></tr></table>
## Custom Zoom
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Custom%20Zoom.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Custom%20Zoom.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">You can set a custom zoom level with this script. This allows you to set a zoom level below 10% or set the zoom level to a specific value. Note however, that Excalidraw has a bug under 10% zoom... a phantom copy of your image may appear on screen. If this happens, increase the zoom and the phantom should disappear, if it doesn't, then close and open the drawing.</td></tr></table>
## Darken background color
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Darken%20background%20color.md
@@ -166,7 +289,7 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Deconstruct%20selected%20elements%20into%20new%20drawing.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Deconstruct%20selected%20elements%20into%20new%20drawing.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Select some elements in the scene. The script will take these elements and move them into a new Excalidraw file, and open that file. The selected elements will also be replaced in your original drawing with the embedded Excalidraw file (the one that was just created). You will be prompted for the file name of the new deconstructed image. The script is useful if you want to break a larger drawing into smaller reusable parts that you want to reference in multiple drawings.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-deconstruct.jpg'></td></tr></table>
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Deconstruct%20selected%20elements%20into%20new%20drawing.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Select some elements in the scene. The script will take these elements and move them into a new Excalidraw file, and open that file. The selected elements will also be replaced in your original drawing with the embedded Excalidraw file (the one that was just created). You will be prompted for the file name of the new deconstructed image. The script is useful if you want to break a larger drawing into smaller reusable parts that you want to reference in multiple drawings.<br><iframe width="400" height="225" src="https://www.youtube.com/embed/HRtaaD34Zzg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-deconstruct.jpg'></td></tr></table>
## Elbow connectors
```excalidraw-script-install
@@ -174,6 +297,18 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Elbow%20connectors.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script converts the selected connectors to elbows.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/elbow-connectors.png'></td></tr></table>
## Ellipse Selected Elements
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Ellipse%20Selected%20Elements.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/mazurov'>@mazurov</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Ellipse%20Selected%20Elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will add an encapsulating ellipse around the currently selected elements in Excalidraw.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-ellipse-elements.png'></td></tr></table>
## Excalidraw Collaboration Frame
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Excalidraw%20Collaboration%20Frame.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Excalidraw%20Collaboration%20Frame.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Creates a new Excalidraw.com collaboration room and places the link to the room on the clipboard.<iframe width="400" height="225" src="https://www.youtube.com/embed/7isRfeAhEH4" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></td></tr></table>
## Expand rectangles horizontally keep text centered
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Expand%20rectangles%20horizontally%20keep%20text%20centered.md
@@ -228,12 +363,51 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Fixed%20vertical%20distance.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script arranges the selected elements vertically with a fixed spacing. When we create an architecture diagram or mind map, we often need to arrange a large number of elements in a fixed spacing. `Fixed spacing` and `Fixed vertical Distance` scripts can save us a lot of time.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fixed-vertical-distance.png'></td></tr></table>
## Folder Note Core - Make Current Drawing a Folder
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Folder%20Note%20Core%20-%20Make%20Current%20Drawing%20a%20Folder.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Folder%20Note%20Core%20-%20Make%20Current%20Drawing%20a%20Folder.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script adds the `Folder Note Core: Make current document folder note` function to Excalidraw drawings. Running this script will convert the active Excalidraw drawing into a folder note. If you already have embedded images in your drawing, those attachments will not be moved when the folder note is created. You need to take care of those attachments separately, or convert the drawing to a folder note prior to adding the attachments. The script requires the <a href="https://github.com/aidenlx/folder-note-core" target="_blank">Folder Note Core</a> plugin.</td></tr></table>
## Golden Ratio
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Golden%20Ratio.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Golden%20Ratio.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script performs two different functions depending on the elements selected in the view.<br>
1) In case you select text elements, the script will cycle through a set of font scales. First the 2 larger fonts following the Fibonacci sequence (fontsize * φ; fonsize * φ^2), then the 2 smaller fonts (fontsize / φ; fontsize / φ^2), finally the original size, followed again by the 2 larger fonts. If you wait 2 seconds, the sequence clears and starts from which ever font size you are on. So if you want the 3rd larges font, then toggle twice, wait 2 sec, then toggle again.<br>
2) In case you select a single rectangle, the script will open the "Golden Grid", "Golden Spiral" window, where you can set up the type of grid or spiral you want to insert into the document.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/golden-ratio.jpg'><br><iframe width="400" height="225" src="https://www.youtube.com/embed/2SHn_ruax-s" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></td></tr></table>
## Grid selected images
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Grid%20Selected%20Images.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/7flash'>@7flash</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Grid%20Selected%20Images.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script arranges selected images into compact grid view, removing gaps in-between, resizing when necessary and breaking into multiple rows/columns.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-grid-selected-images.png'></td></tr></table>
## ExcaliAI
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/ExcaliAI.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/ExcaliAI.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Various AI features based on GPT Vision.<br><iframe width="400" height="225" src="https://www.youtube.com/embed/A1vrSGBbWgo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-draw-a-ui.jpg'></td></tr></table>
## GPT Draw-a-UI
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/GPT-Draw-a-UI.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/GPT-Draw-a-UI.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script was discontinued in favor of ExcaliAI. Draw a UI and let GPT create the code for you.<br><iframe width="400" height="225" src="https://www.youtube.com/embed/y3kHl_6Ll4w" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-draw-a-ui.jpg'></td></tr></table>
## Hardware Eraser Support
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Hardware%20Eraser%20Support.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/threethan'>@threethan</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Hardware%20Eraser%20Support.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Allows you to use inversion, aka hardware eraser, on supported pens.</td></tr></table>
## Invert colors
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Invert%20colors.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Invert%20colors.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script inverts the colors on the canvas including the color palette in Element Properties.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-invert-colors.jpg'></td></tr></table>
## Lighten background color
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Lighten%20background%20color.md
@@ -246,6 +420,12 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/xllowl'>@xllowl</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Mindmap%20connector.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script creates mindmap like lines (only right side and down available currently) for selected elements. The line will start according to the creation time of the elements. So you should create the header element first.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/mindmap%20connector.png'></td></tr></table>
## Mindmap format
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Mindmap%20format.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/pandoralink'>@pandoralink</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Mindmap%20format.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Automatically formats a mindmap from left to right based on the creation sequence of arrows.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-mindmap-format-1.png'><br>A mindmap is actually a tree, so you must have a <b>root node</b>. The script will determine <b>the leftmost element</b> of the selected element as the root element (the node must be a rectangle, diamond, ellipse, text, image, but it can't be an arrow, line, freedraw, or <b>group</b>)<br>The element connecting node and node must be an <b>arrow</b> and have the correct direction, e.g. <b>parent node -> child node</b>.<br>The order of nodes in the Y axis or vertical direction is determined by <b>the creation time</b> of the arrow connecting it.<br><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-mindmap-format-2.png"><br>If you want to readjust the order, you can <b>delete arrows and reconnect them</b>.<br>The script provides options to adjust the style of the mindmap. Options are at the bottom of excalidraw plugin options (Settings -> Community plugins -> Excalidraw -> drag to bottom).<br>Since the start bingding and end bingding of the arrows are easily disconnected from the node, if there are unformatted parts, please <b>check the connection</b> and use the script to <b>reformat</b>.</td></tr></table>
## Modify background color opacity
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Modify%20background%20color%20opacity.md
@@ -258,17 +438,17 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Normalize%20Selected%20Arrows.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will reset the start and end positions of the selected arrows. The arrow will point to the center of the connected box and will have a gap of 8px from the box.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-normalize-selected-arrows.png'></td></tr></table>
## OCR - Optical Character Recognition
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/OCR%20-%20Optical%20Character%20Recognition.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/OCR%20-%20Optical%20Character%20Recognition.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">REQUIRES EXCALIDRAW 1.5.15<br>The script will 1) send the selected image file to [taskbone.com](https://taskbone.com) to exctract the text from the image, and 2) will add the text to your drawing as a text element.<br><mark>⚠ Note that you will need to manually paste your token into the script after the first run! ⚠</mark><br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-ocr.jpg'><br><iframe width="560" height="315" src="https://www.youtube.com/embed/W2NMzR8s4eE" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></td></tr></table>
## Organic Line
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Organic%20Line.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Organic%20Line.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Converts selected freedraw lines such that pencil pressure will decrease from maximum to minimum from the beginning of the line to its end. The resulting line is placed at the back of the layers, under all other items. Helpful when drawing organic mindmaps.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-organic-line.jpg'></td></tr></table>
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Organic%20Line.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Converts selected freedraw lines such that pencil pressure will decrease from maximum to minimum from the beginning of the line to its end. The resulting line is placed at the back of the layers, under all other items. Helpful when drawing organic mindmaps.<br>The script has been superseded by Custom Pens that you can enable in plugin settings. Find out more by watching this <a href="https://youtu.be/OjNhjaH2KjI" target="_blank">video</a><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-organic-line.jpg'></td></tr></table>
## Organic Line Legacy
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Organic%20Line%20Legacy.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Organic%20Line%20Legacy.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Converts selected freedraw lines such that pencil pressure will decrease from maximum to minimum from the beginning of the line to its end. The resulting line is placed at the back of the layers, under all other items. Helpful when drawing organic mindmaps.<br>This is the old script from this <a href="https://youtu.be/JMcNDdj_lPs?t=479" target="_blank">video</a>. Since it's release this has been superseded by custom pens that you can enable in plugin settings. For more on custom pens, watch <a href="https://youtu.be/OjNhjaH2KjI" target="_blank">this</a><br>The benefit of the approach in this implementation of custom pens is that it will look the same on excalidraw.com when you copy your drawing over for sharing with non-Obsidian users. Otherwise custom pens are faster to use and much more configurable.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-organic-line-legacy.jpg'></td></tr></table>
## Palette Loader
```excalidraw-script-install
@@ -276,6 +456,18 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Palette%20loader.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Design your palette at <a href="http://paletton.com/" target="_blank">paletton.com</a> Once you are happy with your colors, click Tables/Export in the bottom right of the screen. Then click "Color swatches/as Sketch Palette", and copy the contents of the page to a markdown file in the palette folder of your vault (default is Excalidraw/Palette)<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-sketch-palette-loader-1.jpg'></td></tr></table>
## PDF Page Text to Clipboard
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/PDF%20Page%20Text%20to%20Clipboard.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/PDF%20Page%20Text%20to%20Clipboard.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Copies the text from the selected PDF page on the Excalidraw canvas to the clipboard.<br><iframe width="400" height="225" src="https://www.youtube.com/embed/Kwt_8WdOUT4" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><br><a href='https://youtu.be/Kwt_8WdOUT4' target='_blank'>Link to video on YouTube</a></td></tr></table>
## Relative Font Size Cycle
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Relative%20Font%20Size%20Cycle.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Relative%20Font%20Size%20Cycle.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script will cycle through S, M, L, XL font sizes scaled to the current canvas zoom.</td></tr></table>
## Rename Image
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Rename%20Image.md
@@ -288,6 +480,12 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Repeat%20Elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will detect the difference between 2 selected elements, including position, size, angle, stroke and background color, and create several elements that repeat these differences based on the number of repetitions entered by the user.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-repeat-elements.png'></td></tr></table>
## Repeat Texts
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Repeat%20Texts.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/soraliu'>@soraliu</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Repeat%20Texts.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">In the following script, we address the concept of repetition through the lens of numerical progression. As visualized by the image, where multiple circles each labeled with an even task number are being condensed into a linear sequence, our script will similarly iterate through a set of numbers</td></tr></table>
## Reverse arrows
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Reverse%20arrows.md
@@ -298,7 +496,7 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Scribble%20Helper.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Scribble%20Helper.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">iOS scribble helper for better handwriting experience with text elements. If no elements are selected then the creates a text element at pointer position and you can use the edit box to modify the text with scribble. If a text element is selected then opens the input prompt where you can modify this text with scribble.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-scribble-helper.jpg'></td></tr></table>
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Scribble%20Helper.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">iOS scribble helper for better handwriting experience with text elements. If no elements are selected then the creates a text element at pointer position and you can use the edit box to modify the text with scribble. If a text element is selected then opens the input prompt where you can modify this text with scribble.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-scribble-helper.jpg'><br><iframe width="560" height="315" src="https://www.youtube.com/embed/BvYkOaly-QM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></td></tr></table>
## Select Elements of Type
```excalidraw-script-install
@@ -306,6 +504,12 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Select%20Elements%20of%20Type.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Prompts you with a list of the different element types in the active image. Only elements of the selected type will be selected on the canvas. If nothing is selected when running the script, then the script will process all the elements on the canvas. If some elements are selected when the script is executed, then the script will only process the selected elements.<br>The script is useful when, for example, you want to bring to front all the arrows, or want to change the color of all the text elements, etc.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-select-element-of-type.jpg'></td></tr></table>
## Select Similar Elements
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Select%20Similar%20Elements.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Select%20Similar%20Elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script allows you to streamline your Obsidian-Excalidraw workflows by enabling the selection of elements based on similar properties. you can precisely define which attributes such as stroke color, fill style, font family, and more, should match for selection. It's perfect for large canvases where manual selection would be cumbersome. You can either run the script to find and select matching elements across the entire scene, or define a specific group of elements to apply the selection criteria within a defined timeframe.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-select-similar-elements.png'></td></tr></table>
## Set background color of unclosed line object by adding a shadow clone
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20background%20color%20of%20unclosed%20line%20object%20by%20adding%20a%20shadow%20clone.md
@@ -352,7 +556,13 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Slideshow.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Slideshow.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script will convert your drawing into a slideshow presentation.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-slideshow-1.jpg'><br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-slideshow-2.jpg'></td></tr></table>
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Slideshow.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script will convert your drawing into a slideshow presentation.<br><iframe width="560" height="315" src="https://www.youtube.com/embed/JwgtCrIVeEU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></td></tr></table>
## Split Ellipse
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Split%20Ellipse.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/GColoy'>@GColoy</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Split%20Ellipse.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script splits an ellipse at any point where a line intersects it. If no lines are selected, it will use every line that intersects the ellipse. Otherwise, it will only use the selected lines. If there is no intersecting line, the ellipse will be converted into a line object.<br>There is also the option to close the object along the cut, which will close the cut in the shape of the line.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-splitEllipse-demo1.png'><br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-splitEllipse-demo2.png'><br>Tip: To use an ellipse as the cutting object, you first have to use this script on it, since it will convert the ellipse into a line.</td></tr></table>
## Split text by lines
```excalidraw-script-install
@@ -366,11 +576,29 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Text%20Arch.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Fit a text to the arch of a circle. The script will prompt you for the radius of the circle and then split your text to individual letters and place each letter to the arch defined by the radius. Setting a lower radius value will increase the arching of the text. Note that the arched-text will no longer be editable as a text element and it will no longer function as a markdown link. Emojis are currently not supported.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/text-arch.jpg'></td></tr></table>
## Transfer TextElements to Excalidraw markdown metadata
## Text Aura
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Transfer%20TextElements%20to%20Excalidraw%20markdown%20metadata.md
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Text%20Aura.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Transfer%20TextElements%20to%20Excalidraw%20markdown%20metadata.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script will delete the selected text elements from the canvas and will copy the text from these text elements into the Excalidraw markdown file as metadata. This means, that the text will no longer be visible in the drawing, however you will be able to search for the text in Obsidian and find the drawing containing this image.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-text-to-metadata.jpg'></td></tr></table>
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Text%20Aura.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Select a single text element, or a text element in a container. The container must have a transparent background.<br>The script will add an aura to the text by adding 4 copies of the text each with the inverted stroke color of the original text element and with a very small X and Y offset. The resulting 4 + 1 (original) text elements or containers will be grouped.<br>If you copy a color string on the clipboard before running the script, the script will use that color instead of the inverted color.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-text-aura.jpg'></td></tr></table>
## Toggle Grid
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Toggle%20Grid.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/GColoy'>@GColoy</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Toggle%20Grid.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Toggles the grid on and off.<br> Especially useful when drawing with just a pen without a mouse or keyboard, as toggling the grid by left-clicking with the pen is sometimes quite tedious.</table>
## Text to Sticky Notes
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Text%20to%20Sticky%20Notes.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Text%20to%20Sticky%20Notes.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Converts selected plain text element to sticky notes by dividing the text element line by line into separate sticky notes. The color of the stikcy note as well as the arrangement of the grid can be configured in plugin settings.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-sticky-note-matrix.jpg'></td></tr></table>
## Uniform Size
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Uniform%20size.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Uniform%20size.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data"><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-uniform-size.jpg"><br>The script will standardize the sizes of rectangles, diamonds and ellipses adjusting all the elements to match the largest width and height within the group.</td></tr></table>
## Zoom to Fit Selected Elements
```excalidraw-script-install

View File

@@ -0,0 +1,17 @@
{
constantPressure: true,
options: {
smoothing: 0.4,
thinning: -0.5,
streamline: 0.4,
easing: "linear",
start: {
taper: 5,
cap: false,
},
end: {
taper: 5,
cap: false,
},
}
}

View File

@@ -0,0 +1,16 @@
{
options: {
smoothing: 0.22,
thinning: 0.8,
streamline: 0.22,
easing: "easeInQuad",
start: {
taper: true,
cap: true,
},
end: {
taper: 1,
cap: true,
},
}
}

View File

@@ -0,0 +1,16 @@
{
constantPressure: true,
options: {
thinning: 4,
smoothing: 0.5,
streamline: 0.5,
start: {
taper: true,
easing: "linear",
},
end: {
taper: true,
easing: "linear",
}
}
}

View File

@@ -0,0 +1,16 @@
{
constantPressure: true,
options: {
thinning: 4,
smoothing: 0.5,
streamline: 0.5,
start: {
taper: 0,
cap: true
},
end: {
taper: true,
easing: "linear",
}
}
}

View File

@@ -0,0 +1,18 @@
{
hasOutline: true,
outlineWidth: 4,
options: {
thinning: 3,
smoothing: 0.5,
streamline: 0.5,
easing: "easeInOutElastic",
start: {
taper: 50,
cap: true
},
end: {
taper: 50,
cap: true
}
}
}

View File

@@ -0,0 +1,19 @@
{
constantPressure: true,
hasOutline: true,
outlineWidth: 4,
options: {
thinning: 1,
smoothing: 0.5,
streamline: 0.5,
easing: "linear",
start: {
taper: 0,
cap: true
},
end: {
taper: 0,
cap: true
}
}
}

Some files were not shown because too many files have changed in this diff Show More