Compare commits

..

107 Commits

Author SHA1 Message Date
Zsolt Viczian
2d16b59ea3 Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2022-01-07 19:34:46 +01:00
Zsolt Viczian
b292ca0fa3 1.5.15 2022-01-07 19:34:24 +01:00
zsviczian
95c21c2d5e Update README.md 2022-01-06 15:25:27 +01:00
zsviczian
7e45a0f952 Update index.md 2022-01-06 07:50:20 +01:00
zsviczian
7c8460646a Merge pull request #343 from 1-2-3/master
Add modify background color opacity scripts image
2022-01-06 07:47:44 +01:00
zahuifan
8badc3eb8f Add modify background color opacity scripts image 2022-01-06 10:10:19 +08:00
Zsolt Viczian
ad98d114e1 scriptSettings 2022-01-05 21:09:36 +01:00
Zsolt Viczian
c90370a606 1.5.14 2022-01-03 22:43:30 +01:00
Zsolt Viczian
9889567798 update Dimensions 2022-01-03 22:24:03 +01:00
Zsolt Viczian
70ee82bdb1 check github for updates 2022-01-03 21:09:45 +01:00
zsviczian
4effb42762 Merge pull request #339 from 1-2-3/master
Fixed 3 scripts that did not handle NaN input values
2022-01-03 18:16:28 +01:00
zahuifan
176248f33e Fix fixed spacing ea-script not processing NaN. 2022-01-03 21:37:44 +08:00
zahuifan
dbb64e5044 fix modify opacity ea-script not processing NaN. 2022-01-03 21:32:43 +08:00
Zsolt Viczian
be7c043871 added video to OCR description 2022-01-02 22:16:02 +01:00
Zsolt Viczian
ba69f4319f 1.5.13 2022-01-02 22:13:00 +01:00
Zsolt Viczian
5b755db673 updated index.md 2022-01-02 20:43:30 +01:00
Zsolt Viczian
2e0ce819a9 index 2022-01-02 19:45:40 +01:00
Zsolt Viczian
be03026360 . 2022-01-02 19:41:07 +01:00
Zsolt Viczian
d0385563e2 image 2022-01-02 19:33:45 +01:00
Zsolt Viczian
91e84cc41a image 2022-01-02 19:33:12 +01:00
Zsolt Viczian
f308cfe907 index.md update 2022-01-02 19:09:40 +01:00
Zsolt Viczian
0c4919547f . 2022-01-02 19:08:37 +01:00
Zsolt Viczian
c0eb85abf5 updated css, index.md, modal css class 2022-01-02 19:00:50 +01:00
Zsolt Viczian
73b31627f3 typo 2022-01-02 18:33:16 +01:00
Zsolt Viczian
241a1c7301 css 2022-01-02 18:31:57 +01:00
Zsolt Viczian
4182098730 Plugin store MVP 2022-01-02 18:20:53 +01:00
Zsolt Viczian
389387aa6e css 2022-01-02 16:09:12 +01:00
Zsolt Viczian
381401f175 scriptEngineInstall WIP 2022-01-02 15:59:14 +01:00
Zsolt Viczian
cca4158295 added index 2022-01-02 12:53:52 +01:00
Zsolt Viczian
d4ebf68bb5 added set link alias script 2022-01-01 19:26:40 +01:00
Zsolt Viczian
c9b9b64513 1.5.12 2022-01-01 18:08:05 +01:00
Zsolt Viczian
ea202763be typo 2022-01-01 14:21:56 +01:00
Zsolt Viczian
1c86308ee3 typos 2022-01-01 14:20:51 +01:00
Zsolt Viczian
66936975dd new scripts 2022-01-01 14:16:01 +01:00
Zsolt Viczian
d70c290658 delete from filesMaster if file no longer exists 2022-01-01 13:49:03 +01:00
zsviczian
be45a0dfb6 Update README.md 2022-01-01 08:23:12 +01:00
zsviczian
98a76d464b Update README.md 2022-01-01 08:07:08 +01:00
zsviczian
2edd25c298 Merge pull request #334 from 1-2-3/master
Update ea-script readme.md
2022-01-01 08:02:14 +01:00
1-2-3
ca7d9576b4 Update README.md 2022-01-01 01:10:24 +08:00
1-2-3
110cb60e00 Update README.md 2022-01-01 01:08:19 +08:00
zsviczian
83764410f0 Merge pull request #333 from 1-2-3/master
Add ea-scripts demo image files
2021-12-31 16:27:11 +01:00
1-2-3
c7154d531f Update Fixed vertical distance.md 2021-12-31 19:19:56 +08:00
1-2-3
aafedba989 Update Fixed spacing.md 2021-12-31 19:18:54 +08:00
1-2-3
9269b52057 Add ea-scripts demo image files 2021-12-31 19:16:44 +08:00
Zsolt Viczian
1ce44c2d55 1.5.11 fix for #327 2021-12-30 19:15:34 +01:00
Zsolt Viczian
1796402ced readme 2021-12-30 14:41:35 +01:00
Zsolt Viczian
f350895817 escape | 2021-12-30 14:37:45 +01:00
Zsolt Viczian
46db9ccbbf added contributor to readme 2021-12-30 14:35:00 +01:00
Zsolt Viczian
1123a3bd81 ea-script readme 2021-12-30 14:29:54 +01:00
zsviczian
79c62edbe7 Update Create new markdown file and embed into active drawing.md 2021-12-30 13:22:15 +01:00
Zsolt Viczian
76faf3011b 1.5.10 2021-12-30 13:20:51 +01:00
zsviczian
d0d6fbad12 Merge pull request #330 from 1-2-3/master
Add Elbow connectors ea-scripts file
2021-12-30 13:06:42 +01:00
zsviczian
3ba6292d6f Update Elbow connectors.md 2021-12-30 13:06:24 +01:00
zahuifan
adad32b641 Add Darken and Lighten background color ea-scripts 2021-12-30 19:31:48 +08:00
zahuifan
35bb2368fe Add Elbow connectors ea-scripts file 2021-12-30 08:48:33 +08:00
Zsolt Viczian
2c63a24c81 savePNG, saveSVG, and CreateNewDrawing refactored 2021-12-29 22:15:56 +01:00
Zsolt Viczian
db17b91418 Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2021-12-29 10:42:25 +01:00
Zsolt Viczian
47b9b16588 1.5.8 2021-12-29 10:42:11 +01:00
zsviczian
5194ced50c Update Zoom to Fit Selected Elements.md 2021-12-29 10:36:42 +01:00
Zsolt Viczian
9eaf22305a improved scripts, addText solved for non-container bound 2021-12-29 10:33:03 +01:00
Zsolt Viczian
6392bcd06e added ea-script 2021-12-28 09:34:02 +01:00
Zsolt Viczian
b6c5bfb20a Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2021-12-27 18:45:51 +01:00
Zsolt Viczian
4328537034 1.5.7 2021-12-27 18:45:34 +01:00
zsviczian
49f7c47064 Merge pull request #322 from 1-2-3/master
Add expand rectangles ea-script files
2021-12-27 15:32:23 +01:00
zahuifan
fec9d083e7 Add expand rectangles ea-script files 2021-12-27 20:20:39 +08:00
zsviczian
b8374a6b0b Merge pull request #319 from 1-2-3/master
Add Fixed spacing etc. ea-scripts files
2021-12-26 08:13:13 +01:00
zahuifan
a30c6bbf48 Modify the description of ea-Scripts 2021-12-26 10:57:29 +08:00
zahuifan
f85246f894 Add Fixed spacing etc. ea-scripts files 2021-12-26 10:49:10 +08:00
Zsolt Viczian
585640ff2e 1.5.6 2021-12-25 18:05:00 +01:00
Zsolt Viczian
17f6c7d2ac 1.5.5 2021-12-25 16:41:26 +01:00
Zsolt Viczian
896a31d02a Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2021-12-25 16:14:24 +01:00
Zsolt Viczian
c54c133ba0 Fix link click for container with text did not work 2021-12-25 16:14:06 +01:00
zsviczian
7c01da8731 Merge pull request #315 from 1-2-3/master
Add Modify background color opacity ea-scripts file
2021-12-25 16:12:45 +01:00
zahuifan
489b53f0f6 Add opacity ea-scripts file 2021-12-25 19:44:51 +08:00
Zsolt Viczian
73dd39905e strokeStyle spelling error 2021-12-25 11:45:08 +01:00
Zsolt Viczian
2e843f65ed fixed special chars in transclusion block ref 2021-12-25 11:12:59 +01:00
Zsolt Viczian
b18ddc6407 1.5.4 2021-12-23 20:23:09 +01:00
Zsolt Viczian
2359dd7f56 many small bugs...mostly container bound text 2021-12-23 18:49:41 +01:00
Zsolt Viczian
060e86d7ff Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2021-12-21 15:47:04 +01:00
Zsolt Viczian
3995e792fe container bound text element v1 2021-12-21 15:46:35 +01:00
zsviczian
68391e5163 Update ExcalidrawScriptsEngine.md 2021-12-17 08:17:34 +01:00
zsviczian
cce4475577 Update ExcalidrawScriptsEngine.md 2021-12-17 08:15:01 +01:00
zsviczian
73c8b1aa33 Add files via upload 2021-12-15 17:36:37 +01:00
zsviczian
b8d0b47a9d Rename Stroke Width.md to Modify stroke width of selected elements.md 2021-12-15 16:26:44 +01:00
zsviczian
0684ff13cc Update ExcalidrawScriptsEngine.md 2021-12-15 16:21:42 +01:00
zsviczian
1b28cd0e82 Update ExcalidrawScriptsEngine.md 2021-12-15 16:18:48 +01:00
zsviczian
09e8e64a2f Add files via upload 2021-12-15 16:16:30 +01:00
zsviczian
b166d3cef9 Add files via upload 2021-12-15 16:14:53 +01:00
zsviczian
06c3ba0b8f Update README.md 2021-12-14 10:22:05 +01:00
Zsolt Viczian
ba7c39be74 lint 2021-12-13 18:33:46 +01:00
Zsolt Viczian
a844244450 1.5.3 multi-selection line edit 2021-12-13 18:27:26 +01:00
Zsolt Viczian
bd6f9b7a1d 1.5.2 2021-12-12 19:01:16 +01:00
Zsolt Viczian
6b87016cb3 formatting 2021-12-12 15:29:49 +01:00
Zsolt Viczian
e632a3c665 Font Family script 2021-12-12 15:24:54 +01:00
Zsolt Viczian
d2b25441c3 text-align script 2021-12-12 15:14:25 +01:00
Zsolt Viczian
41cca8e68d correct codeblock 2021-12-12 13:53:40 +01:00
Zsolt Viczian
ca3394a2fc correct codeblock 2021-12-12 13:52:29 +01:00
Zsolt Viczian
daeb61e858 split lines 2021-12-12 13:51:24 +01:00
Zsolt Viczian
c39ff3f3e2 1.5.1 2021-12-12 13:44:36 +01:00
zsviczian
f4a045b476 Update ExcalidrawScriptsEngine.md 2021-12-12 10:34:27 +01:00
Zsolt Viczian
d7f8429d91 updated script engine examples 2021-12-12 10:31:26 +01:00
Zsolt Viczian
d4c16b7d04 added thumbnail image to scripts 2021-12-12 10:27:40 +01:00
Zsolt Viczian
e4f8506d24 . 2021-12-12 08:16:52 +01:00
Zsolt Viczian
4f82b5cb0d updated formatting 2021-12-12 08:16:16 +01:00
Zsolt Viczian
8298434f80 added bullet point script 2021-12-12 08:15:17 +01:00
Zsolt Viczian
7336936fca added image 2021-12-12 08:09:31 +01:00
Zsolt Viczian
5692006d19 script changes 2021-12-12 07:43:27 +01:00
74 changed files with 3784 additions and 707 deletions

View File

@@ -12,6 +12,7 @@ Please upgrade to Obsidian v0.12.19 or higher to get the latest release.
|[![6 Links](https://user-images.githubusercontent.com/14358394/125160346-aa0b6580-e17c-11eb-930b-4024807040d1.jpg)](https://youtu.be/MXzeCOEExNo)|[![7 Markdown](https://user-images.githubusercontent.com/14358394/125160354-b2fc3700-e17c-11eb-81af-9e71e461f6dd.jpg)](https://youtu.be/R0IAg0s-wQE)|[![8 Templates](https://user-images.githubusercontent.com/14358394/125160360-b8f21800-e17c-11eb-8bd8-79d4e3f6e92d.jpg)](https://youtu.be/ibdS7ykwpW4)|
|[![9 Excalidraw Automate](https://user-images.githubusercontent.com/14358394/125160367-bdb6cc00-e17c-11eb-92f1-6f59faea85fd.jpg)](https://youtu.be/VRZVujfVab0)|[![10 Miscellaneous](https://user-images.githubusercontent.com/14358394/125160374-c3141680-e17c-11eb-8cc2-dfaffd903d15.jpg)](https://youtu.be/D1iBYo1_jjc)|[![Image Elements](https://user-images.githubusercontent.com/14358394/138607067-ccb62f92-48a4-4880-ac6e-68c1bf86ac2c.png)](https://www.youtube.com/watch?v=_c_0zpBJ4Xc&)|
|[![LaTex Demo](https://user-images.githubusercontent.com/14358394/143732412-1c65227e-4381-406d-847a-b001ab3506ca.jpg)](https://youtu.be/r08wk-58DPk)|[![markdown embeds](https://user-images.githubusercontent.com/14358394/143732440-90bfa029-8615-462e-ada3-c903d71a82c9.jpg)](https://youtu.be/tsecSfnTMow)|[![markdownAdvanced](https://user-images.githubusercontent.com/14358394/143783906-15cee494-c6d5-4495-a2ca-74634e4e7355.jpg)](https://youtu.be/K6qZkTz8GHs)|
|[![Script Engine](https://user-images.githubusercontent.com/14358394/145684531-8d9c2992-59ac-4ebc-804a-4cce1777ded2.jpg)](https://youtu.be/hePJcObHIso)|[![sticky notes thumbnail](https://user-images.githubusercontent.com/14358394/147283367-e5689385-ea51-4983-81a3-04d810d39f62.jpg)](https://youtu.be/NOuddK6xrr8)|[![plugin store](https://user-images.githubusercontent.com/14358394/147889174-6c306d0d-2d29-46cc-a53f-3f0013cf14de.jpg)](https://youtu.be/lzYdOQ6z8F0)|
# Key features
@@ -25,7 +26,7 @@ Please upgrade to Obsidian v0.12.19 or higher to get the latest release.
- 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.
- 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. Any text that appears in `<alignment>` will be added to the rendered SVG element style and to the wrapper DIV element. Check below and styles.css for more insight.
- 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. Any text that appears in `<alignment>` will be added to the rendered SVG element style and to the wrapper DIV element. Check below and styles.css for more insight.
- 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
@@ -72,6 +73,7 @@ Please upgrade to Obsidian v0.12.19 or higher to get the latest release.
- `excalidraw-css: "css-filename|css snippet"`
- Switch to markdown view or use CTRL/CMD+ALT/OPT click on the image to edit properties of the embed: `[[filename#^blockref|WIDTHxMAXHEIGHT]]`
- 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.

View File

@@ -13,7 +13,7 @@ export interface ExcalidrawAutomate {
angle: number; //radian
fillStyle: FillStyle; //type FillStyle = "hachure" | "cross-hatch" | "solid"
strokeWidth: number;
storkeStyle: StrokeStyle; //type StrokeStyle = "solid" | "dashed" | "dotted"
strokeStyle: StrokeStyle; //type StrokeStyle = "solid" | "dashed" | "dotted"
roughness: number;
opacity: number;
strokeSharpness: StrokeSharpness; //type StrokeSharpness = "round" | "sharp"
@@ -38,7 +38,8 @@ export interface ExcalidrawAutomate {
toClipboard(templatePath?: string): void;
getElements(): ExcalidrawElement[]; //get all elements from ExcalidrawAutomate elementsDict
getElement(id: string): ExcalidrawElement; //get single element from ExcalidrawAutomate elementsDict
create(params?: { //create a drawing and save it to filename
create(params?: {
//create a drawing and save it to filename
filename?: string; //if null: default filename as defined in Excalidraw settings
foldername?: string; //if null: default folder as defined in Excalidraw settings
templatePath?: string;
@@ -99,7 +100,7 @@ export interface ExcalidrawAutomate {
objectA: string,
connectionA: ConnectionPoint, //type ConnectionPoint = "top" | "bottom" | "left" | "right" | null
objectB: string,
connectionB: ConnectionPoint, //when passed null, Excalidraw will automatically decide
connectionB: ConnectionPoint, //when passed null, Excalidraw will automatically decide
formatting?: {
numberOfPoints?: number; //points on the line. Default is 0 ie. line will only have a start and end point
startArrowHead?: string; //"triangle"|"dot"|"arrow"|"bar"|null
@@ -112,17 +113,18 @@ export interface ExcalidrawAutomate {
isExcalidrawFile(f: TFile): boolean; //returns true if MD file is an Excalidraw file
//view manipulation
targetView: ExcalidrawView; //the view currently edited
setView(view: ExcalidrawView | "first" | "active"): ExcalidrawView;
setView(view: ExcalidrawView | "first" | "active"): ExcalidrawView;
getExcalidrawAPI(): any; //https://github.com/excalidraw/excalidraw/tree/master/src/packages/excalidraw#ref
getViewElements(): ExcalidrawElement[]; //get elements in View
deleteViewElements(el: ExcalidrawElement[]): boolean;
getViewSelectedElement(): ExcalidrawElement; //get the selected element in the view, if more are selected, get the first
getViewSelectedElements(): ExcalidrawElement[];
getViewFileForImageElement(el: ExcalidrawElement): TFile | null; //Returns the TFile file handle for the image element
copyViewElementsToEAforEditing(elements: ExcalidrawElement[]): void; //copies elements from view to elementsDict for editing
viewToggleFullScreen(forceViewMode?: boolean): void;
connectObjectWithViewSelectedElement( //connect an object to the selected element in the view
objectA: string, //see connectObjects
connectionA: ConnectionPoint,
connectionA: ConnectionPoint,
connectionB: ConnectionPoint,
formatting?: {
numberOfPoints?: number;
@@ -135,7 +137,8 @@ export interface ExcalidrawAutomate {
repositionToCursor: boolean,
save: boolean,
): Promise<boolean>;
onDropHook(data: { //if set Excalidraw will call this function onDrop events
onDropHook(data: {
//if set Excalidraw will call this function onDrop events
ea: ExcalidrawAutomate;
event: React.DragEvent<HTMLDivElement>;
draggable: any; //Obsidian draggable object
@@ -154,24 +157,30 @@ export interface ExcalidrawAutomate {
withBackground: boolean,
withTheme: boolean,
): ExportSettings;
getBoundingBox(elements: ExcalidrawElement[]): { //get bounding box of elements
getBoundingBox(elements: ExcalidrawElement[]): {
//get bounding box of elements
topX: number; //bounding box is the box encapsulating all of the elements completely
topY: number;
width: number;
height: number;
};
//elements grouped by the highest level groups
getMaximumGroups(elements: ExcalidrawElement[]): ExcalidrawElement[][];
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
getLargestElement(elements: ExcalidrawElement[]): ExcalidrawElement;
// Returns 2 or 0 intersection points between line going through `a` and `b`
// and the `element`, in ascending order of distance from `a`.
intersectElementWithLine(
intersectElementWithLine(
element: ExcalidrawBindableElement,
a: readonly [number, number],
b: readonly [number, number],
gap?: number, //if given, element is inflated by this value
): Point[];
//See OCR plugin for example on how to use scriptSettings
activeScript: string; //Set automatically by the ScriptEngine
getScriptSettings(): {}; //Returns script settings. Saves settings in plugin settings, under the activeScript key
setScriptSettings(settings:any):Promise<void>; //sets script settings.
}
```

View File

@@ -29,11 +29,11 @@ function crawl(subtasks) {
return size;
}
const tasks = dv.page("Demo.md").file.tasks[0];
const tasks = dv.page("FamilyTree.md").file.tasks[0];
tasks["size"] = crawl(tasks.subtasks);
const width = 300;
const height = 100;
const height = 150;
const ea = ExcalidrawAutomate;
ea.reset();
@@ -56,7 +56,7 @@ function buildMindmap(subtasks, depth, offset, parentObjectID) {
}
tasks["objectID"] = ea.addText(width*1.5,width,tasks.text,{box:true, textAlign:"center"});
tasks["objectID"] = ea.addText(width*1.5,height*(tasks.size-1),tasks.text,{box:true, textAlign:"center"});
buildMindmap(tasks.subtasks, 2, 0, tasks.objectID);
ea.createSVG().then((svg)=>dv.span(svg.outerHTML));

View File

@@ -1,5 +1,7 @@
# [◀ Excalidraw Automate How To](./readme.md)
[![Script Engine](https://user-images.githubusercontent.com/14358394/145684531-8d9c2992-59ac-4ebc-804a-4cce1777ded2.jpg)](https://youtu.be/hePJcObHIso)
## Introduction
Place your ExcalidrawAutomate Scripts into the folder defined in Excalidraw Settings. The Scripts folder may not be the root folder of your Vault.
@@ -20,19 +22,38 @@ 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.
- `utils`: There is currently only a single function published on `utils`
- `inputPrompt: (header: string, placeholder?: string, value?: string)`. You need to await the result of inputPrompt. See the example below for details.
- `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)`
- Opens a prompt that asks for an input. Returns a string with the input.
- You need to await the result of inputPrompt.
- `suggester: (displayItems: string[], actualItems: string[])`
- Opens a suggester. Displays the displayItems, but you map these the other values with actualItems. Returns the selected value.
- You need to await the result of suggester.
## Example Excalidraw Automate script
---------
## Example Excalidraw Automate Scripts
These scripts are available as downloadable `.md` files on GitHub in [this](https://github.com/zsviczian/obsidian-excalidraw-plugin/tree/master/ea-scripts) folder 📂.
### Add box around selected elements
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-box-elements.jpg)
This script will add an encapsulating box around the currently selected elements in Excalidraw
```javascript
padding = parseInt (await utils.inputPrompt("padding?"));
//uncomment if you want a prompt for custom padding
//const padding = parseInt (await utils.inputPrompt("padding?","number","10"));
const padding = 10
elements = ea.getViewSelectedElements();
const box = ea.getBoundingBox(elements);
const rndColor = '#'+(Math.random()*0xFFFFFF<<0).toString(16).padStart(6,"0");
ea.style.strokeColor = rndColor;
color = ea
.getExcalidrawAPI()
.getAppState()
.currentItemStrokeColor;
//uncomment if you want to set the stroke to a random color
//color = '#'+(Math.random()*0xFFFFFF<<0).toString(16).padStart(6,"0");
ea.style.strokeColor = color;
id = ea.addRect(
box.topX - padding,
box.topY - padding,
@@ -44,7 +65,12 @@ ea.addToGroup([id].concat(elements.map((el)=>el.id)));
ea.addElementsToView(false);
```
----
### Connect selected elements with an arrow
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-connect-elements.jpg)
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).
```javascript
const elements = ea.getViewSelectedElements();
@@ -65,17 +91,47 @@ ea.connectObjects(
ea.addElementsToView();
```
----
### Reverse selected arrows
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-reverse-arrow.jpg)
Reverse the direction of **arrows** within the scope of selected elements.
```javascript
elements = ea.getViewSelectedElements().filter((el)=>el.type==="arrow");
if(!elements || elements.length===0) return;
elements.forEach((el)=>{
const start = el.startArrowhead;
el.startArrowhead = el.endArrowhead;
el.endArrowhead = start;
});
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView();
```
----
### Set line width of selected elements
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-stroke-width.jpg)
This is helpful, for example, when you scale freedraw sketches and want to reduce or increase their line width.
```javascript
width = await utils.inputPrompt("Width?");
let width = (ea.getViewSelectedElement().strokeWidth??1).toString();
width = await utils.inputPrompt("Width?","number",width);
const elements=ea.getViewSelectedElements();
ea.copyViewElementsToEAforEditing(elements);
ea.getElements().forEach((el)=>el.strokeWidth=width);
ea.addElementsToView();
```
----
### Set grid size
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-grid.jpg)
The default grid size in Excalidraw is 20. Currently there is no way to change the grid size via the user interface.
```javascript
const grid = parseInt(await utils.inputPrompt("Grid size?",null,"20"));
@@ -88,7 +144,12 @@ api.updateScene({
});
```
----
### Set element dimensions and position
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-dimensions.jpg)
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.
```javascript
const elements = ea.getViewSelectedElements();
@@ -110,4 +171,87 @@ el.width = size[2];
el.height = size[3];
ea.copyViewElementsToEAforEditing([el]);
ea.addElementsToView();
```
```
----
### Bullet points
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-bullet-point.jpg)
This script will add a small circle to the top left of each text element in the selection and add the text and the "bullet point" into a group.
```javascript
elements = ea.getViewSelectedElements().filter((el)=>el.type==="text");
ea.copyViewElementsToEAforEditing(elements);
const padding = 10;
elements.forEach((el)=>{
ea.style.strokeColor = el.strokeColor;
const size = el.fontSize/2;
const ellipseId = ea.addEllipse(
el.x-padding-size,
el.y+size/2,
size,
size
);
ea.addToGroup([el.id,ellipseId]);
});
ea.addElementsToView();
```
----
### Split text by lines
**!!!Requires Excalidraw 1.5.1 or higher**
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-split-lines.jpg)
Split lines of text into separate text elements for easier reorganization
```javascript
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]);
}
});
ea.addElementsToView();
ea.deleteViewElements(elements);
```
----
### Set Text Alignment
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-text-align.jpg)
Sets text alignment of text block (cetner, right, left). Useful if you want to set a keyboard shortcut for selecting text alignment.
```javascript
elements = ea.getViewSelectedElements().filter((el)=>el.type==="text");
if(elements.length===0) return;
let align = ["left","right","center"];
align = await utils.suggester(align,align);
elements.forEach((el)=>el.textAlign = align);
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView();
```
----
### Set Font Family
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-font-family.jpg)
Sets font family of the text block (Virgil, Helvetica, Cascadia). Useful if you want to set a keyboard shortcut for selecting font family.
```javascript
elements = ea.getViewSelectedElements().filter((el)=>el.type==="text");
if(elements.length===0) return;
let font = ["Virgil","Helvetica","Cascadia"];
font = parseInt(await utils.suggester(font,["1","2","3"]));
if (isNaN(font)) return;
elements.forEach((el)=>el.fontFamily = font);
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView();
```

View File

@@ -1,6 +1,11 @@
/*
![](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-box-elements.jpg)
This script will add an encapsulating box around the currently selected elements in Excalidraw.
See documentation for more details:
@@ -8,12 +13,22 @@ https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.h
```javascript
*/
//const padding = parseInt (await utils.inputPrompt("padding?"));
const padding = 10
//uncomment if you don't want a prompt for custom padding
//const padding = 10
const padding = parseInt (await utils.inputPrompt("padding?","number","10"));
if(isNaN(padding)) {
new Notice("The padding value provided is not a number");
return;
}
elements = ea.getViewSelectedElements();
const box = ea.getBoundingBox(elements);
const rndColor = '#'+(Math.random()*0xFFFFFF<<0).toString(16).padStart(6,"0");
ea.style.strokeColor = rndColor;
color = ea
.getExcalidrawAPI()
.getAppState()
.currentItemStrokeColor;
//uncomment for random color:
//color = '#'+(Math.random()*0xFFFFFF<<0).toString(16).padStart(6,"0");
ea.style.strokeColor = color;
id = ea.addRect(
box.topX - padding,
box.topY - padding,

View File

@@ -0,0 +1,30 @@
/*
![](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-bullet-point.jpg)
This script will add a small circle to the top left of each text element in the selection and add the text and the "bullet point" into a group.
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
elements = ea.getViewSelectedElements().filter((el)=>el.type==="text");
ea.copyViewElementsToEAforEditing(elements);
const padding = 10;
elements.forEach((el)=>{
ea.style.strokeColor = el.strokeColor;
const size = el.fontSize/2;
const ellipseId = ea.addEllipse(
el.x-padding-size,
el.y+size/2,
size,
size
);
ea.addToGroup([el.id,ellipseId]);
});
ea.addElementsToView();

View File

@@ -1,6 +1,11 @@
/*
![](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-connect-elements.jpg)
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).
See documentation for more details:

View File

@@ -0,0 +1,29 @@
/*
![](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.
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.
`original text` => `[[selected folder/original text|original text]]`
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
folders = new Set();
app.vault.getFiles().forEach((f)=>
folders.add(f.path.substring(0,f.path.lastIndexOf("/")))
);
f = Array.from(folders);
folder = await utils.suggester(f,f);
folder = folder === "" ? folder : folder + "/";
elements = ea.getViewSelectedElements().filter((el)=>el.type==="text");
elements.forEach((el)=>{
el.rawText = "[["+folder+el.rawText+"|"+el.rawText+"]]";
})
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView();

View File

@@ -0,0 +1,21 @@
/*
![](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-create-and-embed-new-markdown-file.jpg)
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.
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
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",folder);
const file = await app.fileManager.createAndOpenMarkdownFile(fname,true);
await ea.addImage(0,0,file);
ea.addElementsToView(true,true);

View File

@@ -0,0 +1,289 @@
/*
![](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/darken-lighten-background-color.png)
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.
The color conversion method was copied from [color-convert](https://github.com/Qix-/color-convert).
```javascript
*/
const elements = ea
.getViewSelectedElements()
.filter((el) =>
["rectangle", "ellipse", "diamond", "image"].includes(el.type)
);
ea.copyViewElementsToEAforEditing(elements);
for (const el of ea.getElements()) {
const color = colorNameToHex(el.backgroundColor);
const rgbColor = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(color);
if (rgbColor) {
const r = parseInt(rgbColor[1], 16);
const g = parseInt(rgbColor[2], 16);
const b = parseInt(rgbColor[3], 16);
const originalRgb = [r, g, b];
const hsl = rgbToHsl(originalRgb);
const step = 2;
const newLightness = hsl[2] - step;
if (newLightness > 0) {
hsl[2] = newLightness;
}
const newRgb = hslToRgb(hsl);
el.backgroundColor = "#" + rgbToHexString(newRgb);
}
}
ea.addElementsToView();
function rgbToHexString(args) {
const integer =
((Math.round(args[0]) & 0xff) << 16) +
((Math.round(args[1]) & 0xff) << 8) +
(Math.round(args[2]) & 0xff);
const string = integer.toString(16).toUpperCase();
return "000000".substring(string.length) + string;
}
function hslToRgb(hsl) {
const h = hsl[0] / 360;
const s = hsl[1] / 100;
const l = hsl[2] / 100;
let t2;
let t3;
let val;
if (s === 0) {
val = l * 255;
return [val, val, val];
}
if (l < 0.5) {
t2 = l * (1 + s);
} else {
t2 = l + s - l * s;
}
const t1 = 2 * l - t2;
const rgb = [0, 0, 0];
for (let i = 0; i < 3; i++) {
t3 = h + (1 / 3) * -(i - 1);
if (t3 < 0) {
t3++;
}
if (t3 > 1) {
t3--;
}
if (6 * t3 < 1) {
val = t1 + (t2 - t1) * 6 * t3;
} else if (2 * t3 < 1) {
val = t2;
} else if (3 * t3 < 2) {
val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
} else {
val = t1;
}
rgb[i] = val * 255;
}
return rgb;
}
function rgbToHsl(rgb) {
const r = rgb[0] / 255;
const g = rgb[1] / 255;
const b = rgb[2] / 255;
const min = Math.min(r, g, b);
const max = Math.max(r, g, b);
const delta = max - min;
let h;
let s;
if (max === min) {
h = 0;
} else if (r === max) {
h = (g - b) / delta;
} else if (g === max) {
h = 2 + (b - r) / delta;
} else if (b === max) {
h = 4 + (r - g) / delta;
}
h = Math.min(h * 60, 360);
if (h < 0) {
h += 360;
}
const l = (min + max) / 2;
if (max === min) {
s = 0;
} else if (l <= 0.5) {
s = delta / (max + min);
} else {
s = delta / (2 - max - min);
}
return [h, s * 100, l * 100];
}
function colorNameToHex(color) {
const colors = {
aliceblue: "#f0f8ff",
antiquewhite: "#faebd7",
aqua: "#00ffff",
aquamarine: "#7fffd4",
azure: "#f0ffff",
beige: "#f5f5dc",
bisque: "#ffe4c4",
black: "#000000",
blanchedalmond: "#ffebcd",
blue: "#0000ff",
blueviolet: "#8a2be2",
brown: "#a52a2a",
burlywood: "#deb887",
cadetblue: "#5f9ea0",
chartreuse: "#7fff00",
chocolate: "#d2691e",
coral: "#ff7f50",
cornflowerblue: "#6495ed",
cornsilk: "#fff8dc",
crimson: "#dc143c",
cyan: "#00ffff",
darkblue: "#00008b",
darkcyan: "#008b8b",
darkgoldenrod: "#b8860b",
darkgray: "#a9a9a9",
darkgreen: "#006400",
darkkhaki: "#bdb76b",
darkmagenta: "#8b008b",
darkolivegreen: "#556b2f",
darkorange: "#ff8c00",
darkorchid: "#9932cc",
darkred: "#8b0000",
darksalmon: "#e9967a",
darkseagreen: "#8fbc8f",
darkslateblue: "#483d8b",
darkslategray: "#2f4f4f",
darkturquoise: "#00ced1",
darkviolet: "#9400d3",
deeppink: "#ff1493",
deepskyblue: "#00bfff",
dimgray: "#696969",
dodgerblue: "#1e90ff",
firebrick: "#b22222",
floralwhite: "#fffaf0",
forestgreen: "#228b22",
fuchsia: "#ff00ff",
gainsboro: "#dcdcdc",
ghostwhite: "#f8f8ff",
gold: "#ffd700",
goldenrod: "#daa520",
gray: "#808080",
green: "#008000",
greenyellow: "#adff2f",
honeydew: "#f0fff0",
hotpink: "#ff69b4",
"indianred ": "#cd5c5c",
indigo: "#4b0082",
ivory: "#fffff0",
khaki: "#f0e68c",
lavender: "#e6e6fa",
lavenderblush: "#fff0f5",
lawngreen: "#7cfc00",
lemonchiffon: "#fffacd",
lightblue: "#add8e6",
lightcoral: "#f08080",
lightcyan: "#e0ffff",
lightgoldenrodyellow: "#fafad2",
lightgrey: "#d3d3d3",
lightgreen: "#90ee90",
lightpink: "#ffb6c1",
lightsalmon: "#ffa07a",
lightseagreen: "#20b2aa",
lightskyblue: "#87cefa",
lightslategray: "#778899",
lightsteelblue: "#b0c4de",
lightyellow: "#ffffe0",
lime: "#00ff00",
limegreen: "#32cd32",
linen: "#faf0e6",
magenta: "#ff00ff",
maroon: "#800000",
mediumaquamarine: "#66cdaa",
mediumblue: "#0000cd",
mediumorchid: "#ba55d3",
mediumpurple: "#9370d8",
mediumseagreen: "#3cb371",
mediumslateblue: "#7b68ee",
mediumspringgreen: "#00fa9a",
mediumturquoise: "#48d1cc",
mediumvioletred: "#c71585",
midnightblue: "#191970",
mintcream: "#f5fffa",
mistyrose: "#ffe4e1",
moccasin: "#ffe4b5",
navajowhite: "#ffdead",
navy: "#000080",
oldlace: "#fdf5e6",
olive: "#808000",
olivedrab: "#6b8e23",
orange: "#ffa500",
orangered: "#ff4500",
orchid: "#da70d6",
palegoldenrod: "#eee8aa",
palegreen: "#98fb98",
paleturquoise: "#afeeee",
palevioletred: "#d87093",
papayawhip: "#ffefd5",
peachpuff: "#ffdab9",
peru: "#cd853f",
pink: "#ffc0cb",
plum: "#dda0dd",
powderblue: "#b0e0e6",
purple: "#800080",
rebeccapurple: "#663399",
red: "#ff0000",
rosybrown: "#bc8f8f",
royalblue: "#4169e1",
saddlebrown: "#8b4513",
salmon: "#fa8072",
sandybrown: "#f4a460",
seagreen: "#2e8b57",
seashell: "#fff5ee",
sienna: "#a0522d",
silver: "#c0c0c0",
skyblue: "#87ceeb",
slateblue: "#6a5acd",
slategray: "#708090",
snow: "#fffafa",
springgreen: "#00ff7f",
steelblue: "#4682b4",
tan: "#d2b48c",
teal: "#008080",
thistle: "#d8bfd8",
tomato: "#ff6347",
turquoise: "#40e0d0",
violet: "#ee82ee",
wheat: "#f5deb3",
white: "#ffffff",
whitesmoke: "#f5f5f5",
yellow: "#ffff00",
yellowgreen: "#9acd32",
};
if (typeof colors[color.toLowerCase()] != "undefined")
return colors[color.toLowerCase()];
return color;
}

View File

@@ -1,6 +1,11 @@
/*
![](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-dimensions.jpg)
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.
See documentation for more details:
@@ -11,7 +16,12 @@ https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.h
const elements = ea.getViewSelectedElements();
if(elements.length === 0) return;
const el = ea.getLargestElement(elements);
const sizeIn = [el.x,el.y,el.width,el.height].join(",");
const sizeIn = [
Math.round(el.x),
Math.round(el.y),
Math.round(el.width),
Math.round(el.height)
].join(",");
let res = await utils.inputPrompt("x,y,width,height?",null,sizeIn);
res = res.split(",");
if(res.length !== 4) return;

View File

@@ -0,0 +1,56 @@
/*
![](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/elbow-connectors.png)
This script converts the selected connectors to elbows.
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
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) {
for (var i = 0; i < line.points.length - 2; i++) {
var p1;
var p3;
if (line.points[i][0] < line.points[i + 2][0]) {
p1 = line.points[i];
p3 = line.points[i+2];
} else {
p1 = line.points[i + 2];
p3 = line.points[i];
}
const p2 = line.points[i + 1];
if (p1[0] === p3[0]) {
continue;
}
const k = (p3[1] - p1[1]) / (p3[0] - p1[0]);
const b = p1[1] - k * p1[0];
y0 = k * p2[0] + b;
const up = p2[1] < y0;
if ((k > 0 && !up) || (k < 0 && up)) {
p2[0] = p1[0];
p2[1] = p3[1];
} else {
p2[0] = p3[0];
p2[1] = p1[1];
}
}
}
}
ea.copyViewElementsToEAforEditing(lines);
ea.addElementsToView();

View File

@@ -0,0 +1,75 @@
/*
![](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-expand-rectangles.gif)
This script expands the width of the selected rectangles until they are all the same width and keep the text centered.
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
const elements = ea.getViewSelectedElements();
const topGroups = ea.getMaximumGroups(elements);
const groupWidths = topGroups
.map((g) =>
g.reduce(
(pre, cur, i) => {
if (i === 0) {
return {
minLeft: cur.x,
maxRight: cur.x + cur.width,
index: i,
};
} else {
return {
minLeft: cur.x < pre.minLeft ? cur.x : pre.minLeft,
maxRight:
cur.x + cur.width > pre.maxRight
? cur.x + cur.width
: pre.maxRight,
index: i,
};
}
},
{ minLeft: 0, maxRight: 0 }
)
)
.map((r) => {
r.width = r.maxRight - r.minLeft;
return r;
});
const maxGroupWidth = Math.max(...groupWidths.map((g) => g.width));
for (var i = 0; i < topGroups.length; i++) {
const rects = topGroups[i]
.filter((el) => el.type === "rectangle")
.sort((lha, rha) => lha.x - rha.x);
const texts = topGroups[i]
.filter((el) => el.type === "text")
.sort((lha, rha) => lha.x - rha.x);
const groupWith = groupWidths[i].width;
if (groupWith < maxGroupWidth) {
const distance = maxGroupWidth - groupWith;
const perRectDistance = distance / rects.length;
for (var j = 0; j < rects.length; j++) {
const rect = rects[j];
rect.x = rect.x + perRectDistance * j - perRectDistance / 2;
rect.width += perRectDistance;
}
for (var j = 0; j < texts.length; j++) {
const text = texts[j];
text.x = text.x + perRectDistance * j;
}
}
}
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView();

View File

@@ -0,0 +1,76 @@
/*
![](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-expand-rectangles.gif)
This script expands the width of the selected rectangles until they are all the same width.
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
const elements = ea.getViewSelectedElements();
const topGroups = ea.getMaximumGroups(elements);
const groupWidths = topGroups
.map((g) =>
g.reduce(
(pre, cur, i) => {
if (i === 0) {
return {
minLeft: cur.x,
maxRight: cur.x + cur.width,
index: i,
};
} else {
return {
minLeft: cur.x < pre.minLeft ? cur.x : pre.minLeft,
maxRight:
cur.x + cur.width > pre.maxRight
? cur.x + cur.width
: pre.maxRight,
index: i,
};
}
},
{ minLeft: 0, maxRight: 0 }
)
)
.map((r) => {
r.width = r.maxRight - r.minLeft;
return r;
});
const maxGroupWidth = Math.max(...groupWidths.map((g) => g.width));
for (var i = 0; i < topGroups.length; i++) {
const rects = topGroups[i]
.filter((el) => el.type === "rectangle")
.sort((lha, rha) => lha.x - rha.x);
const texts = topGroups[i]
.filter((el) => el.type === "text")
.sort((lha, rha) => lha.x - rha.x);
const groupWith = groupWidths[i].width;
if (groupWith < maxGroupWidth) {
const distance = maxGroupWidth - groupWith;
const perRectDistance = distance / rects.length;
for (var j = 0; j < rects.length; j++) {
const rect = rects[j];
rect.x = rect.x + perRectDistance * j;
rect.width += perRectDistance;
}
for (var j = 0; j < texts.length; j++) {
const text = texts[j];
text.x = text.x + perRectDistance * j;
}
}
}
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView();

View File

@@ -0,0 +1,75 @@
/*
![](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-expand-rectangles.gif)
This script expands the height of the selected rectangles until they are all the same height and keep the text centered.
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
const elements = ea.getViewSelectedElements();
const topGroups = ea.getMaximumGroups(elements);
const groupHeights = topGroups
.map((g) =>
g.reduce(
(pre, cur, i) => {
if (i === 0) {
return {
minTop: cur.y,
maxBottom: cur.y + cur.height,
index: i,
};
} else {
return {
minTop: cur.y < pre.minTop ? cur.y : pre.minTop,
maxBottom:
cur.y + cur.height > pre.maxBottom
? cur.y + cur.height
: pre.maxBottom,
index: i,
};
}
},
{ minTop: 0, maxBottom: 0 }
)
)
.map((r) => {
r.height = r.maxBottom - r.minTop;
return r;
});
const maxGroupHeight = Math.max(...groupHeights.map((g) => g.height));
for (var i = 0; i < topGroups.length; i++) {
const rects = topGroups[i]
.filter((el) => el.type === "rectangle")
.sort((lha, rha) => lha.y - rha.y);
const texts = topGroups[i]
.filter((el) => el.type === "text")
.sort((lha, rha) => lha.y - rha.y);
const groupWith = groupHeights[i].height;
if (groupWith < maxGroupHeight) {
const distance = maxGroupHeight - groupWith;
const perRectDistance = distance / rects.length;
for (var j = 0; j < rects.length; j++) {
const rect = rects[j];
rect.y = rect.y + perRectDistance * j - perRectDistance / 2;
rect.height += perRectDistance;
}
for (var j = 0; j < texts.length; j++) {
const text = texts[j];
text.y = text.y + perRectDistance * j;
}
}
}
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView();

View File

@@ -0,0 +1,73 @@
/*
![](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-expand-rectangles.gif)
This script expands the height of the selected rectangles until they are all the same height.
```javascript
*/
const elements = ea.getViewSelectedElements();
const topGroups = ea.getMaximumGroups(elements);
const groupHeights = topGroups
.map((g) =>
g.reduce(
(pre, cur, i) => {
if (i === 0) {
return {
minTop: cur.y,
maxBottom: cur.y + cur.height,
index: i,
};
} else {
return {
minTop: cur.y < pre.minTop ? cur.y : pre.minTop,
maxBottom:
cur.y + cur.height > pre.maxBottom
? cur.y + cur.height
: pre.maxBottom,
index: i,
};
}
},
{ minTop: 0, maxBottom: 0 }
)
)
.map((r) => {
r.height = r.maxBottom - r.minTop;
return r;
});
const maxGroupHeight = Math.max(...groupHeights.map((g) => g.height));
for (var i = 0; i < topGroups.length; i++) {
const rects = topGroups[i]
.filter((el) => el.type === "rectangle")
.sort((lha, rha) => lha.y - rha.y);
const texts = topGroups[i]
.filter((el) => el.type === "text")
.sort((lha, rha) => lha.y - rha.y);
const groupWith = groupHeights[i].height;
if (groupWith < maxGroupHeight) {
const distance = maxGroupHeight - groupWith;
const perRectDistance = distance / rects.length;
for (var j = 0; j < rects.length; j++) {
const rect = rects[j];
rect.y = rect.y + perRectDistance * j;
rect.height += perRectDistance;
}
for (var j = 0; j < texts.length; j++) {
const text = texts[j];
text.y = text.y + perRectDistance * j;
}
}
}
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView();

View File

@@ -0,0 +1,37 @@
/*
![](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-fix-space-demo.png)
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.
```javascript
*/
const spacing = parseInt (await utils.inputPrompt("spacing?","number","8"));
if(isNaN(spacing)) {
return;
}
const elements=ea.getViewSelectedElements();
const topGroups = ea.getMaximumGroups(elements);
const groups = topGroups.sort((lha,rha) => lha[0].x - rha[0].x);
for(var i=0; i<groups.length; i++) {
if(i > 0) {
const preGroup = groups[i-1];
const curGroup = groups[i];
const preRight = Math.max(...preGroup.map(el => el.x + el.width));
const curLeft = Math.min(...curGroup.map(el => el.x));
const distance = curLeft - preRight - spacing;
for(const curEl of curGroup) {
curEl.x = curEl.x - distance;
}
}
}
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView();

View File

@@ -0,0 +1,37 @@
/*
![](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-fixed-vertical-distance.png)
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.
```javascript
*/
const spacing = parseInt (await utils.inputPrompt("spacing?","number","8"));
if(isNaN(spacing)) {
return;
}
const elements=ea.getViewSelectedElements();
const topGroups = ea.getMaximumGroups(elements);
const groups = topGroups.sort((lha,rha) => lha[0].y - rha[0].y);
for(var i=0; i<groups.length; i++) {
if(i > 0) {
const preGroup = groups[i-1];
const curGroup = groups[i];
const preBottom = Math.max(...preGroup.map(el => el.y + el.height));
const curTop = Math.min(...curGroup.map(el => el.y));
const distance = curTop - preBottom - spacing;
for(const curEl of curGroup) {
curEl.y = curEl.y - distance;
}
}
}
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView();

22
ea-scripts/Font Family.md Normal file
View File

@@ -0,0 +1,22 @@
/*
![](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-font-family.jpg)
Sets font family of the text block (Virgil, Helvetica, Cascadia). Useful if you want to set a keyboard shortcut for selecting font family.
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
elements = ea.getViewSelectedElements().filter((el)=>el.type==="text");
if(elements.length===0) return;
let font = ["Virgil","Helvetica","Cascadia"];
font = parseInt(await utils.suggester(font,["1","2","3"]));
if (isNaN(font)) return;
elements.forEach((el)=>el.fontFamily = font);
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView();

View File

@@ -1,6 +1,11 @@
/*
![](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-grid.jpg)
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.
See documentation for more details:

View File

@@ -0,0 +1,289 @@
/*
![](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/darken-lighten-background-color.png)
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.
The color conversion method was copied from [color-convert](https://github.com/Qix-/color-convert).
```javascript
*/
const elements = ea
.getViewSelectedElements()
.filter((el) =>
["rectangle", "ellipse", "diamond", "image"].includes(el.type)
);
ea.copyViewElementsToEAforEditing(elements);
for (const el of ea.getElements()) {
const color = colorNameToHex(el.backgroundColor);
const rgbColor = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(color);
if (rgbColor) {
const r = parseInt(rgbColor[1], 16);
const g = parseInt(rgbColor[2], 16);
const b = parseInt(rgbColor[3], 16);
const originalRgb = [r, g, b];
const hsl = rgbToHsl(originalRgb);
const step = 2;
const newLightness = hsl[2] + step;
if (newLightness < 100) {
hsl[2] = newLightness;
}
const newRgb = hslToRgb(hsl);
el.backgroundColor = "#" + rgbToHexString(newRgb);
}
}
ea.addElementsToView();
function rgbToHexString(args) {
const integer =
((Math.round(args[0]) & 0xff) << 16) +
((Math.round(args[1]) & 0xff) << 8) +
(Math.round(args[2]) & 0xff);
const string = integer.toString(16).toUpperCase();
return "000000".substring(string.length) + string;
}
function hslToRgb(hsl) {
const h = hsl[0] / 360;
const s = hsl[1] / 100;
const l = hsl[2] / 100;
let t2;
let t3;
let val;
if (s === 0) {
val = l * 255;
return [val, val, val];
}
if (l < 0.5) {
t2 = l * (1 + s);
} else {
t2 = l + s - l * s;
}
const t1 = 2 * l - t2;
const rgb = [0, 0, 0];
for (let i = 0; i < 3; i++) {
t3 = h + (1 / 3) * -(i - 1);
if (t3 < 0) {
t3++;
}
if (t3 > 1) {
t3--;
}
if (6 * t3 < 1) {
val = t1 + (t2 - t1) * 6 * t3;
} else if (2 * t3 < 1) {
val = t2;
} else if (3 * t3 < 2) {
val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
} else {
val = t1;
}
rgb[i] = val * 255;
}
return rgb;
}
function rgbToHsl(rgb) {
const r = rgb[0] / 255;
const g = rgb[1] / 255;
const b = rgb[2] / 255;
const min = Math.min(r, g, b);
const max = Math.max(r, g, b);
const delta = max - min;
let h;
let s;
if (max === min) {
h = 0;
} else if (r === max) {
h = (g - b) / delta;
} else if (g === max) {
h = 2 + (b - r) / delta;
} else if (b === max) {
h = 4 + (r - g) / delta;
}
h = Math.min(h * 60, 360);
if (h < 0) {
h += 360;
}
const l = (min + max) / 2;
if (max === min) {
s = 0;
} else if (l <= 0.5) {
s = delta / (max + min);
} else {
s = delta / (2 - max - min);
}
return [h, s * 100, l * 100];
}
function colorNameToHex(color) {
const colors = {
aliceblue: "#f0f8ff",
antiquewhite: "#faebd7",
aqua: "#00ffff",
aquamarine: "#7fffd4",
azure: "#f0ffff",
beige: "#f5f5dc",
bisque: "#ffe4c4",
black: "#000000",
blanchedalmond: "#ffebcd",
blue: "#0000ff",
blueviolet: "#8a2be2",
brown: "#a52a2a",
burlywood: "#deb887",
cadetblue: "#5f9ea0",
chartreuse: "#7fff00",
chocolate: "#d2691e",
coral: "#ff7f50",
cornflowerblue: "#6495ed",
cornsilk: "#fff8dc",
crimson: "#dc143c",
cyan: "#00ffff",
darkblue: "#00008b",
darkcyan: "#008b8b",
darkgoldenrod: "#b8860b",
darkgray: "#a9a9a9",
darkgreen: "#006400",
darkkhaki: "#bdb76b",
darkmagenta: "#8b008b",
darkolivegreen: "#556b2f",
darkorange: "#ff8c00",
darkorchid: "#9932cc",
darkred: "#8b0000",
darksalmon: "#e9967a",
darkseagreen: "#8fbc8f",
darkslateblue: "#483d8b",
darkslategray: "#2f4f4f",
darkturquoise: "#00ced1",
darkviolet: "#9400d3",
deeppink: "#ff1493",
deepskyblue: "#00bfff",
dimgray: "#696969",
dodgerblue: "#1e90ff",
firebrick: "#b22222",
floralwhite: "#fffaf0",
forestgreen: "#228b22",
fuchsia: "#ff00ff",
gainsboro: "#dcdcdc",
ghostwhite: "#f8f8ff",
gold: "#ffd700",
goldenrod: "#daa520",
gray: "#808080",
green: "#008000",
greenyellow: "#adff2f",
honeydew: "#f0fff0",
hotpink: "#ff69b4",
"indianred ": "#cd5c5c",
indigo: "#4b0082",
ivory: "#fffff0",
khaki: "#f0e68c",
lavender: "#e6e6fa",
lavenderblush: "#fff0f5",
lawngreen: "#7cfc00",
lemonchiffon: "#fffacd",
lightblue: "#add8e6",
lightcoral: "#f08080",
lightcyan: "#e0ffff",
lightgoldenrodyellow: "#fafad2",
lightgrey: "#d3d3d3",
lightgreen: "#90ee90",
lightpink: "#ffb6c1",
lightsalmon: "#ffa07a",
lightseagreen: "#20b2aa",
lightskyblue: "#87cefa",
lightslategray: "#778899",
lightsteelblue: "#b0c4de",
lightyellow: "#ffffe0",
lime: "#00ff00",
limegreen: "#32cd32",
linen: "#faf0e6",
magenta: "#ff00ff",
maroon: "#800000",
mediumaquamarine: "#66cdaa",
mediumblue: "#0000cd",
mediumorchid: "#ba55d3",
mediumpurple: "#9370d8",
mediumseagreen: "#3cb371",
mediumslateblue: "#7b68ee",
mediumspringgreen: "#00fa9a",
mediumturquoise: "#48d1cc",
mediumvioletred: "#c71585",
midnightblue: "#191970",
mintcream: "#f5fffa",
mistyrose: "#ffe4e1",
moccasin: "#ffe4b5",
navajowhite: "#ffdead",
navy: "#000080",
oldlace: "#fdf5e6",
olive: "#808000",
olivedrab: "#6b8e23",
orange: "#ffa500",
orangered: "#ff4500",
orchid: "#da70d6",
palegoldenrod: "#eee8aa",
palegreen: "#98fb98",
paleturquoise: "#afeeee",
palevioletred: "#d87093",
papayawhip: "#ffefd5",
peachpuff: "#ffdab9",
peru: "#cd853f",
pink: "#ffc0cb",
plum: "#dda0dd",
powderblue: "#b0e0e6",
purple: "#800080",
rebeccapurple: "#663399",
red: "#ff0000",
rosybrown: "#bc8f8f",
royalblue: "#4169e1",
saddlebrown: "#8b4513",
salmon: "#fa8072",
sandybrown: "#f4a460",
seagreen: "#2e8b57",
seashell: "#fff5ee",
sienna: "#a0522d",
silver: "#c0c0c0",
skyblue: "#87ceeb",
slateblue: "#6a5acd",
slategray: "#708090",
snow: "#fffafa",
springgreen: "#00ff7f",
steelblue: "#4682b4",
tan: "#d2b48c",
teal: "#008080",
thistle: "#d8bfd8",
tomato: "#ff6347",
turquoise: "#40e0d0",
violet: "#ee82ee",
wheat: "#f5deb3",
white: "#ffffff",
whitesmoke: "#f5f5f5",
yellow: "#ffff00",
yellowgreen: "#9acd32",
};
if (typeof colors[color.toLowerCase()] != "undefined")
return colors[color.toLowerCase()];
return color;
}

View File

@@ -0,0 +1,187 @@
/*
![](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-modify-background-color-opacity.png)
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.
```javascript
*/
const alpha = parseFloat(await utils.inputPrompt("Background color opacity?","number","0.6"));
if(isNaN(alpha)) {
return;
}
const elements=ea.getViewSelectedElements().filter((el)=>["rectangle","ellipse","diamond","line","image"].includes(el.type));
ea.copyViewElementsToEAforEditing(elements);
ea.getElements().forEach((el)=>{
const color = colorNameToHex(el.backgroundColor);
const rgbColor = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(color);
if(rgbColor) {
const r = parseInt(rgbColor[1], 16);
const g = parseInt(rgbColor[2], 16);
const b = parseInt(rgbColor[3], 16);
el.backgroundColor=`rgba(${r},${g},${b},${alpha})`;
}
else {
const rgbaColor = /^rgba\((\d+,\d+,\d+,)(\d*\.?\d*)\)$/i.exec(color);
if(rgbaColor) {
el.backgroundColor=`rgba(${rgbaColor[1]}${alpha})`;
}
}
});
ea.addElementsToView();
function colorNameToHex(color) {
const colors = {
"aliceblue":"#f0f8ff",
"antiquewhite":"#faebd7",
"aqua":"#00ffff",
"aquamarine":"#7fffd4",
"azure":"#f0ffff",
"beige":"#f5f5dc",
"bisque":"#ffe4c4",
"black":"#000000",
"blanchedalmond":"#ffebcd",
"blue":"#0000ff",
"blueviolet":"#8a2be2",
"brown":"#a52a2a",
"burlywood":"#deb887",
"cadetblue":"#5f9ea0",
"chartreuse":"#7fff00",
"chocolate":"#d2691e",
"coral":"#ff7f50",
"cornflowerblue":"#6495ed",
"cornsilk":"#fff8dc",
"crimson":"#dc143c",
"cyan":"#00ffff",
"darkblue":"#00008b",
"darkcyan":"#008b8b",
"darkgoldenrod":"#b8860b",
"darkgray":"#a9a9a9",
"darkgreen":"#006400",
"darkkhaki":"#bdb76b",
"darkmagenta":"#8b008b",
"darkolivegreen":"#556b2f",
"darkorange":"#ff8c00",
"darkorchid":"#9932cc",
"darkred":"#8b0000",
"darksalmon":"#e9967a",
"darkseagreen":"#8fbc8f",
"darkslateblue":"#483d8b",
"darkslategray":"#2f4f4f",
"darkturquoise":"#00ced1",
"darkviolet":"#9400d3",
"deeppink":"#ff1493",
"deepskyblue":"#00bfff",
"dimgray":"#696969",
"dodgerblue":"#1e90ff",
"firebrick":"#b22222",
"floralwhite":"#fffaf0",
"forestgreen":"#228b22",
"fuchsia":"#ff00ff",
"gainsboro":"#dcdcdc",
"ghostwhite":"#f8f8ff",
"gold":"#ffd700",
"goldenrod":"#daa520",
"gray":"#808080",
"green":"#008000",
"greenyellow":"#adff2f",
"honeydew":"#f0fff0",
"hotpink":"#ff69b4",
"indianred ":"#cd5c5c",
"indigo":"#4b0082",
"ivory":"#fffff0",
"khaki":"#f0e68c",
"lavender":"#e6e6fa",
"lavenderblush":"#fff0f5",
"lawngreen":"#7cfc00",
"lemonchiffon":"#fffacd",
"lightblue":"#add8e6",
"lightcoral":"#f08080",
"lightcyan":"#e0ffff",
"lightgoldenrodyellow":"#fafad2",
"lightgrey":"#d3d3d3",
"lightgreen":"#90ee90",
"lightpink":"#ffb6c1",
"lightsalmon":"#ffa07a",
"lightseagreen":"#20b2aa",
"lightskyblue":"#87cefa",
"lightslategray":"#778899",
"lightsteelblue":"#b0c4de",
"lightyellow":"#ffffe0",
"lime":"#00ff00",
"limegreen":"#32cd32",
"linen":"#faf0e6",
"magenta":"#ff00ff",
"maroon":"#800000",
"mediumaquamarine":"#66cdaa",
"mediumblue":"#0000cd",
"mediumorchid":"#ba55d3",
"mediumpurple":"#9370d8",
"mediumseagreen":"#3cb371",
"mediumslateblue":"#7b68ee",
"mediumspringgreen":"#00fa9a",
"mediumturquoise":"#48d1cc",
"mediumvioletred":"#c71585",
"midnightblue":"#191970",
"mintcream":"#f5fffa",
"mistyrose":"#ffe4e1",
"moccasin":"#ffe4b5",
"navajowhite":"#ffdead",
"navy":"#000080",
"oldlace":"#fdf5e6",
"olive":"#808000",
"olivedrab":"#6b8e23",
"orange":"#ffa500",
"orangered":"#ff4500",
"orchid":"#da70d6",
"palegoldenrod":"#eee8aa",
"palegreen":"#98fb98",
"paleturquoise":"#afeeee",
"palevioletred":"#d87093",
"papayawhip":"#ffefd5",
"peachpuff":"#ffdab9",
"peru":"#cd853f",
"pink":"#ffc0cb",
"plum":"#dda0dd",
"powderblue":"#b0e0e6",
"purple":"#800080",
"rebeccapurple":"#663399",
"red":"#ff0000",
"rosybrown":"#bc8f8f",
"royalblue":"#4169e1",
"saddlebrown":"#8b4513",
"salmon":"#fa8072",
"sandybrown":"#f4a460",
"seagreen":"#2e8b57",
"seashell":"#fff5ee",
"sienna":"#a0522d",
"silver":"#c0c0c0",
"skyblue":"#87ceeb",
"slateblue":"#6a5acd",
"slategray":"#708090",
"snow":"#fffafa",
"springgreen":"#00ff7f",
"steelblue":"#4682b4",
"tan":"#d2b48c",
"teal":"#008080",
"thistle":"#d8bfd8",
"tomato":"#ff6347",
"turquoise":"#40e0d0",
"violet":"#ee82ee",
"wheat":"#f5deb3",
"white":"#ffffff",
"whitesmoke":"#f5f5f5",
"yellow":"#ffff00",
"yellowgreen":"#9acd32"
};
if (typeof colors[color.toLowerCase()] != 'undefined')
return colors[color.toLowerCase()];
return color;
}

View File

@@ -1,15 +1,21 @@
/*
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.
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.
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
width = await utils.inputPrompt("Width?");
const elements=ea.getViewSelectedElements();
ea.copyViewElementsToEAforEditing(elements);
ea.getElements().forEach((el)=>el.strokeWidth=width);
ea.addElementsToView();
/*
![](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-stroke-width.jpg)
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.
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
let width = (ea.getViewSelectedElement().strokeWidth??1).toString();
width = await utils.inputPrompt("Width?","number",width);
const elements=ea.getViewSelectedElements();
ea.copyViewElementsToEAforEditing(elements);
ea.getElements().forEach((el)=>el.strokeWidth=width);
ea.addElementsToView();

View File

@@ -0,0 +1,121 @@
/*
![](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-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
*/
const curVersion = app.plugins.manifests["obsidian-excalidraw-plugin"].version;
if(curVersion < "1.5.15") new Notice("please update Excalidraw plugin to the latest version");
let token = ea.getScriptSettings()?.token;
const BASE_URL = "https://ocr.taskbone.com";
//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});
} 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();
const API = ea.getExcalidrawAPI();
st = API.getAppState();
st.selectedElementIds = {};
st.selectedElementIds[id] = true;
API.updateScene({appState: st});
API.zoomToFit(ea.getViewSelectedElements(),1);
//utility function
function notice(message) {
new Notice(message,10000);
console.log(message);
}

42
ea-scripts/README.md Normal file
View File

@@ -0,0 +1,42 @@
# Excalidraw Script Engine scripts library
Click to watch the intro video:
[![Script Engine](https://user-images.githubusercontent.com/14358394/145684531-8d9c2992-59ac-4ebc-804a-4cce1777ded2.jpg)](https://youtu.be/hePJcObHIso)
See the [Excalidraw Script Engine](https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html) documentation for more details.
## How to install scripts into your Obsidian Vault
Open the script you are interested in and save it 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-download-raw.jpg)
## List of available scripts
|Title|Description|Icon|Contributor|
|----|----|----|----|
|[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)|
|[Bullet Point](Bullet%20Point.md)|This script will add a small circle to the top left of each text element in the selection and add the text and the "bullet point" into a group.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-bullet-point.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Connect elements](Connect%20elements.md)|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).|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-connect-elements.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Convert text to link with folder and alias](Convert%20text%20to%20link%20with%20folder%20and%20alias.md)|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.|`original text` => `[[selected folder/original text\|original text]]`|[@zsviczian](https://github.com/zsviczian)|
|[Create new markdown file and embed into active drawing](Create%20new%20markdown%20file%20and%20embed%20into%20active%20drawing.md)|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.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-create-and-embed-new-markdown-file.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Darken background color](Darken%20background%20color.md)|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.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/darken-lighten-background-color.png)|[@1-2-3](https://github.com/1-2-3)|
|[Dimensions](Dimensions.md)|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.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-dimensions.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Elbow connectors](Elbow%20connectors.md)|This script converts the selected connectors to elbows.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/elbow-connectors.png)|[@1-2-3](https://github.com/1-2-3)|
|[Expand rectangles horizontally keep text centered](Expand%20rectangles%20horizontally%20keep%20text20%centered.md)|This script expands the width of the selected rectangles until they are all the same width and keep the text centered.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-expand-rectangles.gif)|[@1-2-3](https://github.com/1-2-3)|
|[Expand rectangles horizontally](Expand%20rectangles%20horizontally.md)|This script expands the width of the selected rectangles until they are all the same width.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-expand-rectangles.gif)|[@1-2-3](https://github.com/1-2-3)|
|[Expand rectangles vertically keep text centered](Expand%20rectangles%20vertically%20keep%20text%20centered.md)|This script expands the height of the selected rectangles until they are all the same height and keep the text centered.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-expand-rectangles.gif)|[@1-2-3](https://github.com/1-2-3)|
|[Expand rectangles vertically](Expand%20rectangles%20vertically.md)|This script expands the height of the selected rectangles until they are all the same height.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-expand-rectangles.gif)|[@1-2-3](https://github.com/1-2-3)|
|[Fixed spacing](Fixed%20spacing.md)|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.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fix-space-demo.png)|[@1-2-3](https://github.com/1-2-3)|
|[Fixed vertical distance](Fixed%20vertical%20distance.md)|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.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fixed-vertical-distance.png)|[@1-2-3](https://github.com/1-2-3)|
|[Font Family](Font%20Family.md)|Sets font family of the text block (Virgil, Helvetica, Cascadia). Useful if you want to set a keyboard shortcut for selecting font family.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-font-family.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Grid](Grid.md)|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.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-grid.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Lighten background color](Lighten%20background%20color.md)|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.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/darken-lighten-background-color.png)|[@1-2-3](https://github.com/1-2-3)|
|[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)|
|[Modify stroke width of selected elements](Modify%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)|
|[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)|
|[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)|
|[Set Link Alias](Set20%Link20%Alias.md)|Iterates all of the links in the selected TextElements and prompts the user to set or modify the alias for each link found.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-set-link-alias.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)|
|[Text Align](Text%20Align.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)|
|[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 SHIFT+2 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)|

View File

@@ -0,0 +1,23 @@
/*
![](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-reverse-arrow.jpg)
Reverse the direction of **arrows** within the scope of selected elements.
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
elements = ea.getViewSelectedElements().filter((el)=>el.type==="arrow");
if(!elements || elements.length===0) return;
elements.forEach((el)=>{
const start = el.startArrowhead;
el.startArrowhead = el.endArrowhead;
el.endArrowhead = start;
});
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView();

View File

@@ -0,0 +1,53 @@
/*
![](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-set-link-alias.jpg)
Iterates all of the links in the selected TextElements and prompts the user to set or modify the alias for each link found.
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
elements = ea.getViewSelectedElements().filter((el)=>el.type==="text");
// `[[markdown links]]`
for(el of elements) { //doing for instead of .forEach due to await inputPrompt
parts = el.rawText.split(/(\[\[[\w\W]*?]])/);
newText = "";
for(t of parts) { //doing for instead of .map due to await inputPrompt
if(!t.match(/(\[\[[\w\W]*?]])/)) {
newText += t;
} else {
original = t.split(/\[\[|]]/)[1];
cut = original.indexOf("|");
alias = cut === -1 ? "" : original.substring(cut+1);
link = cut === -1 ? original : original.substring(0,cut);
alias = await utils.inputPrompt(`Alias for [[${link}]]`,"type alias here",alias);
newText += `[[${link}|${alias}]]`;
}
}
el.rawText = newText;
};
// `[wiki](links)`
for(el of elements) { //doing for instead of .forEach due to await inputPrompt
parts = el.rawText.split(/(\[[\w\W]*?]\([\w\W]*?\))/);
newText = "";
for(t of parts) { //doing for instead of .map due to await inputPrompt
if(!t.match(/(\[[\w\W]*?]\([\w\W]*?\))/)) {
newText += t;
} else {
alias = t.match(/\[([\w\W]*?)]/)[1];
link = t.match(/\(([\w\W]*?)\)/)[1];
alias = await utils.inputPrompt(`Alias for [[${link}]]`,"type alias here",alias);
newText += `[[${link}|${alias}]]`;
}
}
el.rawText = newText;
};
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView();

View File

@@ -0,0 +1,27 @@
/*
## requires Excalidraw 1.5.1 or higher
![](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-split-lines.jpg)
Split lines of text into separate text elements for easier reorganization
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
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]);
}
});
ea.addElementsToView();
ea.deleteViewElements(elements);

21
ea-scripts/Text Align.md Normal file
View File

@@ -0,0 +1,21 @@
/*
![](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-align.jpg)
Sets text alignment of text block (cetner, right, left). Useful if you want to set a keyboard shortcut for selecting text alignment.
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
elements = ea.getViewSelectedElements().filter((el)=>el.type==="text");
if(elements.length===0) return;
let align = ["left","right","center"];
align = await utils.suggester(align,align);
elements.forEach((el)=>el.textAlign = align);
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView();

View File

@@ -0,0 +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);
}

View File

@@ -0,0 +1,15 @@
/*
![](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.
Similar to Excalidraw standard SHIFT+2 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)
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
elements = ea.getViewSelectedElements();
api = ea.getExcalidrawAPI();
api.zoomToFit(elements,10);

210
ea-scripts/index.md Normal file
View File

@@ -0,0 +1,210 @@
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]]
# Intorducing 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
- [[#Box Selected Elements]]
- [[#Bullet Point]]
- [[#Connect elements]]
- [[#Convert text to link with folder and alias]]
- [[#Create new markdown file and embed into active drawing]]
- [[#Darken background color]]
- [[#Dimensions]]
- [[#Elbow connectors]]
- [[#Expand rectangles horizontally keep text centered]]
- [[#Expand rectangles horizontally]]
- [[#Expand rectangles vertically keep text centered]]
- [[#Expand rectangles vertically]]
- [[#Fixed spacing]]
- [[#Fixed vertical distance]]
- [[#Font Family]]
- [[#Grid]]
- [[#Lighten background color]]
- [[#Modify background color opacity]]
- [[#Modify stroke width of selected elements]]
- [[#OCR - Optical Character Recognition]]
- [[#Reverse arrows]]
- [[#Set Link Alias]]
- [[#Split text by lines]]
- [[#Text Align]]
- [[#Transfer TextElements to Excalidraw markdown metadata]]
- [[#Zoom to Fit Selected Elements]]
## 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>
## Bullet Point
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Bullet%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/Bullet%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 "bullet point" into a group.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-bullet-point.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 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>
## 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>
## Dimensions
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Dimensions.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/Dimensions.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>
## 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 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
```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>
## Font Family
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Font%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/Font%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>
## Grid
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Grid.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/Grid.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>
## 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>
## 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>
## Modify stroke width of selected elements
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Modify%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/Modify%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>
## 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>
## 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>
## 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>
## 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>
## Text Align
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Text%20Align.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%20Align.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>
## 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 SHIFT+2 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
images/elbow-connectors.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 864 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
images/scripts-grid.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

BIN
images/scripts-ocr.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-excalidraw-plugin",
"name": "Excalidraw",
"version": "1.5.0",
"version": "1.5.15",
"minAppVersion": "0.12.16",
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
"author": "Zsolt Viczian",

View File

@@ -12,7 +12,7 @@
"author": "",
"license": "MIT",
"dependencies": {
"@zsviczian/excalidraw": "0.10.0-obsidian-18",
"@zsviczian/excalidraw": "0.10.0-obsidian-33",
"monkey-around": "^2.2.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
@@ -34,7 +34,7 @@
"cross-env": "^7.0.3",
"html2canvas": "^1.3.2",
"nanoid": "^3.1.23",
"obsidian": "^0.12.16",
"obsidian": "^0.13.11",
"rollup": "^2.52.3",
"rollup-plugin-visualizer": "^5.5.2",
"tslib": "^2.3.1",

View File

@@ -369,7 +369,11 @@ const convertMarkdownToSVG = async (
): Promise<DataURL> => {
//1.
//get the markdown text
const text = (await getTransclusion(linkParts, plugin.app, file)).contents;
let text = (await getTransclusion(linkParts, plugin.app, file)).contents;
if (text === "") {
text =
"# Empty markdown file\nCTRL+Click here to open the file for editing in the current active pane, or CTRL+SHIFT+Click to open it in an adjacent pane.";
}
//2.
//get styles

View File

@@ -6,7 +6,7 @@ import {
ExcalidrawElement,
ExcalidrawBindableElement,
} from "@zsviczian/excalidraw/types/element/types";
import { normalizePath, TFile } from "obsidian";
import { Component, MarkdownRenderer, normalizePath, TFile } from "obsidian";
import ExcalidrawView, { ExportSettings, TextMode } from "./ExcalidrawView";
import { ExcalidrawData } from "./ExcalidrawData";
import {
@@ -48,7 +48,7 @@ export interface ExcalidrawAutomate {
angle: number; //radian
fillStyle: FillStyle; //type FillStyle = "hachure" | "cross-hatch" | "solid"
strokeWidth: number;
storkeStyle: StrokeStyle; //type StrokeStyle = "solid" | "dashed" | "dotted"
strokeStyle: StrokeStyle; //type StrokeStyle = "solid" | "dashed" | "dotted"
roughness: number;
opacity: number;
strokeSharpness: StrokeSharpness; //type StrokeSharpness = "round" | "sharp"
@@ -154,6 +154,7 @@ export interface ExcalidrawAutomate {
deleteViewElements(el: ExcalidrawElement[]): boolean;
getViewSelectedElement(): ExcalidrawElement; //get the selected element in the view, if more are selected, get the first
getViewSelectedElements(): ExcalidrawElement[];
getViewFileForImageElement(el: ExcalidrawElement): TFile | null; //Returns the TFile file handle for the image element
copyViewElementsToEAforEditing(elements: ExcalidrawElement[]): void; //copies elements from view to elementsDict for editing
viewToggleFullScreen(forceViewMode?: boolean): void;
connectObjectWithViewSelectedElement( //connect an object to the selected element in the view
@@ -210,6 +211,11 @@ export interface ExcalidrawAutomate {
b: readonly [number, number],
gap?: number, //if given, element is inflated by this value
): Point[];
//See OCR plugin for example on how to use scriptSettings
activeScript: string; //Set automatically by the ScriptEngine
getScriptSettings(): {}; //Returns script settings. Saves settings in plugin settings, under the activeScript key
setScriptSettings(settings:any):Promise<void>; //sets script settings.
}
declare let window: any;
@@ -218,7 +224,7 @@ export async function initExcalidrawAutomate(
plugin: ExcalidrawPlugin,
): Promise<ExcalidrawAutomate> {
window.ExcalidrawAutomate = {
plugin,
plugin: plugin,
elementsDict: {},
imagesDict: {},
style: {
@@ -227,7 +233,7 @@ export async function initExcalidrawAutomate(
angle: 0,
fillStyle: "hachure",
strokeWidth: 1,
storkeStyle: "solid",
strokeStyle: "solid",
roughness: 1,
opacity: 100,
strokeSharpness: "sharp",
@@ -430,7 +436,7 @@ export async function initExcalidrawAutomate(
files: template?.files ?? {},
};
return plugin.createDrawing(
return plugin.createAndOpenDrawing(
params?.filename
? `${params.filename}.excalidraw.md`
: this.plugin.getNextDefaultFilename(),
@@ -635,6 +641,7 @@ export async function initExcalidrawAutomate(
id?: string,
): string {
id = id ?? nanoid();
const originalText = text;
text = formatting?.wrapAt ? this.wrapText(text, formatting.wrapAt) : text;
const { w, h, baseline } = measureText(
text,
@@ -645,7 +652,7 @@ export async function initExcalidrawAutomate(
const height = formatting?.height ? formatting.height : h;
let boxId: string = null;
const boxPadding = formatting?.boxPadding ?? 10;
const boxPadding = formatting?.boxPadding ?? 30;
if (formatting?.box) {
switch (formatting?.box) {
case "ellipse":
@@ -681,21 +688,32 @@ export async function initExcalidrawAutomate(
);
}
}
const ea = window.ExcalidrawAutomate;
//const ea = window.ExcalidrawAutomate;
const isContainerBound = formatting?.box && formatting.box !== "blob";
this.elementsDict[id] = {
text,
fontSize: ea.style.fontSize,
fontFamily: ea.style.fontFamily,
fontSize: this.style.fontSize,
fontFamily: this.style.fontFamily,
textAlign: formatting?.textAlign
? formatting.textAlign
: ea.style.textAlign,
verticalAlign: ea.style.verticalAlign,
: this.style.textAlign,
verticalAlign: this.style.verticalAlign,
baseline,
...boxedElement(id, "text", topX, topY, width, height),
containerId: isContainerBound ? boxId : null,
originalText: isContainerBound ? originalText : text,
rawText: isContainerBound ? originalText : text,
};
if (boxId) {
if (boxId && formatting?.box === "blob") {
this.addToGroup([id, boxId]);
}
if (boxId && formatting?.box !== "blob") {
const box = this.elementsDict[boxId];
if (!box.boundElements) {
box.boundElements = [];
}
box.boundElements.push({ type: "text", id });
}
return boxId ?? id;
},
addLine(points: [[x: number, y: number]]): string {
@@ -761,16 +779,22 @@ export async function initExcalidrawAutomate(
...boxedElement(id, "arrow", points[0][0], points[0][1], box.w, box.h),
};
if (formatting?.startObjectId) {
if (!this.elementsDict[formatting.startObjectId].boundElementIds) {
this.elementsDict[formatting.startObjectId].boundElementIds = [];
if (!this.elementsDict[formatting.startObjectId].boundElements) {
this.elementsDict[formatting.startObjectId].boundElements = [];
}
this.elementsDict[formatting.startObjectId].boundElementIds.push(id);
this.elementsDict[formatting.startObjectId].boundElements.push({
type: "arrow",
id,
});
}
if (formatting?.endObjectId) {
if (!this.elementsDict[formatting.endObjectId].boundElementIds) {
this.elementsDict[formatting.endObjectId].boundElementIds = [];
if (!this.elementsDict[formatting.endObjectId].boundElements) {
this.elementsDict[formatting.endObjectId].boundElements = [];
}
this.elementsDict[formatting.endObjectId].boundElementIds.push(id);
this.elementsDict[formatting.endObjectId].boundElements.push({
type: "arrow",
id,
});
}
return id;
},
@@ -950,12 +974,13 @@ export async function initExcalidrawAutomate(
},
reset() {
this.clear();
this.activeScript = null;
this.style.strokeColor = "#000000";
this.style.backgroundColor = "transparent";
this.style.angle = 0;
this.style.fillStyle = "hachure";
this.style.strokeWidth = 1;
this.style.storkeStyle = "solid";
this.style.strokeStyle = "solid";
this.style.roughness = 1;
this.style.opacity = 100;
this.style.strokeSharpness = "sharp";
@@ -1028,7 +1053,7 @@ export async function initExcalidrawAutomate(
appState: st,
commitToHistory: true,
});
this.targetView.save();
//this.targetView.save();
return true;
},
getViewSelectedElement(): any {
@@ -1040,8 +1065,8 @@ export async function initExcalidrawAutomate(
errorMessage("targetView not set", "getViewSelectedElements()");
return [];
}
const current = this.targetView?.excalidrawRef?.current;
const selectedElements = current?.getAppState()?.selectedElementIds;
const excalidrawAPI = this.targetView?.excalidrawAPI;
const selectedElements = excalidrawAPI.getAppState()?.selectedElementIds;
if (!selectedElements) {
return [];
}
@@ -1049,9 +1074,46 @@ export async function initExcalidrawAutomate(
if (!selectedElementsKeys) {
return [];
}
return current
const elements: ExcalidrawElement[] = excalidrawAPI
.getSceneElements()
.filter((e: any) => selectedElementsKeys.includes(e.id));
const containerBoundTextElmenetsReferencedInElements = elements
.filter(
(el) =>
el.boundElements &&
el.boundElements.filter((be) => be.type === "text").length > 0,
)
.map(
(el) =>
el.boundElements
.filter((be) => be.type === "text")
.map((be) => be.id)[0],
);
const elementIDs = elements
.map((el) => el.id)
.concat(containerBoundTextElmenetsReferencedInElements);
return this.getViewElements().filter((el: ExcalidrawElement) =>
elementIDs.contains(el.id),
);
},
getViewFileForImageElement(el: ExcalidrawElement): TFile | null {
if (!this.targetView || !this.targetView?._loaded) {
errorMessage("targetView not set", "getViewSelectedElements()");
return null;
}
if (!el || el.type !== "image") {
errorMessage(
"Must provide an image element as input",
"getViewFileForImageElement()",
);
return null;
}
return (this.targetView as ExcalidrawView)?.excalidrawData?.getFile(
el.fileId,
)?.file;
},
copyViewElementsToEAforEditing(elements: ExcalidrawElement[]): void {
elements.forEach((el) => {
@@ -1181,6 +1243,16 @@ export async function initExcalidrawAutomate(
): Point[] {
return intersectElementWithLine(element, a, b, gap);
},
activeScript: null,
getScriptSettings(): {} {
if(!this.activeScript) return null;
return this.plugin.settings.scriptEngineSettings[this.activeScript];
},
async setScriptSettings(settings:any): Promise<void> {
if(!this.activeScript) return null;
this.plugin.settings.scriptEngineSettings[this.activeScript] = settings;
await this.plugin.saveSettings();
},
};
await initFonts();
return window.ExcalidrawAutomate;
@@ -1223,16 +1295,16 @@ function boxedElement(
backgroundColor: ea.style.backgroundColor,
fillStyle: ea.style.fillStyle,
strokeWidth: ea.style.strokeWidth,
storkeStyle: ea.style.storkeStyle,
strokeStyle: ea.style.strokeStyle,
roughness: ea.style.roughness,
opacity: ea.style.opacity,
strokeSharpness: ea.style.strokeSharpness,
seed: Math.floor(Math.random() * 100000),
version: 1,
versionNounce: 1,
versionNonce: 1,
isDeleted: false,
groupIds: [] as any,
boundElementIds: [] as any,
boundElements: [] as any,
};
}
@@ -1446,7 +1518,7 @@ export async function createSVG(
elements = elements.concat(automateElements);
const svg = await getSVG(
{
//createDrawing
//createAndOpenDrawing
type: "excalidraw",
version: 2,
source: "https://excalidraw.com",

View File

@@ -1,3 +1,10 @@
/**
** About the various text fields of textElements
** rawText vs. text vs. original text
text: The displyed text. This will have linebreaks if wrapped & will be the parsed text or the original-markup depending on Obsidian view mode
originalText: this is the text without added linebreaks for wrapping. This will be parsed or markup depending on view mode
rawText: text with original markdown markup and without the added linebreaks for wrapping
*/
import { App, TFile } from "obsidian";
import {
nanoid,
@@ -6,6 +13,7 @@ import {
FRONTMATTER_KEY_CUSTOM_URL_PREFIX,
FRONTMATTER_KEY_DEFAULT_MODE,
fileid,
REG_BLOCK_REF_CLEAN,
} from "./constants";
import { measureText } from "./ExcalidrawAutomate";
import ExcalidrawPlugin from "./main";
@@ -63,7 +71,11 @@ export const REGEX_LINK = {
: parts.value[6];
},
getWrapLength: (parts: IteratorResult<RegExpMatchArray, any>): number => {
return parts.value[8];
const len = parseInt(parts.value[8]);
if (isNaN(len)) {
return null;
}
return len;
},
};
@@ -100,8 +112,40 @@ export function getMarkdownDrawingSection(jsonString: string) {
)}${String.fromCharCode(96)}${String.fromCharCode(96)}\n%%`;
}
/**
*
* @param text - TextElement.text
* @param originalText - TextElement.originalText
* @returns null if the textElement is not wrapped or the longest line in the text element
*/
const estimateMaxLineLen = (text: string, originalText: string): number => {
if (!originalText || !text) {
return null;
}
if (text === originalText) {
return null;
} //text will contain extra new line characters if wrapped
let maxLineLen = 0; //will be non-null if text is container bound and multi line
const splitText = text.split("\n");
if (splitText.length === 1) {
return null;
}
for (const line of splitText) {
if (line.length > maxLineLen) {
maxLineLen = line.length;
}
}
return maxLineLen;
};
const wrap = (text: string, lineLen: number) =>
lineLen ? wrapText(text, lineLen, false, 0) : text;
export class ExcalidrawData {
private textElements: Map<string, { raw: string; parsed: string }> = null;
private textElements: Map<
string,
{ raw: string; parsed: string; wrapAt: number | null }
> = null;
public scene: any = null;
private file: TFile = null;
private app: App;
@@ -122,6 +166,40 @@ export class ExcalidrawData {
this.equations = new Map<FileId, { latex: string; isLoaded: boolean }>();
}
/**
* 1.5.4: for backward compatibility following the release of container bound text elements and the depreciation boundElementIds field
*/
private convert_boundElementIds_to_boundElements() {
if (!this.scene) {
return;
}
for (let i = 0; i < this.scene.elements?.length; i++) {
//convert .boundElementIds to boundElements
if (this.scene.elements[i].boundElementIds) {
if (!this.scene.elements[i].boundElements) {
this.scene.elements[i].boundElements = [];
}
this.scene.elements[i].boundElements = this.scene.elements[
i
].boundElements.concat(
this.scene.elements[i].boundElementIds.map((id: string) => ({
type: "arrow",
id,
})),
);
delete this.scene.elements[i].boundElementIds;
}
//add containerId to TextElements if missing
if (
this.scene.elements[i].type === "text" &&
!this.scene.elements[i].containerId
) {
this.scene.elements[i].containerId = null;
}
}
}
/**
* Loads a new drawing
* @param {TFile} file - the MD file containing the Excalidraw drawing
@@ -132,8 +210,14 @@ export class ExcalidrawData {
file: TFile,
textMode: TextMode,
): Promise<boolean> {
if (!file) {
return false;
}
this.loaded = false;
this.textElements = new Map<string, { raw: string; parsed: string }>();
this.textElements = new Map<
string,
{ raw: string; parsed: string; wrapAt: number }
>();
if (this.file != file) {
//this is a reload - files and equations will take care of reloading when needed
this.files.clear();
@@ -184,6 +268,8 @@ export class ExcalidrawData {
this.scene.appState.theme = isObsidianThemeDark() ? "dark" : "light";
}
this.convert_boundElementIds_to_boundElements();
data = data.substring(0, sceneJSONandPOS.pos);
//The Markdown # Text Elements take priority over the JSON text elements. Assuming the scenario in which the link was updated due to filename changes
@@ -208,9 +294,14 @@ export class ExcalidrawData {
while (!(parts = res.next()).done) {
const text = data.substring(position, parts.value.index);
const id: string = parts.value[1];
this.textElements.set(id, { raw: text, parsed: await this.parse(text) });
//this will set the rawText field of text elements imported from files before 1.3.14, and from other instances of Excalidraw
const textEl = this.scene.elements.filter((el: any) => el.id === id)[0];
const wrapAt = estimateMaxLineLen(textEl.text, textEl.originalText);
this.textElements.set(id, {
raw: text,
parsed: await this.parse(text),
wrapAt,
});
//this will set the rawText field of text elements imported from files before 1.3.14, and from other instances of Excalidraw
if (textEl && (!textEl.rawText || textEl.rawText === "")) {
textEl.rawText = text;
}
@@ -252,9 +343,15 @@ export class ExcalidrawData {
}
public async loadLegacyData(data: string, file: TFile): Promise<boolean> {
if (!file) {
return false;
}
this.compatibilityMode = true;
this.file = file;
this.textElements = new Map<string, { raw: string; parsed: string }>();
this.textElements = new Map<
string,
{ raw: string; parsed: string; wrapAt: number }
>();
this.setShowLinkBrackets();
this.setLinkPrefix();
this.setUrlPrefix();
@@ -262,6 +359,7 @@ export class ExcalidrawData {
if (!this.scene.files) {
this.scene.files = {}; //loading legacy scenes without the files element
}
this.convert_boundElementIds_to_boundElements();
if (this.plugin.settings.matchThemeAlways) {
this.scene.appState.theme = isObsidianThemeDark() ? "dark" : "light";
}
@@ -281,6 +379,7 @@ export class ExcalidrawData {
public updateTextElement(
sceneTextElement: any,
newText: string,
newOriginalText: string,
forceUpdate: boolean = false,
) {
if (forceUpdate || newText != sceneTextElement.text) {
@@ -290,6 +389,7 @@ export class ExcalidrawData {
sceneTextElement.fontFamily,
);
sceneTextElement.text = newText;
sceneTextElement.originalText = newOriginalText;
sceneTextElement.width = measure.w;
sceneTextElement.height = measure.h;
sceneTextElement.baseline = measure.baseline;
@@ -305,27 +405,41 @@ export class ExcalidrawData {
private async updateSceneTextElements(forceupdate: boolean = false) {
//update text in scene based on textElements Map
//first get scene text elements
const texts = this.scene.elements?.filter((el: any) => el.type == "text");
const texts = this.scene.elements?.filter((el: any) => el.type === "text");
for (const te of texts) {
const originalText =
(await this.getText(te.id, false)) ?? te.originalText ?? te.text;
const wrapAt = this.textElements.get(te.id)?.wrapAt;
this.updateTextElement(
te,
(await this.getText(te.id)) ?? te.text,
wrap(originalText, wrapAt),
originalText,
forceupdate,
); //(await this.getText(te.id))??te.text serves the case when the whole #Text Elements section is deleted by accident
}
}
private async getText(id: string): Promise<string> {
if (this.textMode == TextMode.parsed) {
if (!this.textElements.get(id).parsed) {
const raw = this.textElements.get(id).raw;
this.textElements.set(id, { raw, parsed: await this.parse(raw) });
private async getText(
id: string,
wrapResult: boolean = true,
): Promise<string> {
const t = this.textElements.get(id);
if (!t) {
return null;
}
if (this.textMode === TextMode.parsed) {
if (!t.parsed) {
this.textElements.set(id, {
raw: t.raw,
parsed: await this.parse(t.raw),
wrapAt: t.wrapAt,
});
}
//console.log("parsed",this.textElements.get(id).parsed);
return this.textElements.get(id).parsed;
return wrapResult ? wrap(t.parsed, t.wrapAt) : t.parsed;
}
//console.log("raw",this.textElements.get(id).raw);
return this.textElements.get(id)?.raw;
return t.raw;
}
/**
@@ -353,15 +467,20 @@ export class ExcalidrawData {
}
if (te.id.length > 8 && this.textElements.has(te.id)) {
//element was created with onBeforeTextSubmit
const element = this.textElements.get(te.id);
this.textElements.set(id, { raw: element.raw, parsed: element.parsed });
const t = this.textElements.get(te.id);
this.textElements.set(id, {
raw: t.raw,
parsed: t.parsed,
wrapAt: t.wrapAt,
});
this.textElements.delete(te.id); //delete the old ID from the Map
dirty = true;
} else if (!this.textElements.has(id)) {
dirty = true;
const raw = te.rawText && te.rawText !== "" ? te.rawText : te.text; //this is for compatibility with drawings created before the rawText change on ExcalidrawTextElement
this.textElements.set(id, { raw, parsed: null });
this.parseasync(id, raw);
const wrapAt = estimateMaxLineLen(te.text, te.originalText);
this.textElements.set(id, { raw, parsed: null, wrapAt });
this.parseasync(id, raw, wrapAt);
}
}
if (dirty) {
@@ -380,24 +499,26 @@ export class ExcalidrawData {
for (const key of this.textElements.keys()) {
//find text element in the scene
const el = this.scene.elements?.filter(
(el: any) => el.type == "text" && el.id == key,
(el: any) => el.type === "text" && el.id === key,
);
if (el.length == 0) {
if (el.length === 0) {
this.textElements.delete(key); //if no longer in the scene, delete the text element
} else {
const text = await this.getText(key);
if (text != el[0].text) {
const text = await this.getText(key, false);
if (text !== (el[0].originalText ?? el[0].text)) {
const wrapAt = estimateMaxLineLen(el[0].text, el[0].originalText);
this.textElements.set(key, {
raw: el[0].text,
parsed: await this.parse(el[0].text),
raw: el[0].originalText ?? el[0].text,
parsed: await this.parse(el[0].originalText ?? el[0].text),
wrapAt,
});
}
}
}
}
private async parseasync(key: string, raw: string) {
this.textElements.set(key, { raw, parsed: await this.parse(raw) });
private async parseasync(key: string, raw: string, wrapAt: number) {
this.textElements.set(key, { raw, parsed: await this.parse(raw), wrapAt });
}
private parseLinks(text: string, position: number, parts: any): string {
@@ -562,6 +683,10 @@ export class ExcalidrawData {
return outString + getMarkdownDrawingSection(sceneJSONstring);
}
/**
* deletes fileIds from Excalidraw data for files no longer in the scene
* @returns
*/
private async syncFiles(): Promise<boolean> {
let dirty = false;
const scene = this.scene as SceneDataWithFiles;
@@ -703,38 +828,61 @@ export class ExcalidrawData {
return this.textElements.get(id)?.raw;
}
public getParsedText(id: string): string {
return this.textElements.get(id)?.parsed;
public getParsedText(id: string): [string, string] {
const t = this.textElements.get(id);
if (!t) {
return;
}
return [wrap(t.parsed, t.wrapAt), t.parsed];
}
public setTextElement(
elementID: string,
rawText: string,
rawOriginalText: string,
updateScene: Function,
): string {
const parseResult = this.quickParse(rawText); //will return the parsed result if raw text does not include transclusion
): [string, string] {
const maxLineLen = estimateMaxLineLen(rawText, rawOriginalText);
const parseResult = this.quickParse(rawOriginalText); //will return the parsed result if raw text does not include transclusion
if (parseResult) {
//No transclusion
this.textElements.set(elementID, { raw: rawText, parsed: parseResult });
return parseResult;
this.textElements.set(elementID, {
raw: rawOriginalText,
parsed: parseResult,
wrapAt: maxLineLen,
});
return [wrap(parseResult, maxLineLen), parseResult];
}
//transclusion needs to be resolved asynchornously
this.parse(rawText).then((parsedText: string) => {
this.textElements.set(elementID, { raw: rawText, parsed: parsedText });
this.parse(rawOriginalText).then((parsedText: string) => {
this.textElements.set(elementID, {
raw: rawOriginalText,
parsed: parsedText,
wrapAt: maxLineLen,
});
if (parsedText) {
updateScene(parsedText);
updateScene(wrap(parsedText, maxLineLen), parsedText);
}
});
return null;
return [null, null];
}
public async addTextElement(
elementID: string,
rawText: string,
): Promise<string> {
const parseResult = await this.parse(rawText);
this.textElements.set(elementID, { raw: rawText, parsed: parseResult });
return parseResult;
rawOriginalText: string,
): Promise<[string, string]> {
let wrapAt: number = estimateMaxLineLen(rawText, rawOriginalText);
if (this.textElements.has(elementID)) {
wrapAt = this.textElements.get(elementID).wrapAt;
}
const parseResult = await this.parse(rawOriginalText);
this.textElements.set(elementID, {
raw: rawOriginalText,
parsed: parseResult,
wrapAt,
});
return [wrap(parseResult, wrapAt), parseResult];
}
public deleteTextElement(id: string) {
@@ -804,14 +952,14 @@ export class ExcalidrawData {
return showLinkBrackets != this.showLinkBrackets;
}
/*
// Files and equations copy/paste support
// This is not a complete solution, it assumes the source document is opened first
// at that time the fileId is stored in the master files/equations map
// when pasted the map is checked if the file already exists
// This will not work if pasting from one vault to another, but for the most common usecase
// of copying an image or equation from one drawing to another within the same vault
// this is going to do the job
/**
Files and equations copy/paste support
This is not a complete solution, it assumes the source document is opened first
at that time the fileId is stored in the master files/equations map
when pasted the map is checked if the file already exists
This will not work if pasting from one vault to another, but for the most common usecase
of copying an image or equation from one drawing to another within the same vault
this is going to do the job
*/
public setFile(fileId: FileId, data: EmbeddedFile) {
//always store absolute path because in case of paste, relative path may not resolve ok
@@ -850,6 +998,10 @@ export class ExcalidrawData {
}
if (this.plugin.filesMaster.has(fileId)) {
const fileMaster = this.plugin.filesMaster.get(fileId);
if (!this.app.vault.getAbstractFileByPath(fileMaster.path)) {
this.plugin.filesMaster.delete(fileId);
return true;
} // the file no longer exists
const embeddedFile = new EmbeddedFile(
this.plugin,
this.file.path,
@@ -918,7 +1070,7 @@ export const getTransclusion = async (
if (!linkParts.ref) {
//no blockreference
return charCountLimit
? { contents: contents.substr(0, charCountLimit).trim(), lineNum: 0 }
? { contents: contents.substring(0, charCountLimit).trim(), lineNum: 0 }
: { contents: contents.trim(), lineNum: 0 };
}
//const isParagraphRef = parts.value[2] ? true : false; //does the reference contain a ^ character?
@@ -947,7 +1099,7 @@ export const getTransclusion = async (
const endPos =
para.children[para.children.length - 1]?.position.start.offset - 1; //alternative: filter((c:any)=>c.type=="blockid")[0]
return {
contents: contents.substr(startPos, endPos - startPos).trim(),
contents: contents.substring(startPos, endPos).trim(),
lineNum,
};
}
@@ -961,24 +1113,28 @@ export const getTransclusion = async (
if (startPos && !endPos) {
endPos = headings[i].node.position.start.offset - 1;
return {
contents: contents.substr(startPos, endPos - startPos).trim(),
contents: contents.substring(startPos, endPos).trim(),
lineNum,
};
}
const c = headings[i].node.children[0];
const dataHeading = headings[i].node.data?.hProperties?.dataHeading;
const cc = c?.children;
if (
!startPos &&
(c?.value === linkParts.ref ||
c?.title === linkParts.ref ||
(cc ? cc[0]?.value === linkParts.ref : false))
(c?.value?.replaceAll(REG_BLOCK_REF_CLEAN, "") === linkParts.ref ||
c?.title?.replaceAll(REG_BLOCK_REF_CLEAN, "") === linkParts.ref ||
dataHeading?.replaceAll(REG_BLOCK_REF_CLEAN, "") === linkParts.ref ||
(cc
? cc[0]?.value?.replaceAll(REG_BLOCK_REF_CLEAN, "") === linkParts.ref
: false))
) {
startPos = headings[i].node.children[0]?.position.start.offset; //
lineNum = headings[i].node.children[0]?.position.start.line; //
}
}
if (startPos) {
return { contents: contents.substr(startPos).trim(), lineNum };
return { contents: contents.substring(startPos).trim(), lineNum };
}
return { contents: linkParts.original.trim(), lineNum: 0 };
};

View File

@@ -24,6 +24,7 @@ import {
VIEW_TYPE_EXCALIDRAW,
ICON_NAME,
DISK_ICON_NAME,
SCRIPTENGINE_ICON_NAME,
PNG_ICON_NAME,
SVG_ICON_NAME,
FRONTMATTER_KEY,
@@ -32,6 +33,8 @@ import {
FULLSCREEN_ICON_NAME,
IMAGE_TYPES,
CTRL_OR_CMD,
REG_LINKINDEX_INVALIDCHARS,
KEYCODE,
} from "./constants";
import ExcalidrawPlugin from "./main";
import { repositionElementsToCursor } from "./ExcalidrawAutomate";
@@ -43,6 +46,7 @@ import {
} from "./ExcalidrawData";
import {
checkAndCreateFolder,
checkExcalidrawVersion,
//debug,
download,
embedFontsInSVG,
@@ -66,6 +70,7 @@ import {
EmbeddedFilesLoader,
FileData,
} from "./EmbeddedFileLoader";
import { ScriptInstallPrompt } from "./ScriptInstallPrompt";
export enum TextMode {
parsed,
@@ -81,8 +86,6 @@ export interface ExportSettings {
withTheme: boolean;
}
const REG_LINKINDEX_INVALIDCHARS = /[<>:"\\|?*]/g;
export const addFiles = async (
files: FileData[],
view: ExcalidrawView,
@@ -137,7 +140,7 @@ export default class ExcalidrawView extends TextFileView {
private refresh: Function = null;
public excalidrawRef: React.MutableRefObject<any> = null;
public excalidrawAPI: any = null;
private excalidrawWrapperRef: React.MutableRefObject<any> = null;
public excalidrawWrapperRef: React.MutableRefObject<any> = null;
private justLoaded: boolean = false;
private plugin: ExcalidrawPlugin;
private dirty: string = null;
@@ -153,6 +156,10 @@ export default class ExcalidrawView extends TextFileView {
private shiftKeyDown = false;
private altKeyDown = false;
//https://stackoverflow.com/questions/27132796/is-there-any-javascript-event-fired-when-the-on-screen-keyboard-on-mobile-safari
private isEditingText: boolean = false;
private isEditingTextResetTimer: NodeJS.Timeout = null;
id: string = (this.leaf as any).id;
constructor(leaf: WorkspaceLeaf, plugin: ExcalidrawPlugin) {
@@ -180,7 +187,7 @@ export default class ExcalidrawView extends TextFileView {
}
}
public saveSVG(scene?: any) {
public async saveSVG(scene?: any) {
if (!scene) {
if (!this.getScene) {
return false;
@@ -189,26 +196,24 @@ export default class ExcalidrawView extends TextFileView {
}
const filepath = getIMGFilename(this.file.path, "svg"); //.substring(0,this.file.path.lastIndexOf(this.compatibilityMode ? '.excalidraw':'.md')) + '.svg';
const file = this.app.vault.getAbstractFileByPath(normalizePath(filepath));
(async () => {
const exportSettings: ExportSettings = {
withBackground: this.plugin.settings.exportWithBackground,
withTheme: this.plugin.settings.exportWithTheme,
};
const svg = await getSVG(scene, exportSettings);
if (!svg) {
return;
}
const serializer = new XMLSerializer();
const svgString = serializer.serializeToString(embedFontsInSVG(svg));
if (file && file instanceof TFile) {
await this.app.vault.modify(file, svgString);
} else {
await this.app.vault.create(filepath, svgString);
}
})();
const exportSettings: ExportSettings = {
withBackground: this.plugin.settings.exportWithBackground,
withTheme: this.plugin.settings.exportWithTheme,
};
const svg = await getSVG(scene, exportSettings);
if (!svg) {
return;
}
const serializer = new XMLSerializer();
const svgString = serializer.serializeToString(embedFontsInSVG(svg));
if (file && file instanceof TFile) {
await this.app.vault.modify(file, svgString);
} else {
await this.app.vault.create(filepath, svgString);
}
}
public savePNG(scene?: any) {
public async savePNG(scene?: any) {
if (!scene) {
if (!this.getScene) {
return false;
@@ -219,25 +224,23 @@ export default class ExcalidrawView extends TextFileView {
const filepath = getIMGFilename(this.file.path, "png"); //this.file.path.substring(0,this.file.path.lastIndexOf(this.compatibilityMode ? '.excalidraw':'.md')) + '.png';
const file = this.app.vault.getAbstractFileByPath(normalizePath(filepath));
(async () => {
const exportSettings: ExportSettings = {
withBackground: this.plugin.settings.exportWithBackground,
withTheme: this.plugin.settings.exportWithTheme,
};
const png = await getPNG(
scene,
exportSettings,
this.plugin.settings.pngExportScale,
);
if (!png) {
return;
}
if (file && file instanceof TFile) {
await this.app.vault.modifyBinary(file, await png.arrayBuffer());
} else {
await this.app.vault.createBinary(filepath, await png.arrayBuffer());
}
})();
const exportSettings: ExportSettings = {
withBackground: this.plugin.settings.exportWithBackground,
withTheme: this.plugin.settings.exportWithTheme,
};
const png = await getPNG(
scene,
exportSettings,
this.plugin.settings.pngExportScale,
);
if (!png) {
return;
}
if (file && file instanceof TFile) {
await this.app.vault.modifyBinary(file, await png.arrayBuffer());
} else {
await this.app.vault.createBinary(filepath, await png.arrayBuffer());
}
}
async save(preventReload: boolean = true) {
@@ -261,6 +264,21 @@ export default class ExcalidrawView extends TextFileView {
await this.loadDrawing(false);
}
await super.save();
if (!this.autosaving) {
if (this.plugin.settings.autoexportSVG) {
await this.saveSVG();
}
if (this.plugin.settings.autoexportPNG) {
await this.savePNG();
}
if (
!this.compatibilityMode &&
this.plugin.settings.autoexportExcalidraw
) {
this.saveExcalidraw();
}
}
}
// get the new file content
@@ -284,18 +302,6 @@ export default class ExcalidrawView extends TextFileView {
return this.data;
}
if (!this.autosaving) {
if (this.plugin.settings.autoexportSVG) {
this.saveSVG(scene);
}
if (this.plugin.settings.autoexportPNG) {
this.savePNG(scene);
}
if (this.plugin.settings.autoexportExcalidraw) {
this.saveExcalidraw(scene);
}
}
let header = this.data
.substring(0, trimLocation)
.replace(
@@ -315,19 +321,76 @@ export default class ExcalidrawView extends TextFileView {
return header + this.excalidrawData.generateMD();
}
if (this.compatibilityMode) {
if (!this.autosaving) {
if (this.plugin.settings.autoexportSVG) {
this.saveSVG(scene);
}
if (this.plugin.settings.autoexportPNG) {
this.savePNG(scene);
}
}
return JSON.stringify(scene, null, "\t");
}
return this.data;
}
addFullscreenchangeEvent() {
//excalidrawWrapperRef.current
this.contentEl.onfullscreenchange = () => {
if (this.plugin.settings.zoomToFitOnResize) {
this.zoomToFit();
}
if (!this.isFullscreen()) {
this.clearFullscreenObserver();
this.contentEl.removeAttribute("style");
}
};
}
fullscreenModalObserver: MutationObserver = null;
gotoFullscreen() {
if (!this.excalidrawWrapperRef) {
return;
}
this.contentEl.requestFullscreen(); //{navigationUI: "hide"});
this.excalidrawWrapperRef.current.focus();
this.contentEl.setAttribute("style", "padding:0px;margin:0px;");
this.fullscreenModalObserver = new MutationObserver((m) => {
if (m.length !== 1) {
return;
}
if (!m[0].addedNodes || m[0].addedNodes.length !== 1) {
return;
}
const node: Node = m[0].addedNodes[0];
if (node.nodeType !== Node.ELEMENT_NODE) {
return;
}
const element = node as HTMLElement;
if (!element.classList.contains("modal-container")) {
return;
}
this.contentEl.appendChild(element);
element.querySelector("input").focus();
});
this.fullscreenModalObserver.observe(document.body, {
childList: true,
subtree: false,
});
}
clearFullscreenObserver() {
if (this.fullscreenModalObserver) {
this.fullscreenModalObserver.disconnect();
this.fullscreenModalObserver = null;
}
}
isFullscreen(): boolean {
return (
document.fullscreenEnabled &&
document.fullscreenElement === this.contentEl // excalidrawWrapperRef?.current
); //this.contentEl;
}
exitFullscreen() {
document.exitFullscreen();
}
async handleLinkClick(view: ExcalidrawView, ev: MouseEvent) {
const selectedText = this.getSelectedTextElement();
let file = null;
@@ -336,10 +399,13 @@ export default class ExcalidrawView extends TextFileView {
if (selectedText?.id) {
linkText =
this.textMode == TextMode.parsed
this.textMode === TextMode.parsed
? this.excalidrawData.getRawText(selectedText.id)
: selectedText.text;
if (!linkText) {
return;
}
linkText = linkText.replaceAll("\n", ""); //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/187
if (linkText.match(REG_LINKINDEX_HYPERLINK)) {
window.open(linkText, "_blank");
@@ -363,9 +429,8 @@ export default class ExcalidrawView extends TextFileView {
search[0].view.setQuery(`tag:${tags.value[1]}`);
this.app.workspace.revealLeaf(search[0]);
if (document.fullscreenElement === this.contentEl) {
document.exitFullscreen();
this.zoomToFit();
if (this.isFullscreen()) {
this.exitFullscreen();
}
return;
}
@@ -458,9 +523,8 @@ export default class ExcalidrawView extends TextFileView {
}
try {
if (ev.shiftKey && document.fullscreenElement === this.contentEl) {
document.exitFullscreen();
this.zoomToFit();
if (ev.shiftKey && this.isFullscreen()) {
this.exitFullscreen();
}
const leaf = ev.shiftKey
? getNewOrAdjacentLeaf(this.plugin, view.leaf)
@@ -483,11 +547,22 @@ export default class ExcalidrawView extends TextFileView {
if (!this.excalidrawRef) {
return;
}
if (this.isEditingText) {
return;
}
//final fallback to prevent resizing when text element is in edit mode
//this is to prevent jumping text due to on-screen keyboard popup
if (this.excalidrawAPI?.getAppState()?.editingElement?.type === "text") {
return;
}
this.zoomToFit(false);
}
onload() {
//console.log("ExcalidrawView.onload()");
this.addAction(SCRIPTENGINE_ICON_NAME, t("INSTALL_SCRIPT_BUTTON"), () => {
new ScriptInstallPrompt(this.plugin).open();
});
this.addAction(DISK_ICON_NAME, t("FORCE_SAVE"), async () => {
await this.save(false);
this.plugin.triggerEmbedUpdates();
@@ -513,16 +588,8 @@ export default class ExcalidrawView extends TextFileView {
this.addAction(
FULLSCREEN_ICON_NAME,
"Press ESC to exit fullscreen mode",
() => {
this.contentEl.requestFullscreen(); //{navigationUI: "hide"});
if (this.excalidrawWrapperRef) {
this.excalidrawWrapperRef.current.focus();
}
},
() => this.gotoFullscreen(),
);
this.contentEl.onfullscreenchange = () => {
this.zoomToFit();
};
}
//this is to solve sliding panes bug
@@ -566,7 +633,7 @@ export default class ExcalidrawView extends TextFileView {
public async changeTextMode(textMode: TextMode, reload: boolean = true) {
this.textMode = textMode;
if (textMode == TextMode.parsed) {
if (textMode === TextMode.parsed) {
this.textIsRaw_Element.hide();
this.textIsParsed_Element.show();
} else {
@@ -575,6 +642,7 @@ export default class ExcalidrawView extends TextFileView {
}
if (reload) {
await this.save(false);
this.updateContainerSize();
this.excalidrawAPI.history.clear(); //to avoid undo replacing links with parsed text
}
}
@@ -602,6 +670,10 @@ export default class ExcalidrawView extends TextFileView {
clearInterval(this.autosaveTimer);
this.autosaveTimer = null;
}
if (this.fullscreenModalObserver) {
this.fullscreenModalObserver.disconnect();
this.fullscreenModalObserver = null;
}
}
public async reload(fullreload: boolean = false, file?: TFile) {
@@ -643,12 +715,16 @@ export default class ExcalidrawView extends TextFileView {
this.activeLoader.terminate = true;
}
this.nextLoader = null;
/*ReactDOM.unmountComponentAtode(this.contentEl);
this.excalidrawRef = null;
this.excalidrawAPI = null;*/
this.excalidrawAPI.resetScene();
this.excalidrawAPI.history.clear();
}
private isLoaded: boolean = false;
async setViewData(data: string, clear: boolean = false) {
checkExcalidrawVersion(this.app);
this.isLoaded = false;
if (clear) {
this.clear();
@@ -765,6 +841,7 @@ export default class ExcalidrawView extends TextFileView {
}
//debug({where:"ExcalidrawView.loadDrawing",file:this.file.name,before:"this.loadSceneFiles"});
this.loadSceneFiles();
this.updateContainerSize(null, true);
} else {
this.instantiateExcalidraw({
elements: excalidrawData.elements,
@@ -993,6 +1070,7 @@ export default class ExcalidrawView extends TextFileView {
//console.log({where:"ExcalidrawView.React.ReadyPromise"});
//debug({where:"ExcalidrawView.React.useEffect",file:this.file.name,before:"this.loadSceneFiles"});
this.loadSceneFiles();
this.updateContainerSize(null, true);
});
}, [excalidrawRef]);
@@ -1037,29 +1115,51 @@ export default class ExcalidrawView extends TextFileView {
const selectedElement = this.excalidrawAPI
.getSceneElements()
.filter(
(el: any) =>
el.id ==
(el: ExcalidrawElement) =>
el.id ===
Object.keys(
this.excalidrawAPI.getAppState().selectedElementIds,
)[0],
);
if (selectedElement.length == 0) {
if (selectedElement.length === 0) {
return { id: null, text: null };
}
if (selectedElement[0].type == "text") {
if (selectedElement[0].type === "text") {
return { id: selectedElement[0].id, text: selectedElement[0].text };
} //a text element was selected. Return text
if (selectedElement[0].groupIds.length == 0) {
if (selectedElement[0].type === "image") {
return { id: null, text: null };
}
const boundTextElements = selectedElement[0].boundElements?.filter(
(be: any) => be.type === "text",
);
if (boundTextElements?.length > 0) {
const textElement = this.excalidrawAPI
.getSceneElements()
.filter(
(el: ExcalidrawElement) => el.id === boundTextElements[0].id,
);
if (textElement.length > 0) {
return { id: textElement[0].id, text: textElement[0].text };
}
} //is a text container selected?
if (selectedElement[0].groupIds.length === 0) {
return { id: null, text: null };
} //is the selected element part of a group?
const group = selectedElement[0].groupIds[0]; //if yes, take the first group it is part of
const textElement = this.excalidrawAPI
.getSceneElements()
.filter((el: any) => el.groupIds?.includes(group))
.filter((el: any) => el.type == "text"); //filter for text elements of the group
if (textElement.length == 0) {
.filter((el: any) => el.type === "text"); //filter for text elements of the group
if (textElement.length === 0) {
return { id: null, text: null };
} //the group had no text element member
return { id: selectedElement[0].id, text: selectedElement[0].text }; //return text element text
};
@@ -1093,6 +1193,11 @@ export default class ExcalidrawView extends TextFileView {
fileId: selectedElement[0].fileId,
};
} //an image element was selected. Return fileId
if (selectedElement[0].type === "text") {
return { id: null, fileId: null };
}
if (selectedElement[0].groupIds.length === 0) {
return { id: null, fileId: null };
} //is the selected element part of a group?
@@ -1104,7 +1209,7 @@ export default class ExcalidrawView extends TextFileView {
if (imageElement.length === 0) {
return { id: null, fileId: null };
} //the group had no image element member
return { id: selectedElement[0].id, fileId: selectedElement[0].fileId }; //return image element fileId
return { id: imageElement[0].id, fileId: imageElement[0].fileId }; //return image element fileId
};
this.addText = (text: string, fontFamily?: 1 | 2 | 3) => {
@@ -1137,22 +1242,23 @@ export default class ExcalidrawView extends TextFileView {
const textElements = newElements.filter((el) => el.type == "text");
for (let i = 0; i < textElements.length; i++) {
const parseResult = await this.excalidrawData.addTextElement(
textElements[i].id,
//@ts-ignore
textElements[i].text,
);
const [parseResultWrapped, parseResult] =
await this.excalidrawData.addTextElement(
textElements[i].id,
//@ts-ignore
textElements[i].text,
//@ts-ignore
textElements[i].rawText, //TODO: implement originalText support in ExcalidrawAutomate
);
if (this.textMode == TextMode.parsed) {
this.excalidrawData.updateTextElement(textElements[i], parseResult);
this.excalidrawData.updateTextElement(
textElements[i],
parseResultWrapped,
parseResult,
);
}
}
const newIds = newElements.map((e) => e.id);
const el: ExcalidrawElement[] = this.excalidrawAPI
.getSceneElements()
.filter((e: ExcalidrawElement) => !newIds.includes(e.id));
const st: AppState = this.excalidrawAPI.getAppState();
if (repositionToCursor) {
newElements = repositionElementsToCursor(
newElements,
@@ -1160,8 +1266,26 @@ export default class ExcalidrawView extends TextFileView {
true,
);
}
const newIds = newElements.map((e) => e.id);
const el: ExcalidrawElement[] = this.excalidrawAPI.getSceneElements();
const removeList: string[] = [];
//need to update elements in scene.elements to maintain sequence of layers
for (let i = 0; i < el.length; i++) {
const id = el[i].id;
if (newIds.includes(id)) {
el[i] = newElements.filter((ne) => ne.id === id)[0];
removeList.push(id);
}
}
const st: AppState = this.excalidrawAPI.getAppState();
//debug({where:"ExcalidrawView.addElements",file:this.file.name,dataTheme:this.excalidrawData.scene.appState.theme,before:"updateScene",state:st})
const elements = el.concat(newElements);
const elements = el.concat(
newElements.filter((e) => !removeList.includes(e.id)),
);
this.excalidrawAPI.updateScene({
elements,
appState: st,
@@ -1304,7 +1428,7 @@ export default class ExcalidrawView extends TextFileView {
const elementsWithLinks = elements.filter(
(e: ExcalidrawTextElement) => {
const text: string =
this.textMode == TextMode.parsed
this.textMode === TextMode.parsed
? this.excalidrawData.getRawText(e.id)
: e.text;
if (!text) {
@@ -1424,13 +1548,8 @@ export default class ExcalidrawView extends TextFileView {
if (e.target === excalidrawDiv.ref.current) {
return;
} //event should originate from the canvas
if (
document.fullscreenEnabled &&
document.fullscreenElement == this.contentEl &&
e.keyCode == 27
) {
document.exitFullscreen();
this.zoomToFit();
if (this.isFullscreen() && e.keyCode === KEYCODE.ESC) {
this.exitFullscreen();
}
this.ctrlKeyDown = e[CTRL_OR_CMD]; //.ctrlKey||e.metaKey;
@@ -1461,7 +1580,7 @@ export default class ExcalidrawView extends TextFileView {
.path + ref;
} else {
const text: string =
this.textMode == TextMode.parsed
this.textMode === TextMode.parsed
? this.excalidrawData.getRawText(selectedElement.id)
: selectedElement.text;
@@ -1494,7 +1613,7 @@ export default class ExcalidrawView extends TextFileView {
sourcePath: this.plugin.hover.sourcePath,
});
hoverPoint = currentPosition;
if (document.fullscreenElement === this.contentEl) {
if (this.isFullscreen()) {
const self = this;
setTimeout(() => {
const popover = document.body.querySelector("div.popover");
@@ -1787,6 +1906,9 @@ export default class ExcalidrawView extends TextFileView {
clearInterval(this.autosaveTimer);
this.autosaveTimer = null;
}
clearTimeout(this.isEditingTextResetTimer);
this.isEditingTextResetTimer = null;
this.isEditingText = true; //to prevent autoresize on mobile when keyboard pops up
//if(this.textMode==TextMode.parsed) {
const raw = this.excalidrawData.getRawText(textElement.id);
if (!raw) {
@@ -1799,81 +1921,130 @@ export default class ExcalidrawView extends TextFileView {
onBeforeTextSubmit: (
textElement: ExcalidrawTextElement,
text: string,
originalText: string,
isDeleted: boolean,
) => {
): [string, string] => {
this.isEditingTextResetTimer = setTimeout(() => {
this.isEditingText = false;
this.isEditingTextResetTimer = null;
}, 300); // to give time for the onscreen keyboard to disappear
if (isDeleted) {
this.excalidrawData.deleteTextElement(textElement.id);
this.dirty = this.file?.path;
this.setupAutosaveTimer();
return;
return [null, null];
}
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/318
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/299
if (!this.app.isMobile) {
setTimeout(() => {
this?.excalidrawWrapperRef?.current?.firstElementChild?.focus();
}, 50);
}
const containerId = textElement.containerId;
//If the parsed text is different than the raw text, and if View is in TextMode.parsed
//Then I need to clear the undo history to avoid overwriting raw text with parsed text and losing links
if (
text != textElement.text ||
text !== textElement.text ||
originalText !== textElement.originalText ||
!this.excalidrawData.getRawText(textElement.id)
) {
//the user made changes to the text or the text is missing from Excalidraw Data (recently copy/pasted)
//setTextElement will attempt a quick parse (without processing transclusions)
const parseResult = this.excalidrawData.setTextElement(
textElement.id,
text,
async () => {
await this.save(false);
//this callback function will only be invoked if quick parse fails, i.e. there is a transclusion in the raw text
//thus I only check if TextMode.parsed, text is always != with parseResult
if (this.textMode == TextMode.parsed) {
this.excalidrawAPI.history.clear();
}
this.setupAutosaveTimer();
},
);
if (parseResult) {
const [parseResultWrapped, parseResultOriginal] =
this.excalidrawData.setTextElement(
textElement.id,
text,
originalText,
async () => {
await this.save(false);
//this.updateContainerSize(4,textElement.id,true); //not required, because save preventReload==false, it will reload and update container sizes
//this callback function will only be invoked if quick parse fails, i.e. there is a transclusion in the raw text
//thus I only check if TextMode.parsed, text is always != with parseResult
if (this.textMode === TextMode.parsed) {
this.excalidrawAPI.history.clear();
}
this.setupAutosaveTimer();
},
);
if (parseResultWrapped) {
if (containerId) {
this.updateContainerSize(containerId, true);
}
//there were no transclusions in the raw text, quick parse was successful
this.setupAutosaveTimer();
if (this.textMode == TextMode.raw) {
return;
if (this.textMode === TextMode.raw) {
return [null, null];
} //text is displayed in raw, no need to clear the history, undo will not create problems
if (text == parseResult) {
return;
if (text === parseResultWrapped) {
return [null, null];
} //There were no links to parse, raw text and parsed text are equivalent
this.excalidrawAPI.history.clear();
return parseResult;
return [parseResultWrapped, parseResultOriginal];
}
return;
return [null, null];
}
this.setupAutosaveTimer();
if (this.textMode == TextMode.parsed) {
if (containerId) {
this.updateContainerSize(containerId, true);
}
if (this.textMode === TextMode.parsed) {
return this.excalidrawData.getParsedText(textElement.id);
}
return [null, null];
},
}),
);
return React.createElement(React.Fragment, null, excalidrawDiv);
});
ReactDOM.render(reactElement, this.contentEl, () => {
this.excalidrawWrapperRef.current.focus();
this.addFullscreenchangeEvent();
});
}
private updateContainerSize(containerId?: string, delay: boolean = false) {
const api = this.excalidrawAPI;
const update = () => {
const containers = containerId
? api
.getSceneElements()
.filter((el: ExcalidrawElement) => el.id === containerId)
: api
.getSceneElements()
.filter((el: ExcalidrawElement) =>
el.boundElements?.map((e) => e.type).includes("text"),
);
api.updateContainerSize(containers);
};
if (delay) {
setTimeout(() => update(), 50);
} else {
update();
}
}
public zoomToFit(delay: boolean = true) {
if (!this.excalidrawRef) {
return;
}
const maxZoom = this.plugin.settings.zoomToFitMaxLevel;
const current = this.excalidrawAPI;
const fullscreen = document.fullscreenElement == this.contentEl;
const elements = current.getSceneElements();
if (delay) {
//time for the DOM to render, I am sure there is a more elegant solution
setTimeout(
() => current.zoomToFit(elements, maxZoom, fullscreen ? 0 : 0.05),
() =>
current.zoomToFit(elements, maxZoom, this.isFullscreen() ? 0 : 0.05),
100,
);
} else {
current.zoomToFit(elements, maxZoom, fullscreen ? 0 : 0.05);
current.zoomToFit(elements, maxZoom, this.isFullscreen() ? 0 : 0.05);
}
}
}

View File

@@ -20,21 +20,25 @@ export class InsertLinkDialog extends FuzzySuggestModal<TFile> {
this.emptyStateText = t("NO_MATCH");
}
getItems(): TFile[] {
return this.app.vault.getFiles();
getItems(): any[] {
//@ts-ignore
return this.app.metadataCache.getLinkSuggestions();
}
getItemText(item: TFile): string {
return item.path;
getItemText(item: any): string {
return item.path + (item.alias ? `|${item.alias}` : "");
}
onChooseItem(item: TFile): void {
const filepath = this.app.metadataCache.fileToLinktext(
item,
this.drawingPath,
true,
);
this.addText(`[[${filepath}]]`);
onChooseItem(item: any): void {
let filepath = item.path;
if (item.file) {
filepath = this.app.metadataCache.fileToLinktext(
item.file,
this.drawingPath,
true,
);
}
this.addText(`[[${filepath + (item.alias ? `|${item.alias}` : "")}]]`);
}
public start(drawingPath: string, addText: Function) {

View File

@@ -0,0 +1,481 @@
import { settings } from "cluster";
import { MarkdownPostProcessorContext, MetadataCache, TFile, Vault } from "obsidian";
import { CTRL_OR_CMD, RERENDER_EVENT } from "./constants";
import { EmbeddedFilesLoader } from "./EmbeddedFileLoader";
import { createPNG, createSVG } from "./ExcalidrawAutomate";
import { ExportSettings } from "./ExcalidrawView";
import ExcalidrawPlugin from "./main";
import { embedFontsInSVG, getIMGFilename, isObsidianThemeDark, splitFolderAndFilename, svgToBase64 } from "./Utils";
interface imgElementAttributes {
file?: TFile;
fname: string; //Excalidraw filename
fwidth: string; //Display width of image
fheight: string; //Display height of image
style: string; //css style to apply to IMG element
}
let plugin: ExcalidrawPlugin;
let vault: Vault;
let metadataCache: MetadataCache;
export const initializeMarkdownPostProcessor = (p:ExcalidrawPlugin) => {
plugin = p;
vault = p.app.vault;
metadataCache = p.app.metadataCache;
}
/**
* Generates an img element with the drawing encoded as a base64 SVG or a PNG (depending on settings)
* @param parts {imgElementAttributes} - display properties of the image
* @returns {Promise<HTMLElement>} - the IMG HTML element containing the image
*/
const getIMG = async (
imgAttributes: imgElementAttributes,
): Promise<HTMLElement> => {
let file = imgAttributes.file;
if (!imgAttributes.file) {
const f = vault.getAbstractFileByPath(imgAttributes.fname);
if (!(f && f instanceof TFile)) {
return null;
}
file = f;
}
const exportSettings: ExportSettings = {
withBackground: plugin.settings.exportWithBackground,
withTheme: plugin.settings.exportWithTheme,
};
const img = createEl("img");
let style = `max-width:${imgAttributes.fwidth}px !important; width:100%;`;
if (imgAttributes.fheight) {
style += `height:${imgAttributes.fheight}px;`;
}
img.setAttribute("style", style);
img.addClass(imgAttributes.style);
const theme = plugin.settings.previewMatchObsidianTheme
? isObsidianThemeDark()
? "dark"
: "light"
: !plugin.settings.exportWithTheme
? "light"
: undefined;
if (theme) {
exportSettings.withTheme = true;
}
const loader = new EmbeddedFilesLoader(
plugin,
theme ? theme === "dark" : undefined,
);
if (!plugin.settings.displaySVGInPreview) {
const width = parseInt(imgAttributes.fwidth);
let scale = 1;
if (width >= 600) {
scale = 2;
}
if (width >= 1200) {
scale = 3;
}
if (width >= 1800) {
scale = 4;
}
if (width >= 2400) {
scale = 5;
}
const png = await createPNG(
file.path,
scale,
exportSettings,
loader,
theme,
null,
null,
[],
plugin,
);
//const png = await getPNG(JSON_parse(scene),exportSettings, scale);
if (!png) {
return null;
}
img.src = URL.createObjectURL(png);
return img;
}
const svgSnapshot = (
await createSVG(
file.path,
true,
exportSettings,
loader,
theme,
null,
null,
[],
plugin,
)
).outerHTML;
let svg: SVGSVGElement = null;
const el = document.createElement("div");
el.innerHTML = svgSnapshot;
const firstChild = el.firstChild;
if (firstChild instanceof SVGSVGElement) {
svg = firstChild;
}
if (!svg) {
return null;
}
svg = embedFontsInSVG(svg);
svg.removeAttribute("width");
svg.removeAttribute("height");
img.setAttribute("src", svgToBase64(svg.outerHTML));
return img;
};
const createImageDiv = async (
attr: imgElementAttributes,
): Promise<HTMLDivElement> => {
const img = await getIMG(attr);
return createDiv(attr.style, (el) => {
el.append(img);
el.setAttribute("src", attr.file.path);
if (attr.fwidth) {
el.setAttribute("w", attr.fwidth);
}
if (attr.fheight) {
el.setAttribute("h", attr.fheight);
}
el.onClickEvent((ev) => {
if (
ev.target instanceof Element &&
ev.target.tagName.toLowerCase() != "img"
) {
return;
}
const src = el.getAttribute("src");
if (src) {
plugin.openDrawing(
vault.getAbstractFileByPath(src) as TFile,
ev[CTRL_OR_CMD],
);
} //.ctrlKey||ev.metaKey);
});
el.addEventListener(RERENDER_EVENT, async (e) => {
e.stopPropagation();
el.empty();
const img = await getIMG({
fname: el.getAttribute("src"),
fwidth: el.getAttribute("w"),
fheight: el.getAttribute("h"),
style: el.getAttribute("class"),
});
el.append(img);
});
});
};
const processInternalEmbeds = async (
embeddedItems:NodeListOf<Element>|[HTMLElement],
ctx: MarkdownPostProcessorContext,
) => {
//if not, then we are processing a non-excalidraw file in reading mode
//in that cases embedded files will be displayed in an .internal-embed container
const attr: imgElementAttributes = {
fname: "",
fheight: "",
fwidth: "",
style: "",
};
let alt: string;
let parts;
let file: TFile;
//Iterating through all the containers to check which one is an excalidraw drawing
//This is a for loop instead of embeddedItems.forEach() because createImageDiv at the end
//is awaited, otherwise excalidraw images would not display in the Kanban plugin
for (const maybeDrawing of embeddedItems) {
//check to see if the file in the src attribute exists
attr.fname = maybeDrawing.getAttribute("src");
file = metadataCache.getFirstLinkpathDest(
attr.fname?.split("#")[0],
ctx.sourcePath,
);
//if the embeddedFile exits and it is an Excalidraw file
//then lets replace the .internal-embed with the generated PNG or SVG image
if (file && file instanceof TFile && plugin.isExcalidrawFile(file)) {
attr.fwidth = maybeDrawing.getAttribute("width")
? maybeDrawing.getAttribute("width")
: plugin.settings.width;
attr.fheight = maybeDrawing.getAttribute("height");
alt = maybeDrawing.getAttribute("alt");
if (alt == attr.fname) {
alt = "";
} //when the filename starts with numbers followed by a space Obsidian recognizes the filename as alt-text
attr.style = "excalidraw-svg";
if (alt) {
//for some reason Obsidian renders ![]() in a DIV and ![[]] in a SPAN
//also the alt-text of the DIV does not include the alt-text of the image
//thus need to add an additional "|" character when its a SPAN
if (maybeDrawing.tagName.toLowerCase() == "span") {
alt = `|${alt}`;
}
//1:width, 2:height, 3:style 1 2 3
parts = alt.match(/[^\|]*\|?(\d*%?)x?(\d*%?)\|?(.*)/);
attr.fwidth = parts[1] ? parts[1] : plugin.settings.width;
attr.fheight = parts[2];
if (parts[3] != attr.fname) {
attr.style = `excalidraw-svg${parts[3] ? `-${parts[3]}` : ""}`;
}
}
attr.fname = file?.path;
attr.file = file;
const div = await createImageDiv(attr);
maybeDrawing.parentElement.replaceChild(div, maybeDrawing);
}
}
}
const tmpObsidianWYSIWYG = async (
el: HTMLElement,
ctx: MarkdownPostProcessorContext,
) => {
if (!ctx.frontmatter) {
return;
}
if (!ctx.frontmatter.hasOwnProperty("excalidraw-plugin")) {
return;
}
//@ts-ignore
if (ctx.remainingNestLevel < 4) {
return;
}
if (!el.querySelector(".frontmatter")) {
el.style.display = "none";
return;
}
const attr: imgElementAttributes = {
fname: ctx.sourcePath,
fheight: "",
fwidth: plugin.settings.width,
style: "excalidraw-svg",
};
attr.file = metadataCache.getFirstLinkpathDest(
ctx.sourcePath,
"",
);
el.empty();
if(!plugin.settings.experimentalLivePreview) {
el.appendChild(await createImageDiv(attr));
return;
}
const div = createDiv();
el.appendChild(div);
//The timeout gives time for obsidian to attach el to the displayed document
//Once the element is attached, I can traverse up the dom tree to find .internal-embed
//If internal embed is not found, it means the that the excalidraw.md file
//is being rendered in "reading" mode. In that case, the image with the default width
//specified in setting should be displayed
//if .internal-embed is found, then contents is replaced with the image using the
//alt, width, and height attributes of .internal-embed to size and style the image
setTimeout(async ()=>{
let internalEmbedDiv:HTMLElement = div;
while(!internalEmbedDiv.hasClass("internal-embed") && internalEmbedDiv.parentElement) {
internalEmbedDiv = internalEmbedDiv.parentElement;
}
if(!internalEmbedDiv.hasClass("internal-embed")) {
el.empty();
el.appendChild(await createImageDiv(attr));
return;
}
internalEmbedDiv.empty();
const basename = splitFolderAndFilename(attr.fname).basename;
const setAttr = () => {
const hasWidth = internalEmbedDiv.getAttribute("width")!=="";
const hasHeight = internalEmbedDiv.getAttribute("height")!=="";
if(hasWidth)
attr.fwidth = internalEmbedDiv.getAttribute("width");
if(hasHeight)
attr.fheight = internalEmbedDiv.getAttribute("height");
const alt = internalEmbedDiv.getAttribute("alt");
const hasAttr = alt && alt!=="" && alt!==basename;
if(hasAttr) {
//1:width, 2:height, 3:style 1 2 3
const parts = alt.match(/(\d*%?)x?(\d*%?)\|?(.*)/);
attr.fwidth = parts[1] ? parts[1] : plugin.settings.width;
attr.fheight = parts[2];
if (parts[3] != attr.fname) {
attr.style = `excalidraw-svg${parts[3] ? `-${parts[3]}` : ""}`;
}
}
if(!hasWidth && !hasHeight && !hasAttr) {
attr.fheight = "";
attr.fwidth = plugin.settings.width;
attr.style = "excalidraw-svg";
}
}
const createImgElement = async () => {
setAttr();
const imgDiv = await createImageDiv(attr);
internalEmbedDiv.appendChild(imgDiv);
}
await createImgElement();
//timer to avoid the image flickering when the user is typing
let timer:NodeJS.Timeout = null;
const observer = new MutationObserver((m) => {
if(!["alt","width","height"].contains(m[0]?.attributeName)) {
return;
}
if(timer) clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
setAttr();
internalEmbedDiv.empty();
createImgElement();
},500);
});
observer.observe(internalEmbedDiv, {
attributes: true, //configure it to listen to attribute changes
});
},300);
};
/**
*
* @param el
* @param ctx
*/
export const markdownPostProcessor = async (
el: HTMLElement,
ctx: MarkdownPostProcessorContext,
) => {
//check to see if we are rendering in editing mode of live preview
//if yes, then there should be no .internal-embed containers
const embeddedItems = el.querySelectorAll(".internal-embed");
if (embeddedItems.length === 0) {
tmpObsidianWYSIWYG(el, ctx);
return;
}
//If the file being processed is an excalidraw file,
//then I want to hide all embedded items as these will be
//transcluded text element or some other transcluded content inside the Excalidraw file
//in reading mode these elements should be hidden
if (ctx.frontmatter?.hasOwnProperty("excalidraw-plugin")) {
el.style.display = "none";
return;
}
await processInternalEmbeds( embeddedItems,ctx);
};
/**
* internal-link quick preview
* @param e
* @returns
*/
export const hoverEvent = (e: any) => {
if (!e.linktext) {
plugin.hover.linkText = null;
return;
}
plugin.hover.linkText = e.linktext;
plugin.hover.sourcePath = e.sourcePath;
};
//monitoring for div.popover.hover-popover.file-embed.is-loaded to be added to the DOM tree
export const observer = new MutationObserver(async (m) => {
if (m.length == 0) {
return;
}
if (!plugin.hover.linkText) {
return;
}
const file = metadataCache.getFirstLinkpathDest(
plugin.hover.linkText,
plugin.hover.sourcePath ? plugin.hover.sourcePath : "",
);
if (!file) {
return;
}
if (!(file instanceof TFile)) {
return;
}
if (file.extension !== "excalidraw") {
return;
}
const svgFileName = getIMGFilename(file.path, "svg");
const svgFile = vault.getAbstractFileByPath(svgFileName);
if (svgFile && svgFile instanceof TFile) {
return;
} //If auto export SVG or PNG is enabled it will be inserted at the top of the excalidraw file. No need to manually insert hover preview
const pngFileName = getIMGFilename(file.path, "png");
const pngFile = vault.getAbstractFileByPath(pngFileName);
if (pngFile && pngFile instanceof TFile) {
return;
} //If auto export SVG or PNG is enabled it will be inserted at the top of the excalidraw file. No need to manually insert hover preview
if (!plugin.hover.linkText) {
return;
}
if (m.length != 1) {
return;
}
if (m[0].addedNodes.length != 1) {
return;
}
if (
//@ts-ignore
m[0].addedNodes[0].className !=
"popover hover-popover file-embed is-loaded"
) {
return;
}
const node = m[0].addedNodes[0];
node.empty();
//this div will be on top of original DIV. By stopping the propagation of the click
//I prevent the default Obsidian feature of openning the link in the native app
const img = await getIMG({
file,
fname: file.path,
fwidth: "300",
fheight: null,
style: "excalidraw-svg",
});
const div = createDiv("", async (el) => {
el.appendChild(img);
el.setAttribute("src", file.path);
el.onClickEvent((ev) => {
ev.stopImmediatePropagation();
const src = el.getAttribute("src");
if (src) {
plugin.openDrawing(
vault.getAbstractFileByPath(src) as TFile,
ev[CTRL_OR_CMD],
);
} //.ctrlKey||ev.metaKey);
});
});
node.appendChild(div);
});

View File

@@ -1,4 +1,11 @@
import { App, ButtonComponent, Modal, TextComponent } from "obsidian";
import {
App,
ButtonComponent,
Modal,
TextComponent,
FuzzyMatch,
FuzzySuggestModal,
} from "obsidian";
export class Prompt extends Modal {
private promptEl: HTMLInputElement;
@@ -55,7 +62,7 @@ export class Prompt extends Modal {
}
}
export default class GenericInputPrompt extends Modal {
export class GenericInputPrompt extends Modal {
public waitForClose: Promise<string>;
private resolvePromise: (input: string) => void;
@@ -203,3 +210,56 @@ export default class GenericInputPrompt extends Modal {
this.removeInputListener();
}
}
export class GenericSuggester extends FuzzySuggestModal<string> {
private resolvePromise: (value: string) => void;
private rejectPromise: (reason?: any) => void;
public promise: Promise<string>;
private resolved: boolean;
public static Suggest(app: App, displayItems: string[], items: string[]) {
const newSuggester = new GenericSuggester(app, displayItems, items);
return newSuggester.promise;
}
public constructor(
app: App,
private displayItems: string[],
private items: string[],
) {
super(app);
this.promise = new Promise<string>((resolve, reject) => {
this.resolvePromise = resolve;
this.rejectPromise = reject;
});
this.open();
}
getItemText(item: string): string {
return this.displayItems[this.items.indexOf(item)];
}
getItems(): string[] {
return this.items;
}
selectSuggestion(value: FuzzyMatch<string>, evt: MouseEvent | KeyboardEvent) {
this.resolved = true;
super.selectSuggestion(value, evt);
}
onChooseItem(item: string): void {
this.resolved = true;
this.resolvePromise(item);
}
onClose() {
super.onClose();
if (!this.resolved) {
this.rejectPromise("no input given.");
}
}
}

View File

@@ -0,0 +1,43 @@
import { App, MarkdownRenderer, Modal, Notice, request } from "obsidian";
import { Url } from "url";
import { t } from "./lang/helpers";
import ExcalidrawPlugin from "./main";
import { errorlog } from "./Utils";
const URL =
"https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/index.md";
export class ScriptInstallPrompt extends Modal {
constructor(private plugin: ExcalidrawPlugin) {
super(plugin.app);
// this.titleEl.setText(t("INSTAL_MODAL_TITLE"));
}
async onOpen(): Promise<void> {
this.contentEl.classList.add("excalidraw-scriptengine-install");
this.containerEl.classList.add("excalidraw-scriptengine-install");
try {
const source = await request({ url: URL });
await MarkdownRenderer.renderMarkdown(
source,
this.contentEl,
"",
this.plugin,
);
this.contentEl.querySelectorAll("h1[data-heading],h2[data-heading],h3[data-heading]").forEach((el) => {
el.setAttribute("id", el.getAttribute("data-heading"));
});
this.contentEl.querySelectorAll("a.internal-link").forEach((el) => {
el.removeAttribute("target");
});
} catch (e) {
errorlog({ where: "ScriptInstallPrompt.onOpen", error: e });
new Notice("Could not open ScriptEngine repository");
this.close();
}
}
onClose(): void {
this.contentEl.empty();
}
}

View File

@@ -1,8 +1,9 @@
import { sub } from "@zsviczian/excalidraw/types/ga";
import { App, TAbstractFile, TFile } from "obsidian";
import { VIEW_TYPE_EXCALIDRAW } from "./constants";
import ExcalidrawView from "./ExcalidrawView";
import ExcalidrawPlugin from "./main";
import GenericInputPrompt from "./Prompt";
import { GenericInputPrompt, GenericSuggester } from "./Prompt";
import { splitFolderAndFilename } from "./Utils";
export class ScriptEngine {
@@ -23,7 +24,7 @@ export class ScriptEngine {
if (!file.path.startsWith(this.scriptPath)) {
return;
}
this.unloadScript(file.basename);
this.unloadScript(this.getScriptName(file));
};
this.plugin.registerEvent(
this.plugin.app.vault.on("delete", deleteEventHandler),
@@ -49,7 +50,7 @@ export class ScriptEngine {
const oldFileIsScript = oldPath.startsWith(this.scriptPath);
const newFileIsScript = file.path.startsWith(this.scriptPath);
if (oldFileIsScript) {
this.unloadScript(splitFolderAndFilename(oldPath).basename);
this.unloadScript(this.getScriptName(oldPath));
}
if (newFileIsScript) {
this.loadScript(file);
@@ -77,10 +78,32 @@ export class ScriptEngine {
scripts.forEach((f) => this.loadScript(f));
}
getScriptName(f:TFile|string):string {
let basename = "";
let path = "";
if(f instanceof TFile) {
basename = f.basename;
path = f.path;
} else {
basename = splitFolderAndFilename(f).basename;
path = f;
}
const subpath = path.split(
`${this.plugin.settings.scriptFolderPath}/`,
)[1];
const lastSlash = subpath.lastIndexOf("/");
if (lastSlash > -1) {
return subpath.substring(0, lastSlash + 1) + basename;
}
return basename;
}
loadScript(f: TFile) {
const scriptName = this.getScriptName(f);
this.plugin.addCommand({
id: f.basename,
name: `(Script) ${f.basename}`,
id: scriptName,
name: `(Script) ${scriptName}`,
checkCallback: (checking: boolean) => {
if (checking) {
return (
@@ -103,7 +126,9 @@ export class ScriptEngine {
const scripts = app.vault
.getFiles()
.filter((f: TFile) => f.path.startsWith(this.scriptPath));
scripts.forEach((f) => this.unloadScript(f.basename));
scripts.forEach((f) => {
this.unloadScript(this.getScriptName(f));
});
}
unloadScript(basename: string) {
@@ -128,14 +153,19 @@ export class ScriptEngine {
return;
}
this.plugin.ea.activeScript = this.getScriptName(f);
//https://stackoverflow.com/questions/45381204/get-asyncfunction-constructor-in-typescript changed tsconfig to es2017
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction
const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor;
return await new AsyncFunction("ea", "utils", script)(this.plugin.ea, {
const result = await new AsyncFunction("ea", "utils", script)(this.plugin.ea, {
inputPrompt: (header: string, placeholder?: string, value?: string) =>
ScriptEngine.inputPrompt(this.plugin.app, header, placeholder, value),
suggester: (displayItems: string[], items: string[]) =>
ScriptEngine.suggester(this.plugin.app, displayItems, items),
});
this.plugin.ea.activeScript = null;
return result;
}
public static async inputPrompt(
@@ -150,4 +180,16 @@ export class ScriptEngine {
return undefined;
}
}
public static async suggester(
app: App,
displayItems: string[],
items: string[],
) {
try {
return await GenericSuggester.Suggest(app, displayItems, items);
} catch {
return undefined;
}
}
}

View File

@@ -2,6 +2,7 @@ import { exportToSvg, exportToBlob } from "@zsviczian/excalidraw";
import {
App,
normalizePath,
Notice,
TAbstractFile,
TFolder,
Vault,
@@ -9,7 +10,7 @@ import {
} from "obsidian";
import { Random } from "roughjs/bin/math";
import { Zoom } from "@zsviczian/excalidraw/types/types";
import { CASCADIA_FONT, VIRGIL_FONT } from "./constants";
import { CASCADIA_FONT, REG_BLOCK_REF_CLEAN, VIRGIL_FONT } from "./constants";
import ExcalidrawPlugin from "./main";
import { ExcalidrawElement } from "@zsviczian/excalidraw/types/element/types";
import { ExportSettings } from "./ExcalidrawView";
@@ -26,6 +27,21 @@ declare module "obsidian" {
}
}
let versionUpdateChecked = false;
export const checkExcalidrawVersion = async (app:App) => {
if(versionUpdateChecked) return;
versionUpdateChecked = true;
//@ts-ignore
const manifest = app.plugins.manifests["obsidian-excalidraw-plugin"];
//@ts-ignore
const latestVersion = await app.plugins.getLatestVersion("obsidian-excalidraw-plugin",manifest)
if(latestVersion>manifest.version) {
new Notice(`A newer version of Excalidraw is available in Community Plugins. You are using ${manifest.version}. The latest version is ${latestVersion}`);
}
setTimeout(()=>versionUpdateChecked=false,28800000);//reset after 8 hours
}
/**
* Splits a full path including a folderpath and a filename into separate folderpath and filename components
* @param filepath
@@ -126,6 +142,7 @@ export function wrapText(
text: string,
lineLen: number,
forceWrap: boolean = false,
tolerance: number = 0,
): string {
if (!lineLen) {
return text;
@@ -139,9 +156,12 @@ export function wrapText(
return outstring.replace(/\n$/, "");
}
// 1 2 3 4
// 1 2 3 4
const reg = new RegExp(
`(.{1,${lineLen}})(\\s+|$\\n?)|([^\\s]+)(\\s+|$\\n?)`,
`(.{1,${lineLen}})(\\s+|$\\n?)|([^\\s]{1,${
lineLen + tolerance
}})(\\s+|$\\n?)?`,
//`(.{1,${lineLen}})(\\s+|$\\n?)|([^\\s]+)(\\s+|$\\n?)`,
"gm",
);
const res = text.matchAll(reg);
@@ -150,15 +170,11 @@ export function wrapText(
outstring += parts.value[1]
? parts.value[1].trimEnd()
: parts.value[3].trimEnd();
const newLine1 = parts.value[2]?.includes("\n");
const newLine2 = parts.value[4]?.includes("\n");
if (newLine1) {
outstring += parts.value[2];
}
if (newLine2) {
outstring += parts.value[4];
}
if (!(newLine1 || newLine2)) {
const newLine =
(parts.value[2] ? parts.value[2].split("\n").length - 1 : 0) +
(parts.value[4] ? parts.value[4].split("\n").length - 1 : 0);
outstring += "\n".repeat(newLine);
if (newLine === 0) {
outstring += "\n";
}
}
@@ -442,7 +458,7 @@ export const getLinkParts = (fname: string): LinkParts => {
original: fname,
path: parts[1],
isBlockRef: parts[2] === "^",
ref: parts[3],
ref: parts[3]?.replaceAll(REG_BLOCK_REF_CLEAN, ""),
width: parts[4] ? parseInt(parts[4]) : undefined,
height: parts[5] ? parseInt(parts[5]) : undefined,
};

View File

@@ -10,7 +10,15 @@ export const nanoid = customAlphabet(
"1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
8,
);
export const KEYCODE = {
ESC: 27,
};
export const SCRIPT_INSTALL_CODEBLOCK = "excalidraw-script-install";
export const SCRIPT_INSTALL_FOLDER = "Downloaded";
export const fileid = customAlphabet("1234567890abcdef", 40);
export const REG_LINKINDEX_INVALIDCHARS = /[<>:"\\|?*]/g;
export const REG_BLOCK_REF_CLEAN =
/\+|\/|~|=|%|\(|\)|{|}|,|\.|\$|!|\?|;|\[|]|\^|#|\*|<|>|&|@|\||\\|"|:/g;
export const IMAGE_TYPES = ["jpeg", "jpg", "png", "gif", "svg"];
export const MAX_IMAGE_SIZE = 500;
export const FRONTMATTER_KEY = "excalidraw-plugin";
@@ -45,6 +53,8 @@ export const TEXT_DISPLAY_PARSED_ICON_NAME = "quote-glyph";
export const TEXT_DISPLAY_RAW_ICON_NAME = "presentation";
export const FULLSCREEN_ICON_NAME = "fullscreen";
export const EXIT_FULLSCREEN_ICON_NAME = "exit-fullscreen";
export const SCRIPTENGINE_ICON_NAME = "ScriptEngine";
export const SCRIPTENGINE_ICON = `<g transform="translate(-8,-8)"><path d="M24.318 37.983c-1.234-1.232-8.433-3.903-7.401-7.387 1.057-3.484 9.893-12.443 13.669-13.517 3.776-1.074 6.142 6.523 9.012 7.073 2.87.55 6.797-1.572 8.207-3.694 1.384-2.148-3.147-7.413.15-9.168 3.298-1.755 16.389-2.646 19.611-1.284 3.247 1.363-1.611 7.335-.151 9.483 1.46 2.148 6.067 3.746 8.836 3.38 2.769-.368 4.154-6.733 7.728-5.633 3.575 1.1 12.36 8.828 13.67 12.233 1.308 3.406-5.186 5.423-5.79 8.2-.58 2.75-.026 6.705 2.265 8.355 2.266 1.65 9.642-1.78 11.404 1.598 1.762 3.38 1.007 15.35-.806 18.651-1.787 3.353-7.753-.367-9.969 1.31-2.215 1.65-3.901 5.92-3.373 8.67.504 2.777 7.754 4.48 6.445 7.885C96.49 87.543 87.15 95.454 83.5 96.685c-3.65 1.231-4.96-4.741-7.577-5.16-2.593-.393-6.57.707-8.03 2.75-1.436 2.017 2.668 7.806-.63 9.483-3.323 1.676-15.759 2.226-19.157.655-3.373-1.598.554-7.964-1.108-10.138-1.687-2.174-6.394-3.431-9.012-2.907-2.643.55-3.273 7.282-6.747 6.103-3.499-1.126-12.788-9.535-14.172-13.019-1.36-3.484 5.437-5.108 5.966-7.858.529-2.777-.68-7.073-2.744-8.697-2.064-1.624-7.93 2.41-9.642-1.126-1.737-3.537-2.441-16.765-.654-20.118 1.787-3.3 9.062 1.598 11.429.183 2.366-1.44 2.316-7.282 2.769-8.749m.126-.104c-1.234-1.232-8.433-3.903-7.401-7.387 1.057-3.484 9.893-12.443 13.669-13.517 3.776-1.074 6.142 6.523 9.012 7.073 2.87.55 6.797-1.572 8.207-3.694 1.384-2.148-3.147-7.413.15-9.168 3.298-1.755 16.389-2.646 19.611-1.284 3.247 1.363-1.611 7.335-.151 9.483 1.46 2.148 6.067 3.746 8.836 3.38 2.769-.368 4.154-6.733 7.728-5.633 3.575 1.1 12.36 8.828 13.67 12.233 1.308 3.406-5.186 5.423-5.79 8.2-.58 2.75-.026 6.705 2.265 8.355 2.266 1.65 9.642-1.78 11.404 1.598 1.762 3.38 1.007 15.35-.806 18.651-1.787 3.353-7.753-.367-9.969 1.31-2.215 1.65-3.901 5.92-3.373 8.67.504 2.777 7.754 4.48 6.445 7.885C96.49 87.543 87.15 95.454 83.5 96.685c-3.65 1.231-4.96-4.741-7.577-5.16-2.593-.393-6.57.707-8.03 2.75-1.436 2.017 2.668 7.806-.63 9.483-3.323 1.676-15.759 2.226-19.157.655-3.373-1.598.554-7.964-1.108-10.138-1.687-2.174-6.394-3.431-9.012-2.907-2.643.55-3.273 7.282-6.747 6.103-3.499-1.126-12.788-9.535-14.172-13.019-1.36-3.484 5.437-5.108 5.966-7.858.529-2.777-.68-7.073-2.744-8.697-2.064-1.624-7.93 2.41-9.642-1.126-1.737-3.537-2.441-16.765-.654-20.118 1.787-3.3 9.062 1.598 11.429.183 2.366-1.44 2.316-7.282 2.769-8.749" fill="none" stroke-width="2" stroke-linecap="round" stroke="currentColor"/><path d="M81.235 56.502a23.3 23.3 0 0 1-1.46 8.068 20.785 20.785 0 0 1-1.762 3.72 24.068 24.068 0 0 1-5.337 6.26 22.575 22.575 0 0 1-3.449 2.358 23.726 23.726 0 0 1-7.803 2.803 24.719 24.719 0 0 1-8.333 0 24.102 24.102 0 0 1-4.028-1.074 23.71 23.71 0 0 1-3.776-1.729 23.259 23.259 0 0 1-6.369-5.265 23.775 23.775 0 0 1-2.416-3.353 24.935 24.935 0 0 1-1.762-3.72 23.765 23.765 0 0 1-1.083-3.981 23.454 23.454 0 0 1 0-8.173c.252-1.336.604-2.698 1.083-3.956a24.935 24.935 0 0 1 1.762-3.72 22.587 22.587 0 0 1 2.416-3.378c.881-1.048 1.888-2.017 2.946-2.908a24.38 24.38 0 0 1 3.423-2.357 23.71 23.71 0 0 1 3.776-1.73 21.74 21.74 0 0 1 4.028-1.047 23.437 23.437 0 0 1 8.333 0 24.282 24.282 0 0 1 7.803 2.777 26.198 26.198 0 0 1 3.45 2.357 24.62 24.62 0 0 1 5.336 6.287 20.785 20.785 0 0 1 1.762 3.72 21.32 21.32 0 0 1 1.083 3.955c.251 1.336.302 3.405.377 4.086.05.681.05-.68 0 0" fill="none" stroke-width="4" stroke-linecap="round" stroke="currentColor"/><path d="M69.404 56.633c-6.596-3.3-13.216-6.6-19.51-9.744m19.51 9.744c-6.747-3.379-13.493-6.758-19.51-9.744m0 0v19.489m0-19.49v19.49m0 0c4.355-2.148 8.71-4.322 19.51-9.745m-19.51 9.745c3.978-1.965 7.93-3.956 19.51-9.745m0 0h0m0 0h0" fill="currentColor" stroke-linecap="round" stroke="currentColor" stroke-width="4"/></g>`;
export const DISK_ICON_NAME = "disk";
export const DISK_ICON = `<path fill="none" stroke="currentColor" fill="#fff" d="M0 0h100v100H0z"/><path fill="none" stroke="currentColor" d="M20.832 4.168c21.824.145 43.645.289 74.68.5m-74.68-.5c17.09.113 34.176.227 74.68.5m0 0c.094 27.3.191 54.602.32 91.164m-.32-91.164c.113 32.633.23 65.27.32 91.164m0 0H4.168m91.664 0H4.168m0 0v-75m0 75v-75m0 0L20.832 4.168M4.168 20.832L20.832 4.168M20.832 4.168h58.336m-58.336 0h58.336m0 0v25m0-25v25m0 0H20.832m58.336 0H20.832m0 0v-25m0 25v-25" stroke-width="1.66668" /><path fill="none" stroke="currentColor" d="M29.168 4.168h16.664v16.664H29.168"/><path fill="none" stroke="currentColor" d="M29.168 4.168h16.664m-16.664 0h16.664m0 0v16.664m0-16.664v16.664m0 0H29.168m16.664 0H29.168m0 0V4.168m0 16.664V4.168M12.5 54.168h75m-75 0h75m0 0v41.664m0-41.664v41.664m0 0h-75m75 0h-75m0 0V54.168m0 41.664V54.168M20.832 62.5c20.11-.18 40.219-.36 55.68-.5m-55.68.5c14.656-.133 29.313-.262 55.68-.5M20.832 71.332c13.098-.117 26.2-.234 55.68-.5m-55.68.5l55.68-.5M21.117 79.582c20.645-.184 41.285-.371 55.68-.5m-55.68.5c18.153-.16 36.301-.324 55.68-.5" stroke-width="1.66668"/>`;
export const PNG_ICON_NAME = "save-png";

View File

@@ -7,6 +7,11 @@ import {
// English
export default {
// main.ts
INSTALL_SCRIPT: "Install this script",
UPDATE_SCRIPT: "An update is available - Click to install",
CHECKING_SCRIPT: "Checking if a newer version is available - Click to reinstall now",
UNABLETOCHECK_SCRIPT: "Update check was unsuccessful - Click to reinstall now",
UPTODATE_SCRIPT: "Script is installed and up to date - Click to reinstall now",
OPEN_AS_EXCALIDRAW: "Open as Excalidraw Drawing",
TOGGLE_MODE: "Toggle between Excalidraw and Markdown mode",
CONVERT_NOTE_TO_EXCALIDRAW: "Convert empty note to Excalidraw Drawing",
@@ -29,6 +34,7 @@ export default {
EXPORT_SVG: "Save as SVG next to the current file",
EXPORT_PNG: "Save as PNG next to the current file",
TOGGLE_LOCK: "Toggle Text Element edit RAW/PREVIEW",
DELETE_FILE: "Delete selected Image or Markdown file from Obsidian Vault",
INSERT_LINK: "Insert link to file",
INSERT_IMAGE: "Insert image from vault",
INSERT_MD: "Insert markdown file from vault",
@@ -37,6 +43,7 @@ export default {
ENTER_LATEX: "Enter a valid LaTeX expression",
//ExcalidrawView.ts
INSTALL_SCRIPT_BUTTON: "Install or update Excalidraw Scripts",
OPEN_AS_MD: "Open as Markdown",
SAVE_AS_PNG: "Save as PNG into Vault (CTRL/CMD+CLICK to export)",
SAVE_AS_SVG: "Save as SVG into Vault (CTRL/CMD+CLICK to export)",
@@ -49,7 +56,7 @@ export default {
TEXT_ELEMENT_EMPTY:
"No ImageElement is selected or TextElement is empty, or [[valid-link|alias]] or [alias](valid-link) is not found",
FILENAME_INVALID_CHARS:
'File name cannot contain any of the following characters: * " \\  < > : | ?',
'File name cannot contain any of the following characters: * " \\ < > : | ?',
FILE_DOES_NOT_EXIST:
"File does not exist. Hold down ALT (or ALT+SHIFT) and CLICK link button to create a new file.",
FORCE_SAVE:
@@ -264,6 +271,10 @@ export default {
FILETAG_NAME: "Set the type indicator for excalidraw.md files",
FILETAG_DESC: "The text or emojii to display as type indicator.",
INSERT_EMOJI: "Insert an emoji",
LIVEPREVIEW_NAME: "Immersive image embedding in live preview editing mode",
LIVEPREVIEW_DESC: "Turn this on to support image embedding styles such as ![[drawing|width|style]] in live preview editing mode. " +
"The setting will not effect the currently open documents. You need close the open documents and re-open them for the change " +
"to take effect.",
//openDrawings.ts
SELECT_FILE: "Select a file then press enter.",

View File

@@ -14,12 +14,16 @@ import {
ViewState,
Notice,
loadMathJax,
Scope,
request,
} from "obsidian";
import {
BLANK_DRAWING,
VIEW_TYPE_EXCALIDRAW,
EXCALIDRAW_ICON,
ICON_NAME,
SCRIPTENGINE_ICON,
SCRIPTENGINE_ICON_NAME,
DISK_ICON,
DISK_ICON_NAME,
PNG_ICON,
@@ -33,6 +37,8 @@ import {
nanoid,
DARK_BLANK_DRAWING,
CTRL_OR_CMD,
SCRIPT_INSTALL_CODEBLOCK,
SCRIPT_INSTALL_FOLDER,
} from "./constants";
import ExcalidrawView, { ExportSettings, TextMode } from "./ExcalidrawView";
import { getMarkdownDrawingSection } from "./ExcalidrawData";
@@ -59,18 +65,21 @@ import {
checkAndCreateFolder,
download,
embedFontsInSVG,
errorlog,
getAttachmentsFolderAndFilePath,
getIMGFilename,
getIMGPathFromExcalidrawFile,
getNewUniqueFilepath,
isObsidianThemeDark,
log,
splitFolderAndFilename,
svgToBase64,
} from "./Utils";
import { OneOffs } from "./OneOffs";
import { FileId } from "@zsviczian/excalidraw/types/element/types";
import { EmbeddedFilesLoader } from "./EmbeddedFileLoader";
import { ScriptEngine } from "./Scripts";
import { hoverEvent, initializeMarkdownPostProcessor, markdownPostProcessor, observer } from "./MarkdownPostProcessor";
declare module "obsidian" {
interface App {
@@ -123,6 +132,7 @@ export default class ExcalidrawPlugin extends Plugin {
async onload() {
addIcon(ICON_NAME, EXCALIDRAW_ICON);
addIcon(SCRIPTENGINE_ICON_NAME, SCRIPTENGINE_ICON);
addIcon(DISK_ICON_NAME, DISK_ICON);
addIcon(PNG_ICON_NAME, PNG_ICON);
addIcon(SVG_ICON_NAME, SVG_ICON);
@@ -140,6 +150,7 @@ export default class ExcalidrawPlugin extends Plugin {
this.registerExtensions(["excalidraw"], VIEW_TYPE_EXCALIDRAW);
this.addMarkdownPostProcessor();
this.registerInstallCodeblockProcessor();
this.addThemeObserver();
this.experimentalFileTypeDisplayToggle(this.settings.experimentalFileType);
this.registerCommands();
@@ -218,371 +229,155 @@ export default class ExcalidrawPlugin extends Plugin {
}
});
}
private registerInstallCodeblockProcessor() {
const codeblockProcessor = async (
source: string,
el: HTMLElement,
ctx: MarkdownPostProcessorContext,
plugin: ExcalidrawPlugin,
) => {
//Button next to the "List of available scripts" at the top
//In try/catch block because this approach is very error prone, depends on
//MarkdownRenderer() and index.md structure, in case these are not as
//expected this code will break
let button2: HTMLButtonElement = null;
try {
const link:HTMLElement = el.parentElement.querySelector(`a[href="#${
el.previousElementSibling.getAttribute("data-heading")}"]`);
link.style.paddingRight = "10px";
button2 = link.parentElement.createEl("button",null,(b) => {
b.setText(t("UPDATE_SCRIPT"));
b.addClass("mod-cta");
b.style.backgroundColor = "var(--interactive-success)";
b.style.display = "none";
});
} catch(e) {
errorlog({where:"this.registerInstallCodeblockProcessor", source, error:e});
}
source = source.trim();
el.createEl("button", null, async (button) => {
const setButtonText = (text:"CHECKING" | "INSTALL" | "UPTODATE" | "UPDATE" | "ERROR") => {
if(button2) button2.style.display = "none";
switch(text) {
case "CHECKING":
button.setText(t("CHECKING_SCRIPT"));
button.style.backgroundColor = "var(--interactive-normal)";
break;
case "INSTALL":
button.setText(t("INSTALL_SCRIPT"));
button.style.backgroundColor = "var(--interactive-accent)";
break;
case "UPTODATE":
button.setText(t("UPTODATE_SCRIPT"));
button.style.backgroundColor = "var(--interactive-normal)";
break;
case "UPDATE":
button.setText(t("UPDATE_SCRIPT"));
button.style.backgroundColor = "var(--interactive-success)";
if(button2) button2.style.display = null;
break;
case "ERROR":
button.setText(t("UNABLETOCHECK_SCRIPT"));
button.style.backgroundColor = "var(--interactive-normal)";
break;
}
}
button.addClass("mod-cta");
let decodedURI = source;
try{
decodedURI = decodeURI(source);
} catch(e) {
errorlog({
where:"ExcalidrawPlugin.registerInstallCodeblockProcessor.codeblockProcessor.onClick",
source,
error:e,
});
}
const fname = decodedURI.substring(
decodedURI.lastIndexOf("/") + 1,
);
const folder = `${
this.settings.scriptFolderPath
}/${SCRIPT_INSTALL_FOLDER}`;
const path = `${folder}/${fname}`;
let f = this.app.vault.getAbstractFileByPath(path);
setButtonText(f?"CHECKING":"INSTALL");
button.onclick = async () => {
try {
const data = await request({url:source});
if(f) {
await this.app.vault.modify(f as TFile,data);
} else {
await checkAndCreateFolder(this.app.vault,folder);
f = await this.app.vault.create(path,data);
}
setButtonText("UPTODATE");
new Notice(`Installed: ${(f as TFile).basename}`)
} catch (e) {
new Notice(`Error installing script: ${fname}`);
errorlog({
where:"ExcalidrawPlugin.registerInstallCodeblockProcessor.codeblockProcessor.onClick",
error:e,
});
}
};
if(button2) button2.onclick = button.onclick;
//check modified date on github
//https://superuser.com/questions/1406875/how-to-get-the-latest-commit-date-of-a-file-from-a-given-github-reposotiry
if(!f || !(f instanceof TFile)) return;
const msgHead = "https://api.github.com/repos/zsviczian/obsidian-excalidraw-plugin/commits?path=ea-scripts%2F";
const msgTail = "&page=1&per_page=1";
const data = await request({
url: msgHead+encodeURI(fname)+msgTail,
});
if(!data) {
setButtonText("ERROR");
return;
}
const result = JSON.parse(data);
if(result.length===0 || !result[0]?.commit?.committer?.date) {
setButtonText("ERROR");
return;
}
//@ts-ignore
const mtime = (new Date(result[0].commit.committer.date))/1;
if(mtime > f.stat.mtime) {
setButtonText("UPDATE");
return;
}
setButtonText("UPTODATE");
});
};
this.registerMarkdownCodeBlockProcessor(
SCRIPT_INSTALL_CODEBLOCK,
async (source, el, ctx) => {
el.addEventListener(RERENDER_EVENT, async (e) => {
e.stopPropagation();
el.empty();
codeblockProcessor(source, el, ctx, this);
});
codeblockProcessor(source, el, ctx, this);
},
);
}
/**
* Displays a transcluded .excalidraw image in markdown preview mode
*/
private addMarkdownPostProcessor() {
interface imgElementAttributes {
file?: TFile;
fname: string; //Excalidraw filename
fwidth: string; //Display width of image
fheight: string; //Display height of image
style: string; //css style to apply to IMG element
}
/**
* Generates an img element with the drawing encoded as a base64 SVG or a PNG (depending on settings)
* @param parts {imgElementAttributes} - display properties of the image
* @returns {Promise<HTMLElement>} - the IMG HTML element containing the image
*/
const getIMG = async (
imgAttributes: imgElementAttributes,
): Promise<HTMLElement> => {
let file = imgAttributes.file;
if (!imgAttributes.file) {
const f = this.app.vault.getAbstractFileByPath(imgAttributes.fname);
if (!(f && f instanceof TFile)) {
return null;
}
file = f;
}
const exportSettings: ExportSettings = {
withBackground: this.settings.exportWithBackground,
withTheme: this.settings.exportWithTheme,
};
const img = createEl("img");
let style = `max-width:${imgAttributes.fwidth}px !important; width:100%;`;
if (imgAttributes.fheight) {
style += `height:${imgAttributes.fheight}px;`;
}
img.setAttribute("style", style);
img.addClass(imgAttributes.style);
const theme = this.settings.previewMatchObsidianTheme
? isObsidianThemeDark()
? "dark"
: "light"
: !this.settings.exportWithTheme
? "light"
: undefined;
if (theme) {
exportSettings.withTheme = true;
}
const loader = new EmbeddedFilesLoader(
this,
theme ? theme === "dark" : undefined,
);
if (!this.settings.displaySVGInPreview) {
const width = parseInt(imgAttributes.fwidth);
let scale = 1;
if (width >= 600) {
scale = 2;
}
if (width >= 1200) {
scale = 3;
}
if (width >= 1800) {
scale = 4;
}
if (width >= 2400) {
scale = 5;
}
const png = await createPNG(
file.path,
scale,
exportSettings,
loader,
theme,
null,
null,
[],
this,
);
//const png = await getPNG(JSON_parse(scene),exportSettings, scale);
if (!png) {
return null;
}
img.src = URL.createObjectURL(png);
return img;
}
const svgSnapshot = (
await createSVG(
file.path,
true,
exportSettings,
loader,
theme,
null,
null,
[],
this,
)
).outerHTML;
let svg: SVGSVGElement = null;
const el = document.createElement("div");
el.innerHTML = svgSnapshot;
const firstChild = el.firstChild;
if (firstChild instanceof SVGSVGElement) {
svg = firstChild;
}
if (!svg) {
return null;
}
svg = embedFontsInSVG(svg);
svg.removeAttribute("width");
svg.removeAttribute("height");
img.setAttribute("src", svgToBase64(svg.outerHTML));
return img;
};
const createImageDiv = async (
attr: imgElementAttributes,
): Promise<HTMLDivElement> => {
const img = await getIMG(attr);
return createDiv(attr.style, (el) => {
el.append(img);
el.setAttribute("src", attr.file.path);
if (attr.fwidth) {
el.setAttribute("w", attr.fwidth);
}
if (attr.fheight) {
el.setAttribute("h", attr.fheight);
}
el.onClickEvent((ev) => {
if (
ev.target instanceof Element &&
ev.target.tagName.toLowerCase() != "img"
) {
return;
}
const src = el.getAttribute("src");
if (src) {
this.openDrawing(
this.app.vault.getAbstractFileByPath(src) as TFile,
ev[CTRL_OR_CMD],
);
} //.ctrlKey||ev.metaKey);
});
el.addEventListener(RERENDER_EVENT, async (e) => {
e.stopPropagation();
el.empty();
const img = await getIMG({
fname: el.getAttribute("src"),
fwidth: el.getAttribute("w"),
fheight: el.getAttribute("h"),
style: el.getAttribute("class"),
});
el.append(img);
});
});
};
const tmpObsidianWYSIWYG = async (
el: HTMLElement,
ctx: MarkdownPostProcessorContext,
) => {
if (!ctx.frontmatter) {
return;
}
if (!ctx.frontmatter.hasOwnProperty("excalidraw-plugin")) {
return;
}
//@ts-ignore
if (ctx.remainingNestLevel < 4) {
return;
}
if (!el.querySelector(".frontmatter")) {
el.style.display = "none";
return;
}
const attr: imgElementAttributes = {
fname: ctx.sourcePath,
fheight: "",
fwidth: this.settings.width,
style: "excalidraw-svg",
};
attr.file = this.app.metadataCache.getFirstLinkpathDest(
ctx.sourcePath,
"",
);
const div = await createImageDiv(attr);
el.childNodes.forEach(
(child: HTMLElement) => (child.style.display = "none"),
);
el.appendChild(div);
};
/**
*
* @param el
* @param ctx
*/
const markdownPostProcessor = async (
el: HTMLElement,
ctx: MarkdownPostProcessorContext,
) => {
const embeddedItems = el.querySelectorAll(".internal-embed");
if (embeddedItems.length === 0) {
tmpObsidianWYSIWYG(el, ctx);
return;
}
const attr: imgElementAttributes = {
fname: "",
fheight: "",
fwidth: "",
style: "",
};
let alt: string;
let parts;
let file: TFile;
for (const drawing of embeddedItems) {
attr.fname = drawing.getAttribute("src");
file = this.app.metadataCache.getFirstLinkpathDest(
attr.fname,
ctx.sourcePath,
);
if (!file && ctx.frontmatter?.hasOwnProperty("excalidraw-plugin")) {
attr.fname = ctx.sourcePath;
file = this.app.metadataCache.getFirstLinkpathDest(
attr.fname,
ctx.sourcePath,
);
}
if (file && file instanceof TFile && this.isExcalidrawFile(file)) {
attr.fwidth = drawing.getAttribute("width")
? drawing.getAttribute("width")
: this.settings.width;
attr.fheight = drawing.getAttribute("height");
alt = drawing.getAttribute("alt");
if (alt == attr.fname) {
alt = "";
} //when the filename starts with numbers followed by a space Obsidian recognizes the filename as alt-text
attr.style = "excalidraw-svg";
if (alt) {
//for some reason Obsidian renders ![]() in a DIV and ![[]] in a SPAN
//also the alt-text of the DIV does not include the alt-text of the image
//thus need to add an additional "|" character when its a SPAN
if (drawing.tagName.toLowerCase() == "span") {
alt = `|${alt}`;
}
//1:width, 2:height, 3:style 1 2 3
parts = alt.match(/[^\|]*\|?(\d*%?)x?(\d*%?)\|?(.*)/);
attr.fwidth = parts[1] ? parts[1] : this.settings.width;
attr.fheight = parts[2];
if (parts[3] != attr.fname) {
attr.style = `excalidraw-svg${parts[3] ? `-${parts[3]}` : ""}`;
}
}
attr.fname = file?.path;
attr.file = file;
const div = await createImageDiv(attr);
drawing.parentElement.replaceChild(div, drawing);
}
}
};
initializeMarkdownPostProcessor(this);
this.registerMarkdownPostProcessor(markdownPostProcessor);
/**
* internal-link quick preview
* @param e
* @returns
*/
const hoverEvent = (e: any) => {
if (!e.linktext) {
this.hover.linkText = null;
return;
}
this.hover.linkText = e.linktext;
this.hover.sourcePath = e.sourcePath;
};
// internal-link quick preview
this.registerEvent(this.app.workspace.on("hover-link", hoverEvent));
//monitoring for div.popover.hover-popover.file-embed.is-loaded to be added to the DOM tree
this.observer = new MutationObserver(async (m) => {
if (m.length == 0) {
return;
}
if (!this.hover.linkText) {
return;
}
const file = this.app.metadataCache.getFirstLinkpathDest(
this.hover.linkText,
this.hover.sourcePath ? this.hover.sourcePath : "",
);
if (!file) {
return;
}
if (!(file instanceof TFile)) {
return;
}
if (file.extension !== "excalidraw") {
return;
}
const svgFileName = getIMGFilename(file.path, "svg");
const svgFile = this.app.vault.getAbstractFileByPath(svgFileName);
if (svgFile && svgFile instanceof TFile) {
return;
} //If auto export SVG or PNG is enabled it will be inserted at the top of the excalidraw file. No need to manually insert hover preview
const pngFileName = getIMGFilename(file.path, "png");
const pngFile = this.app.vault.getAbstractFileByPath(pngFileName);
if (pngFile && pngFile instanceof TFile) {
return;
} //If auto export SVG or PNG is enabled it will be inserted at the top of the excalidraw file. No need to manually insert hover preview
if (!this.hover.linkText) {
return;
}
if (m.length != 1) {
return;
}
if (m[0].addedNodes.length != 1) {
return;
}
if (
//@ts-ignore
m[0].addedNodes[0].className !=
"popover hover-popover file-embed is-loaded"
) {
return;
}
const node = m[0].addedNodes[0];
node.empty();
//this div will be on top of original DIV. By stopping the propagation of the click
//I prevent the default Obsidian feature of openning the link in the native app
const img = await getIMG({
file,
fname: file.path,
fwidth: "300",
fheight: null,
style: "excalidraw-svg",
});
const div = createDiv("", async (el) => {
el.appendChild(img);
el.setAttribute("src", file.path);
el.onClickEvent((ev) => {
ev.stopImmediatePropagation();
const src = el.getAttribute("src");
if (src) {
this.openDrawing(
this.app.vault.getAbstractFileByPath(src) as TFile,
ev[CTRL_OR_CMD],
);
} //.ctrlKey||ev.metaKey);
});
});
node.appendChild(div);
});
this.observer = observer;
this.observer.observe(document, { childList: true, subtree: true });
}
@@ -684,7 +479,7 @@ export default class ExcalidrawPlugin extends Plugin {
this.insertMDDialog = new InsertMDDialog(this);
this.addRibbonIcon(ICON_NAME, t("CREATE_NEW"), async (e) => {
this.createDrawing(this.getNextDefaultFilename(), e[CTRL_OR_CMD]); //.ctrlKey||e.metaKey);
this.createAndOpenDrawing(this.getNextDefaultFilename(), e[CTRL_OR_CMD]); //.ctrlKey||e.metaKey);
});
const fileMenuHandlerCreateNew = (menu: Menu, file: TFile) => {
@@ -699,7 +494,7 @@ export default class ExcalidrawPlugin extends Plugin {
file.path.substr(0, file.path.lastIndexOf(file.name)),
);
}
this.createDrawing(
this.createAndOpenDrawing(
this.getNextDefaultFilename(),
false,
folderpath,
@@ -831,7 +626,7 @@ export default class ExcalidrawPlugin extends Plugin {
id: "excalidraw-autocreate",
name: t("NEW_IN_NEW_PANE"),
callback: () => {
this.createDrawing(this.getNextDefaultFilename(), true);
this.createAndOpenDrawing(this.getNextDefaultFilename(), true);
},
});
@@ -839,7 +634,7 @@ export default class ExcalidrawPlugin extends Plugin {
id: "excalidraw-autocreate-on-current",
name: t("NEW_IN_ACTIVE_PANE"),
callback: () => {
this.createDrawing(this.getNextDefaultFilename(), false);
this.createAndOpenDrawing(this.getNextDefaultFilename(), false);
},
});
@@ -858,12 +653,12 @@ export default class ExcalidrawPlugin extends Plugin {
activeView.file.path,
filename,
);
this.embedDrawing(fFp.filepath);
this.createDrawing(
const file = await this.createDrawing(
filename,
inNewPane,
fFp.folder === "" ? null : fFp.folder,
);
await this.embedDrawing(fFp.filepath);
this.openDrawing(file, inNewPane);
};
this.addCommand({
@@ -954,6 +749,42 @@ export default class ExcalidrawPlugin extends Plugin {
},
});
this.addCommand({
id: "delete-file",
name: t("DELETE_FILE"),
checkCallback: (checking: boolean) => {
if (checking) {
const view = this.app.workspace.activeLeaf.view;
return view instanceof ExcalidrawView;
}
const view = this.app.workspace.activeLeaf.view;
if (view instanceof ExcalidrawView) {
this.ea.reset();
this.ea.setView(view);
const el = this.ea.getViewSelectedElement();
if (el.type !== "image") {
new Notice(
"Please select an image or embedded markdown document",
4000,
);
return true;
}
const file = this.ea.getViewFileForImageElement(el);
if (!file) {
new Notice(
"Please select an image or embedded markdown document",
4000,
);
return true;
}
this.app.vault.delete(file);
this.ea.deleteViewElements([el]);
return true;
}
return false;
},
});
this.addCommand({
id: "insert-link",
hotkeys: [{ modifiers: ["Ctrl" || "Meta", "Shift"], key: "k" }],
@@ -1131,7 +962,7 @@ export default class ExcalidrawPlugin extends Plugin {
const fname = getNewUniqueFilepath(
this.app.vault,
filename,
normalizePath(file.path.substr(0, file.path.lastIndexOf(file.name))),
normalizePath(file.path.substring(0, file.path.lastIndexOf(file.name))),
);
log(fname);
const result = await this.app.vault.create(
@@ -1146,8 +977,8 @@ export default class ExcalidrawPlugin extends Plugin {
normalizePath(oldIMGpath),
);
if (imgFile && imgFile instanceof TFile) {
const newIMGpath = fname.substr(0, fname.lastIndexOf(".md")) + ext;
this.app.vault.rename(imgFile, newIMGpath);
const newIMGpath = fname.substring(0, fname.lastIndexOf(".md")) + ext;
this.app.fileManager.renameFile(imgFile, newIMGpath);
}
});
}
@@ -1266,6 +1097,7 @@ export default class ExcalidrawPlugin extends Plugin {
);
}
private popScope: Function = null;
private registerEventListeners() {
const self = this;
this.app.workspace.onLayoutReady(async () => {
@@ -1290,7 +1122,7 @@ export default class ExcalidrawPlugin extends Plugin {
);
if (imgFile && imgFile instanceof TFile) {
const newIMGpath = getIMGPathFromExcalidrawFile(file.path, ext);
await self.app.vault.rename(imgFile, newIMGpath);
await self.app.fileManager.renameFile(imgFile, newIMGpath);
}
});
};
@@ -1374,6 +1206,7 @@ export default class ExcalidrawPlugin extends Plugin {
const newActiveviewEV: ExcalidrawView =
leaf.view instanceof ExcalidrawView ? leaf.view : null;
self.activeExcalidrawView = newActiveviewEV;
if (newActiveviewEV) {
self.lastActiveExcalidrawFilePath = newActiveviewEV.file?.path;
}
@@ -1412,6 +1245,23 @@ export default class ExcalidrawPlugin extends Plugin {
}, 2000);
} //refresh embedded files
}
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/300
if (self.popScope) {
self.popScope();
self.popScope = null;
}
if (newActiveviewEV) {
//@ts-ignore
const scope = new Scope(self.app.scope);
scope.register(["Mod"], "Enter", () => true);
//@ts-ignore
self.app.keymap.pushScope(scope);
self.popScope = () => {
//@ts-ignore
self.app.keymap.popScope(scope);
};
}
};
self.registerEvent(
self.app.workspace.on(
@@ -1424,6 +1274,10 @@ export default class ExcalidrawPlugin extends Plugin {
onunload() {
destroyExcalidrawAutomate();
if (this.popScope) {
this.popScope();
this.popScope = null;
}
this.observer.disconnect();
this.themeObserver.disconnect();
if (this.fileExplorerObserver) {
@@ -1442,26 +1296,23 @@ export default class ExcalidrawPlugin extends Plugin {
//this.saveSettings();
}
public embedDrawing(data: string) {
public async embedDrawing(data: string) {
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
if (activeView) {
const editor = activeView.editor;
switch (this.settings.embedType) {
case "excalidraw":
editor.replaceSelection(`![[${data}]]`);
break;
case "PNG":
editor.replaceSelection(
`![[${data.substr(0, data.lastIndexOf("."))}.png]] ([[${data}|*]])`,
);
break;
case "SVG":
editor.replaceSelection(
`![[${data.substr(0, data.lastIndexOf("."))}.svg]] ([[${data}|*]])`,
);
break;
if (this.settings.embedType === "excalidraw") {
editor.replaceSelection(`![[${data}]]`);
editor.focus();
return;
}
const filename = `${data.substring(
0,
data.lastIndexOf("."),
)}.${this.settings.embedType.toLowerCase()}`;
await this.app.vault.create(filename, "");
editor.replaceSelection(
`![[${filename}]]\n%%[[${data}|🖋 Edit in Excalidraw]]%%`,
);
editor.focus();
}
}
@@ -1596,27 +1447,29 @@ export default class ExcalidrawPlugin extends Plugin {
public async createDrawing(
filename: string,
onNewPane: boolean,
foldername?: string,
initData?: string,
): Promise<string> {
): Promise<TFile> {
const folderpath = normalizePath(
foldername ? foldername : this.settings.folder,
);
await checkAndCreateFolder(this.app.vault, folderpath); //create folder if it does not exist
const fname = getNewUniqueFilepath(this.app.vault, filename, folderpath);
if (initData) {
this.openDrawing(await this.app.vault.create(fname, initData), onNewPane);
return fname;
}
this.openDrawing(
await this.app.vault.create(fname, await this.getBlankDrawing()),
onNewPane,
return await this.app.vault.create(
fname,
initData ?? (await this.getBlankDrawing()),
);
return fname;
}
public async createAndOpenDrawing(
filename: string,
onNewPane: boolean,
foldername?: string,
initData?: string,
): Promise<string> {
const file = await this.createDrawing(filename, foldername, initData);
this.openDrawing(file, onNewPane);
return file.path;
}
public async setMarkdownView(leaf: WorkspaceLeaf) {
@@ -1649,7 +1502,7 @@ export default class ExcalidrawPlugin extends Plugin {
if (f.extension == "excalidraw") {
return true;
}
const fileCache = this.app.metadataCache.getFileCache(f);
const fileCache = f ? this.app.metadataCache.getFileCache(f) : null;
return !!fileCache?.frontmatter && !!fileCache.frontmatter[FRONTMATTER_KEY];
}
}

View File

@@ -31,7 +31,7 @@ export class OpenFileDialog extends FuzzySuggestModal<TFile> {
this.inputEl.onkeyup = (e) => {
if (e.key == "Enter" && this.action == openDialogAction.openFile) {
if (this.containerEl.innerText.includes(EMPTY_MESSAGE)) {
this.plugin.createDrawing(
this.plugin.createAndOpenDrawing(
`${this.plugin.settings.folder}/${this.inputEl.value}.excalidraw.md`,
this.onNewPane,
);

View File

@@ -45,6 +45,7 @@ export interface ExcalidrawSettings {
compatibilityMode: boolean;
experimentalFileType: boolean;
experimentalFileTag: string;
experimentalLivePreview: boolean;
loadCount: number; //version 1.2 migration counter
drawingOpenCount: number;
library: string;
@@ -58,6 +59,7 @@ export interface ExcalidrawSettings {
mdFont: string;
mdFontColor: string;
mdCSS: string;
scriptEngineSettings: {};
}
export const DEFAULT_SETTINGS: ExcalidrawSettings = {
@@ -94,6 +96,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
syncExcalidraw: false,
experimentalFileType: false,
experimentalFileTag: "✏️",
experimentalLivePreview: true,
compatibilityMode: false,
loadCount: 0,
drawingOpenCount: 0,
@@ -113,6 +116,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
mdFont: "Virgil",
mdFontColor: "Black",
mdCSS: "",
scriptEngineSettings: {},
};
export class ExcalidrawSettingTab extends PluginSettingTab {
@@ -153,6 +157,13 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
if (this.requestEmbedUpdate) {
this.plugin.triggerEmbedUpdates();
}
if (
this.plugin.settings.scriptFolderPath === "/" ||
this.plugin.settings.scriptFolderPath === ""
) {
this.plugin.settings.scriptFolderPath = "Excalidraw/Scripts";
this.plugin.saveSettings();
}
this.plugin.scriptEngine.updateScriptPath();
}
@@ -819,5 +830,18 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
this.applySettingsUpdate();
}),
);
new Setting(containerEl)
.setName(t("LIVEPREVIEW_NAME"))
.setDesc(t("LIVEPREVIEW_DESC"))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.experimentalLivePreview)
.onChange(async (value) => {
this.plugin.settings.experimentalLivePreview = value;
this.applySettingsUpdate();
}),
);
}
}

View File

@@ -35,6 +35,10 @@ img.excalidraw-svg-right {
float: right;
}
.excalidraw-svg-center {
text-align: center;
}
img.excalidraw-svg-left {
float: left;
}
@@ -95,3 +99,38 @@ li[data-testid] {
margin-bottom: 20px;
}
.excalidraw-scriptengine-install td>img {
width: 100%;
max-width:800px;
}
.excalidraw-scriptengine-install img.coffee {
width: 130px;
}
.excalidraw-scriptengine-install tr {
vertical-align: top;
}
.excalidraw-scriptengine-install table {
max-width: 130ch;
}
.excalidraw-scriptengine-install td.label {
width: 11ch;
font-weight: bold;
padding-right: 5px;
}
.excalidraw-scriptengine-install td.data {
width: 100%;
}
.modal-content.excalidraw-scriptengine-install {
max-width: 130ch;
}
.excalidraw-scriptengine-install .modal {
max-height:90%;
}

View File

@@ -1,4 +1,4 @@
{
"1.5.0": "0.12.16",
"1.5.15": "0.12.16",
"1.4.2": "0.11.13"
}

View File

@@ -920,6 +920,36 @@
"@babel/helper-validator-identifier" "^7.14.9"
"to-fast-properties" "^2.0.0"
"@codemirror/rangeset@^0.19.5":
"integrity" "sha512-L3b+RIwIRKOJ3pJLOtpkxCUjGnxZKFyPb0CjYWKnVLuzEIaEExWWK7sp6rsejxOy8RjYzfCHlFhYB4UdQN7brw=="
"resolved" "https://registry.npmjs.org/@codemirror/rangeset/-/rangeset-0.19.5.tgz"
"version" "0.19.5"
dependencies:
"@codemirror/state" "^0.19.0"
"@codemirror/state@^0.19.0", "@codemirror/state@^0.19.3", "@codemirror/state@^0.19.6":
"integrity" "sha512-sqIQZE9VqwQj7D4c2oz9mfLhlT1ElAzGB5lO1lE33BPyrdNy1cJyCIOecT4cn4VeJOFrnjOeu+IftZ3zqdFETw=="
"resolved" "https://registry.npmjs.org/@codemirror/state/-/state-0.19.6.tgz"
"version" "0.19.6"
dependencies:
"@codemirror/text" "^0.19.0"
"@codemirror/text@^0.19.0":
"integrity" "sha512-Syu5Xc7tZzeUAM/y4fETkT0zgGr48rDG+w4U38bPwSIUr+L9S/7w2wDE1WGNzjaZPz12F6gb1gxWiSTg9ocLow=="
"resolved" "https://registry.npmjs.org/@codemirror/text/-/text-0.19.5.tgz"
"version" "0.19.5"
"@codemirror/view@^0.19.31":
"integrity" "sha512-SLuLx9p0O1ZHXLehvl5MwSvUrQRcsNGemzTgJ0zRajmc3BBsNigI1PXxdo7tvBhO5DcAzRRBXoke9DZFUR6Qqg=="
"resolved" "https://registry.npmjs.org/@codemirror/view/-/view-0.19.37.tgz"
"version" "0.19.37"
dependencies:
"@codemirror/rangeset" "^0.19.5"
"@codemirror/state" "^0.19.3"
"@codemirror/text" "^0.19.0"
"style-mod" "^4.0.0"
"w3c-keyname" "^2.2.4"
"@eslint/eslintrc@^1.0.5":
"integrity" "sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ=="
"resolved" "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz"
@@ -1202,10 +1232,10 @@
"@typescript-eslint/types" "5.6.0"
"eslint-visitor-keys" "^3.0.0"
"@zsviczian/excalidraw@0.10.0-obsidian-18":
"integrity" "sha512-hnlDZUVVOMSgIoKRTu6gGe6mQVg4ggExWqB/DyaWJkv9DoFigMUYvPYA8xz3t1hWqtRHKTWOyA1CiFJFK/Zt8w=="
"resolved" "https://registry.npmjs.org/@zsviczian/excalidraw/-/excalidraw-0.10.0-obsidian-18.tgz"
"version" "0.10.0-obsidian-18"
"@zsviczian/excalidraw@0.10.0-obsidian-33":
"integrity" "sha512-ych61N48QASpcU3YJGX5nCWmnsdku5vsm6RLp7eiE8HzAdGGDFTlbwYg6p+uBw7MeJIu9gQx2hET9Gnx79LFRg=="
"resolved" "https://registry.npmjs.org/@zsviczian/excalidraw/-/excalidraw-0.10.0-obsidian-33.tgz"
"version" "0.10.0-obsidian-33"
dependencies:
"dotenv" "10.0.0"
@@ -7441,11 +7471,13 @@
dependencies:
"isobject" "^3.0.1"
"obsidian@^0.12.16":
"integrity" "sha512-YwwhYrmlv71A/ntTiGOyGaqr9ONhKPE1OKN1tcHovLJC0N/BRZ7XlGpzaN1T3BnAE5H8DmYLsAI3h67u6eJPGQ=="
"resolved" "https://registry.npmjs.org/obsidian/-/obsidian-0.12.16.tgz"
"version" "0.12.16"
"obsidian@^0.13.11":
"integrity" "sha512-KxOvAh4CG5vzcukmHvyuK9hUIr6ZFlM9FQfGZEwrrEV8VG2/W2Tk5cWrg0VM7EkGE3QBmjX6owjIDIO8QDXVUQ=="
"resolved" "https://registry.npmjs.org/obsidian/-/obsidian-0.13.11.tgz"
"version" "0.13.11"
dependencies:
"@codemirror/state" "^0.19.6"
"@codemirror/view" "^0.19.31"
"@types/codemirror" "0.0.108"
"moment" "2.29.1"
@@ -9736,6 +9768,11 @@
"loader-utils" "^1.0.2"
"schema-utils" "^0.3.0"
"style-mod@^4.0.0":
"integrity" "sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw=="
"resolved" "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz"
"version" "4.0.0"
"supports-color@^2.0.0":
"integrity" "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
"resolved" "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz"
@@ -10362,6 +10399,11 @@
"resolved" "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz"
"version" "1.1.2"
"w3c-keyname@^2.2.4":
"integrity" "sha512-tOhfEwEzFLJzf6d1ZPkYfGj+FWhIpBux9ppoP3rlclw3Z0BZv3N7b7030Z1kYth+6rDuAsXUFr+d0VE6Ed1ikw=="
"resolved" "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.4.tgz"
"version" "2.2.4"
"walker@~1.0.5":
"integrity" "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs="
"resolved" "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz"