Compare commits

..

85 Commits

Author SHA1 Message Date
Zsolt Viczian
1ca87741a5 1.6.14 2022-02-20 23:14:23 +01:00
zsviczian
463eb1d228 Update README.md 2022-02-19 23:32:49 +01:00
Zsolt Viczian
935febf337 disable save on addElementsToView to avoid the autozoom effect 2022-02-19 22:21:46 +01:00
Zsolt Viczian
e4c7fe7fc9 edit most recently drawn line 2022-02-19 20:00:09 +01:00
Zsolt Viczian
e98b1755ee 1.6.13 2022-02-19 18:16:00 +01:00
Zsolt Viczian
bc71bde9b7 1.6.12 2022-02-19 00:00:52 +01:00
zsviczian
2dc10af004 Update README.md 2022-02-18 13:22:32 +01:00
zsviczian
46d21f53e5 Merge pull request #452 from 1-2-3/master
feat: add detection angle function in script Repeat Elements
2022-02-18 10:15:00 +01:00
zahuifan
44d9b44c88 update demo image 2022-02-18 16:11:45 +08:00
zahuifan
5875f268f4 feat: add detection angle function in script 2022-02-18 15:58:19 +08:00
zsviczian
bc1120ac3a Update Toggle Fullscreen on Mobile.md 2022-02-18 07:43:55 +01:00
Zsolt Viczian
40a10620c1 added warning 2022-02-17 20:56:25 +01:00
Zsolt Viczian
28295deaa0 add thind header to allow command palette 2022-02-17 20:43:48 +01:00
Zsolt Viczian
b783d9b992 script toggle fullscreen 2022-02-17 20:34:13 +01:00
Zsolt Viczian
98df940f52 correct link 2022-02-17 20:04:01 +01:00
Zsolt Viczian
ab3b84a765 update script 2022-02-17 20:01:53 +01:00
Zsolt Viczian
5fc6905ac7 upload image 2022-02-17 19:59:29 +01:00
Zsolt Viczian
00c7a0e934 Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2022-02-17 19:36:44 +01:00
Zsolt Viczian
60ed7f21f6 toggle fullscreen script 2022-02-17 19:36:26 +01:00
zsviczian
9e0c5c48d4 Update bug_report.md 2022-02-17 11:49:37 +01:00
zsviczian
7e76c2b693 Merge pull request #446 from 1-2-3/master
fix: line and freedraw do not scale correctly in repeat elements script
2022-02-17 11:20:08 +01:00
zahuifan
dd9b363427 fix: line and freedraw do not scale correctly 2022-02-17 16:51:59 +08:00
zahuifan
90b693151a fix: line and freedraw do not scale correctly 2022-02-17 16:30:53 +08:00
zsviczian
d280c7e953 Merge pull request #439 from 1-2-3/master
feat: new repeat elements script
2022-02-16 22:06:19 +01:00
zahuifan
b227ad05fd update repeat-elements demo picture 2022-02-16 16:49:54 +08:00
zahuifan
cf07458fbb fix: when alert return 2022-02-16 16:37:50 +08:00
zahuifan
c191b7264d feat: new repeat elements script 2022-02-16 16:17:19 +08:00
Zsolt Viczian
109fe05302 autozoom after new text, scriptstore style 2022-02-15 22:11:32 +01:00
Zsolt Viczian
d55bb1a2c4 organic line script 2022-02-15 21:40:28 +01:00
Zsolt Viczian
1362c7f416 add image 2022-02-15 21:31:35 +01:00
Zsolt Viczian
dc718bbfe1 1.6.11 2022-02-15 19:37:15 +01:00
zsviczian
3ab2a3d7fc Merge pull request #437 from 1-2-3/master
fix: await addElementsToView() in scripts
2022-02-15 13:55:24 +01:00
zahuifan
494556b501 Merge branch 'master' of github.com:1-2-3/obsidian-excalidraw-plugin 2022-02-15 19:28:00 +08:00
zahuifan
76b767f505 fix: await addElementsToView() in scripts 2022-02-15 19:26:55 +08:00
zsviczian
8178328ce8 Merge pull request #435 from 1-2-3/master
fix: only wraps the first element in 1.6.10
2022-02-15 10:55:27 +01:00
zsviczian
766bf25773 Update Box Each Selected Groups.md 2022-02-15 10:52:49 +01:00
zahuifan
ccf81efa7b fix: only wraps the first element in 1.6.10 2022-02-15 09:56:45 +08:00
Zsolt Viczian
4e101f8786 1.6.10 2022-02-14 21:50:59 +01:00
Zsolt Viczian
05a7ee8270 insert link/image invalid characters 2022-02-13 16:13:31 +01:00
Zsolt Viczian
0e5004ae08 Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2022-02-13 14:02:09 +01:00
Zsolt Viczian
77f103d46e 1.6.9 2022-02-13 14:01:34 +01:00
zsviczian
e2ad92a19c Update README.md 2022-02-11 23:00:53 +01:00
Zsolt Viczian
0eb3bc6798 1.6.8 2022-02-11 22:51:28 +01:00
zsviczian
b11cf139a8 Update versions.json 2022-02-10 08:19:57 +01:00
zsviczian
81ce31dfbf Update manifest.json 2022-02-10 08:19:40 +01:00
zsviczian
e3f7455511 Update ExcalidrawView.ts 2022-02-10 08:17:46 +01:00
Zsolt Viczian
321a5e8ea1 1.6.6 2022-02-09 22:49:14 +01:00
Zsolt Viczian
b16362a6fd 1.6.5 2022-02-08 20:15:11 +01:00
Zsolt Viczian
a52f633755 1.6.4 2022-02-08 19:52:26 +01:00
Zsolt Viczian
65fe5282c4 1.6.3 2022-02-07 21:48:37 +01:00
Zsolt Viczian
4efa4866ef publish scribble helper 2022-02-06 23:37:06 +01:00
Zsolt Viczian
a11c195ea4 scribble script 2022-02-06 23:33:30 +01:00
Zsolt Viczian
9d31de7674 image 2022-02-06 23:30:11 +01:00
Zsolt Viczian
bf9e6e0179 1.6.2 2022-02-06 21:46:29 +01:00
Zsolt Viczian
1638cb7fc7 1.6.1 2022-02-06 21:30:48 +01:00
Zsolt Viczian
96c4e9a56b publish new scripts 2022-02-06 20:25:06 +01:00
Zsolt Viczian
bfffa9609f Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2022-02-06 20:19:07 +01:00
Zsolt Viczian
ba4e3652da adding images 2022-02-06 20:18:35 +01:00
zsviczian
bb0d49d94f Update README.md 2022-02-04 20:58:53 +01:00
Zsolt Viczian
55eb54d162 1.6.0 2022-02-04 19:53:10 +01:00
Zsolt Viczián
2c584b50ec fix typo 2022-02-04 14:43:37 +00:00
Zsolt Viczián
830f51e1ec updateElementLinks findNewElementLinksInScene 2022-02-04 14:31:22 +00:00
Zsolt Viczian
259176e7c8 support new excalidraw link element 2022-02-03 22:44:18 +01:00
Zsolt Viczian
4ae38c70bf open in active pane, and set filename 2022-02-03 17:58:30 +01:00
Zsolt Viczian
0a2ef2d751 1.5.30 2022-02-03 07:40:24 +01:00
Zsolt Viczian
1d830526a7 1.5.29 2022-02-02 21:59:24 +01:00
Zsolt Viczian
71f1072822 1.5.28 2022-02-01 23:22:29 +01:00
Zsolt Viczian
8029c5d4cd 1.5.27 2022-02-01 22:26:04 +01:00
Zsolt Viczian
962c6a71f7 improved new link handling 2022-02-01 21:56:19 +01:00
zsviczian
3e0a6e839f Update README.md 2022-01-30 16:11:10 +01:00
Zsolt Viczian
7aa416766c updated script description 2022-01-30 13:47:32 +01:00
Zsolt Viczian
3dae930201 1.5.26 2022-01-30 10:10:34 +01:00
Zsolt Viczian
379c2d0e52 1.5.25 2022-01-29 16:54:37 +01:00
Zsolt Viczian
d9a1113f25 changed filename 2022-01-29 15:38:21 +01:00
Zsolt Viczian
d0d5677c81 select all if only one type of element in view 2022-01-29 14:52:01 +01:00
Zsolt Viczian
a364c0fe45 typo 2022-01-29 14:37:41 +01:00
Zsolt Viczian
bf9a917af3 typo 2022-01-29 14:29:34 +01:00
Zsolt Viczian
7d98bf691c 1.5.24 2022-01-29 14:20:34 +01:00
Zsolt Viczian
b927a48eb3 add image 2022-01-29 14:03:15 +01:00
Zsolt Viczian
7f2af801a9 add image 2022-01-29 10:23:06 +01:00
Zsolt Viczian
2c2bbc2d62 add image 2022-01-29 10:21:08 +01:00
Zsolt Viczian
f66cf344da upload image 2022-01-29 08:10:11 +01:00
Zsolt Viczian
6c4169e9c9 . 2022-01-28 19:38:41 +01:00
Zsolt Viczian
92f8b68445 Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2022-01-28 19:35:50 +01:00
Zsolt Viczian
d0bfd834c8 1.5.23 2022-01-28 19:33:32 +01:00
83 changed files with 7337 additions and 4740 deletions

View File

@@ -23,16 +23,10 @@ A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Environment (please complete the following information):**
- OS including version: [e.g. iOS 15.1, Android 9, Windows 11, etc]
- Plugin version:
- Obsidian version:
**Additional context**
Add any other context about the problem here.

2
.replit Normal file
View File

@@ -0,0 +1,2 @@
language = "nodejs"
run = "npm run dev"

View File

@@ -13,7 +13,8 @@ Please upgrade to Obsidian v0.12.19 or higher to get the latest release.
|[![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)|
|[![fourtfont](https://user-images.githubusercontent.com/14358394/149659524-2a4e0a24-40c9-4e66-a6b1-c92f3b88ecd5.jpg)](https://youtu.be/eKFmrSQhFA4)|||
|[![fourtfont](https://user-images.githubusercontent.com/14358394/149659524-2a4e0a24-40c9-4e66-a6b1-c92f3b88ecd5.jpg)](https://youtu.be/eKFmrSQhFA4)|[![thumbnail](https://user-images.githubusercontent.com/14358394/151705333-54e9ffd2-0bd7-4d02-b99e-0bd4e4708d4d.jpg)](https://youtu.be/qbPIAZguJeo)|[![Thumbnail](https://user-images.githubusercontent.com/14358394/152585752-7eb0371f-0bab-40f6-a194-3b48e5811735.jpg)](https://youtu.be/2Y8OhkGiTHg)|
|[![Thumbnail](https://user-images.githubusercontent.com/14358394/153676009-6f86b2d7-c248-49a2-b802-be21c6999e4f.jpg)](https://youtu.be/2v9TZmQNO8c)|[![Thumbnail](https://user-images.githubusercontent.com/14358394/154821232-a404b6cf-72fb-4ce4-9d53-619132dce491.jpg)](https://youtu.be/xHPGWR3m0c8)||
# Key features
@@ -27,7 +28,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. See [styles.css](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/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

View File

@@ -134,7 +134,7 @@ export interface ExcalidrawAutomate {
},
): boolean;
addElementsToView( //Adds elements from elementsDict to the current view
repositionToCursor?: boolean, //default is false
repositionToCursor?: boolean, //default is false
save?: boolean, //default is true
//newElementsOnTop controls whether elements created with ExcalidrawAutomate
//are added at the bottom of the stack or the top of the stack of elements already in the view
@@ -185,13 +185,22 @@ export interface ExcalidrawAutomate {
//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.
openFileInNewOrAdjacentLeaf (file:TFile):WorkspaceLeaf;//Open a file in a new workspaceleaf or reuse an existing adjacent leaf depending on Excalidraw Plugin Settings
measureText(text:string):{ width: number, height: number }; //measure text size based on current style settings
setScriptSettings(settings: any): Promise<void>; //sets script settings.
openFileInNewOrAdjacentLeaf(file: TFile): WorkspaceLeaf; //Open a file in a new workspaceleaf or reuse an existing adjacent leaf depending on Excalidraw Plugin Settings
measureText(text: string): { width: number; height: number }; //measure text size based on current style settings
//verifyMinimumPluginVersion returns true if plugin version is >= than required
//recommended use:
//if(!(ea.verifyMinimumPluginVersion && ea.verifyMinimumPluginVersion("1.5.20"))) {new Notice("message");return;}
verifyMinimumPluginVersion(requiredVersion: string):boolean;
//recommended use:
//if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.20")) {new Notice("message");return;}
verifyMinimumPluginVersion(requiredVersion: string): boolean;
selectElementsInView(elements: ExcalidrawElement[]): void; //sets selection in view
generateElementId(): string; //returns an 8 character long random id
cloneElement(element: ExcalidrawElement): ExcalidrawElement; //Returns a clone of the element with a new id
moveViewElementToZIndex(elementId:number, newZIndex:number): void; //Moves the element to a specific position in the z-index
hexStringToRgb(color: string):number[];
rgbToHexString(color: number[]):string;
hslToRgb(color: number[]):number[];
rgbToHsl(color:number[]):number[];
colorNameToHex(color:string):string;
}
```

View File

@@ -23,9 +23,29 @@ This will allow you to assign hotkeys to your favorite scripts just like to any
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`: 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)`
- `inputPrompt: (header: string, placeholder?: string, value?: string, buttons?: [{caption:string, action:Function}])`
- Opens a prompt that asks for an input. Returns a string with the input.
- You need to await the result of inputPrompt.
- You need to await the result of inputPrompt.
- `buttons.action(input: string) => string`. The button action will receive the current input string. If action returns null, the input will be unchanged. If action returns a string, the inputPrompt will resolve to this value.
```typescript
let fileType = "";
const filename = await utils.inputPrompt (
"Filename for new document",
"Placeholder",
"DefaultFilename.md",
[
{
caption: "Markdown",
action: ()=>{fileType="md";return;}
},
{
caption: "Excalidraw",
action: ()=>{fileType="ex";return;}
}
]
);
```
- `suggester: (displayItems: string[], items: any[], hint?: string, instructions?:Instruction[])`
- Opens a suggester. Displays the displayItems and returns the corresponding item from items[].
- You need to await the result of suggester.

View File

@@ -22,4 +22,4 @@ elements.forEach((el)=>{
);
ea.addToGroup([el.id,ellipseId]);
});
ea.addElementsToView();
ea.addElementsToView(false,false);

View File

@@ -1,88 +0,0 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-link-and-open.jpg)
Prompt for a file in the vault. Add a link above or below (based on settings) the selected element, to the selected file. If no file is selected then the script creates a new file following the default filename defined for excalidraw embeds. Creates empty markdown file by default, this can be changed to creating a drawing by default via settings.
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.21")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
const BLANK_DRAWING = ["---","","excalidraw-plugin: parsed","","---","==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠==","","","%%","# Drawing","\x60\x60\x60json",'{"type":"excalidraw","version":2,"source":"https://excalidraw.com","elements":[],"appState":{"gridSize":null,"viewBackgroundColor":"#ffffff"}}',"\x60\x60\x60","%%"].join("\n");
settings = ea.getScriptSettings();
//set default values on first run
if(!settings["Link position"]) {
settings = {
"Link position" : {
value: "below",
valueset: ["above","below"],
description: "Add link below or above the selected object?"
},
"Link font size" : {
value: 12
}
};
ea.setScriptSettings(settings);
}
if(!settings["New document should be an Excalidraw drawing"]) {
settings = {
"New document should be an Excalidraw drawing": {
value: false,
description: "When adding a new document, should the new document be a blank markdown document (toggle == off) or a blank Excalidraw drawing (toggle=on)?"
},
...settings
};
ea.setScriptSettings(settings);
}
const below = settings["Link position"].value === "below";
const newDocExcalidraw = settings["New document should be an Excalidraw drawing"].value;
const fontSize = Math.floor(settings["Link font size"].value);
elements = ea.getViewSelectedElements();
if(elements.length === 0) {
new Notice("No selected elements");
return;
}
const files = app.vault.getFiles()
const filePaths = files.map((f)=>f.path);
file = await utils.suggester(filePaths,files,"Select file or press ESC to create a new document");
alias = null;
if(file) {
alias = file.basename;
} else {
const prefix = ea.targetView.file.path.substring(0,ea.targetView.file.path.length-3);
const timestamp = moment(Date.now()).format(ea.plugin.settings.drawingFilenameDateTime);
file = await app.vault.create(`${prefix} ${timestamp}.md`,newDocExcalidraw?BLANK_DRAWING:"");
if(newDocExcalidraw) await new Promise(r => setTimeout(r, 100)); //wait for metadata cache to update, so file opens as excalidraw
}
const filepath = app.metadataCache.fileToLinktext(file,ea.targetView.file.path,true);
ea.style.textAlign = "center";
ea.style.fontSize = fontSize;
const textElementsIfAny = elements.filter(el=>el.type==="text");
if(textElementsIfAny.length>0) ea.style.fontFamily = textElementsIfAny[0].fontFamily;
ea.style.strokeColor = elements[0].strokeColor;
const box = ea.getBoundingBox(elements);
const linkText = `[[${filepath}${alias?"|"+alias:""}]]`;
const size = ea.measureText(alias?ea.plugin.settings.linkPrefix+alias:linkText);
const id = ea.addText(
box.topX+(box.width-size.width)/2,
below ? box.topY + box.height + size.height : box.topY - size.height - 3,
linkText
);
ea.copyViewElementsToEAforEditing(elements);
ea.addToGroup(elements.map((e)=>e.id).concat([id]));
ea.addElementsToView(false,true,true);
ea.openFileInNewOrAdjacentLeaf(file);

View File

@@ -0,0 +1,65 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-link-and-open.jpg)
Prompts for a file from the vault. Adds a link to the selected element pointing to the selected file. You can control in settings to open the file in the current active pane or an adjacent pane.
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.21")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
settings = ea.getScriptSettings();
if(!settings["Open link in active pane"]) {
settings = {
"Open link in active pane": {
value: false,
description: "Open the link in the current active pane (on) or a new pane (off)."
},
...settings
};
ea.setScriptSettings(settings);
}
const openInCurrentPane = settings["Open link in active pane"].value;
elements = ea.getViewSelectedElements();
if(elements.length === 0) {
new Notice("No selected elements");
return;
}
const files = app.vault.getFiles()
const filePaths = files.map((f)=>f.path);
file = await utils.suggester(filePaths,files,"Select a file");
if(!file) return;
const link = `[[${app.metadataCache.fileToLinktext(file,ea.targetView.file.path,true)}]]`;
ea.style.backgroundColor = "transparent";
ea.style.strokeColor = "rgba(70,130,180,0.05)"
ea.style.strokeWidth = 2;
ea.style.roughness = 0;
if(elements.lenght===1 && elements[0].type !== "text") {
ea.copyViewElementsToEAforEditing(elements);
ea.getElements()[0].link = link;
} else {
const b = ea.getBoundingBox(elements);
const id = ea.addEllipse(b.topX+b.width-5, b.topY, 5, 5);
ea.getElement(id).link = link;
ea.copyViewElementsToEAforEditing(elements);
ea.addToGroup(elements.map((e)=>e.id).concat([id]));
}
await ea.addElementsToView(false,true,true);
ea.selectElementsInView(ea.getElements());
if(openInCurrentPane) {
app.workspace.openLinkText(file.path,ea.targetView.file.path,false);
return;
}
ea.openFileInNewOrAdjacentLeaf(file);

View File

@@ -0,0 +1,91 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-link-to-new-page-and-pen.jpg)
Prompts for filename. Offers option to create and open a new Markdown or Excalidraw document. Adds link pointing to the new file, to the selected objects in the drawing. You can control in settings to open the file in the current active pane or an adjacent pane.
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.6.1")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
const BLANK_DRAWING = ["---","","excalidraw-plugin: parsed","","---","==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠==","","","%%","# Drawing","\x60\x60\x60json",'{"type":"excalidraw","version":2,"source":"https://excalidraw.com","elements":[],"appState":{"gridSize":null,"viewBackgroundColor":"#ffffff"}}',"\x60\x60\x60","%%"].join("\n");
settings = ea.getScriptSettings();
if(!settings["Open link in active pane"]) {
settings = {
"Open link in active pane": {
value: false,
description: "Open the link in the current active pane (on) or a new pane (off)."
},
...settings
};
ea.setScriptSettings(settings);
}
const openInCurrentPane = settings["Open link in active pane"].value;
elements = ea.getViewSelectedElements();
if(elements.length === 0) {
new Notice("No selected elements");
return;
}
const activeFile = ea.targetView.file;
const prefix = activeFile.basename;
const timestamp = moment(Date.now()).format(ea.plugin.settings.drawingFilenameDateTime);
let fileType = "";
const filename = await utils.inputPrompt (
"Filename for new document",
"",
`${prefix} - ${timestamp}`,
[
{
caption: "Markdown",
action: ()=>{fileType="md";return;}
},
{
caption: "Excalidraw",
action: ()=>{fileType="ex";return;}
}
]
);
if(!filename || filename === "") return;
const filepath = activeFile.path.replace(activeFile.name,`${filename}.md`);
const file = await app.fileManager.createNewMarkdownFileFromLinktext(filepath);
if(file && fileType==="ex") {
await app.vault.modify(file,BLANK_DRAWING);
await new Promise(r => setTimeout(r, 100)); //wait for metadata cache to update, so file opens as excalidraw
}
const link = `[[${app.metadataCache.fileToLinktext(file,ea.targetView.file.path,true)}]]`;
ea.style.backgroundColor = "transparent";
ea.style.strokeColor = "rgba(70,130,180,0.05)"
ea.style.strokeWidth = 2;
ea.style.roughness = 0;
if(elements.lenght===1 && elements[0].type !== "text") {
ea.copyViewElementsToEAforEditing(elements);
ea.getElements()[0].link = link;
} else {
const b = ea.getBoundingBox(elements);
const id = ea.addEllipse(b.topX+b.width-5, b.topY, 5, 5);
ea.getElement(id).link = link;
ea.copyViewElementsToEAforEditing(elements);
ea.addToGroup(elements.map((e)=>e.id).concat([id]));
}
await ea.addElementsToView(false,true,true);
ea.selectElementsInView(ea.getElements());
if(openInCurrentPane) {
app.workspace.openLinkText(file.path,ea.targetView.file.path,false);
return;
}
ea.openFileInNewOrAdjacentLeaf(file);

View File

@@ -6,7 +6,7 @@ This script will prompt you for the title of the process step, then will create
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.21")) {
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.24")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
@@ -113,7 +113,7 @@ if(!isFirst) {
rect.backgroundColor = fromElement.backgroundColor;
rect.fillStyle = fromElement.fillStyle;
await ea.addElementsToView(false);
await ea.addElementsToView(false,false);
} else {
id = ea.addText(
0,
@@ -127,11 +127,7 @@ if(!isFirst) {
...fixWidth?{width: width}:null
}
);
await ea.addElementsToView(true);
await ea.addElementsToView(true,false);
}
const API = ea.getExcalidrawAPI();
st = API.getAppState();
st.selectedElementIds = {};
st.selectedElementIds[id] = true;
API.updateScene({appState: st});
ea.selectElementsInView([ea.getElement(id)]);

View File

@@ -130,10 +130,10 @@ for(const elements of groups) {
ea.copyViewElementsToEAforEditing(elements);
ea.addToGroup([id].concat(elements.map((el)=>el.id)));
ea.addElementsToView(false);
ea.reset();
}
await ea.addElementsToView(false,false);
function recalculateStartPointOfLine(line, el) {
const aX = el.x + el.width/2;
const bX = line.x + line.points[1][0];
@@ -178,4 +178,4 @@ function recalculateEndPointOfLine(line, el) {
if(intersectA.length > 0) {
line.points[line.points.length - 1] = [intersectA[0][0] - line.x, intersectA[0][1] - line.y];
}
}
}

View File

@@ -52,4 +52,4 @@ id = ea.addRect(
);
ea.copyViewElementsToEAforEditing(elements);
ea.addToGroup([id].concat(elements.map((el)=>el.id)));
ea.addElementsToView(false);
ea.addElementsToView(false,false);

View File

@@ -5,12 +5,36 @@ The script allows you to change the shape of selected Rectangles, Diamonds and E
```javascript
*/
const shapesDispaly=["○ ellipse","□ rectangle","◇ diamond"];
const shapes=["ellipse","rectangle","diamond"];
elements = ea.getViewSelectedElements().filter(el=>shapes.contains(el.type));
newShape = await utils.suggester(shapesDispaly, shapes);
if(!newShape) return;
const boxShapesDispaly=["○ ellipse","□ rectangle","◇ diamond"];
const boxShapes=["ellipse","rectangle","diamond"];
const lineShapesDispaly=["- line","⭢ arrow"];
const lineShapes=["line","arrow"];
elements.forEach(el=>el.type = newShape);
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView();
let editedElements = [];
let elements = ea.getViewSelectedElements().filter(el=>boxShapes.contains(el.type));
if (elements.length>0) {
newShape = await utils.suggester(boxShapesDispaly, boxShapes, "Change shape of 'box' type elements in selection");
if(newShape) {
editedElements = elements;
elements.forEach(el=>el.type = newShape);
}
}
elements = ea.getViewSelectedElements().filter(el=>lineShapes.contains(el.type));
if (elements.length>0) {
newShape = await utils.suggester(lineShapesDispaly, lineShapes, "Change shape of 'line' type elements in selection");
if(newShape) {
editedElements = editedElements.concat(elements);
elements.forEach((el)=>{
el.type = newShape;
if(newShape === "arrow") {
el.endArrowhead = "triangle";
}
});
}
}
ea.copyViewElementsToEAforEditing(editedElements);
ea.addElementsToView(false,false);

View File

@@ -78,4 +78,4 @@ ea.connectObjects(
numberOfPoints: linePoints
}
);
ea.addElementsToView(false,true,true);
ea.addElementsToView(false,false,true);

View File

@@ -0,0 +1,64 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-convert-freedraw-to-line.jpg)
Convert selected freedraw objects into editable lines. This will allow you to adjust your drawings by dragging line points and will also allow you to select shape fill in case of enclosed lines. You can adjust conversion point density in settings.
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.21")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
settings = ea.getScriptSettings();
//set default values on first run
if(!settings["Point density"]) {
settings = {
"Point density" : {
value: "7:1",
valueset: ["1:1","2:1","3:1","4:1","5:1","6:1","7:1","8:1","9:1","10:1","11:1"],
description: "A freedraw object has many points. Converting freedraw to a line with too many points will result in an impractical object that is hard to edit. This setting sepcifies how many points from freedraw should be averaged to form a point on the line"
},
};
ea.setScriptSettings(settings);
}
const scale = settings["Point density"].value;
const setSize = parseInt(scale.substring(0,scale.indexOf(":")));
const elements = ea.getViewSelectedElements().filter(el=>el.type==="freedraw");
if(elements.length === 0) {
new Notice("No freedraw object is selected");
}
ea.style.roughness=0;
ea.style.strokeSharpness="round";
elements.forEach((el)=>{
points = [];
points.push(el.points[0]);
for(i=1;i<el.points.length;i+=setSize) {
point = [0,0];
count = 0;
for(p of el.points.slice(i,i+setSize)) {
point = [ point[0]+p[0] , point[1]+p[1] ];
count++;
}
point = [point[0]/count,point[1]/count];
points.push(point);
}
const lineId = ea.addLine(points);
const line = ea.getElement(lineId);
line.strokeWidth = el.strokeWidth*3;
line.strokeColor = el.strokeColor;
line.width = el.width;
line.height = el.height;
line.x = el.x;
line.y = el.y;
});
ea.deleteViewElements(elements);
await ea.addElementsToView(false,false,true);
ea.selectElementsInView(ea.getElements());

View File

@@ -48,4 +48,4 @@ elements.forEach((el)=>{
el.containerId = id;
});
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView();
ea.addElementsToView(false,false);

View File

@@ -40,7 +40,7 @@ for (const el of ea.getElements()) {
el.backgroundColor = "#" + rgbToHexString(newRgb);
}
}
ea.addElementsToView(false, false);
await ea.addElementsToView(false, false);
function rgbToHexString(args) {
const integer =

View File

@@ -1,56 +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();
/*
![](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);
await ea.addElementsToView(false,false);

View File

@@ -1,146 +1,146 @@
/*
![](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 allIndividualArrows = ea.getMaximumGroups(ea.getViewElements())
.reduce((result, group) => (group.length === 1 && (group[0].type === 'arrow')) ?
[...result, group[0]] : result, []);
const groupWidths = topGroups
.map((g) => {
if(g.length === 1 && (g[0].type === 'arrow' || g[0].type === 'line')) {
// ignore individual lines
return { minLeft: 0, maxRight: 0 };
}
return 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];
const rectLeft = rect.x;
const rectTop = rect.y;
const rectRight = rect.x + rect.width;
const rectBottom = rect.y + rect.height;
rect.x = rect.x + perRectDistance * j - perRectDistance / 2;
rect.width += perRectDistance;
const textsWithRect = texts.filter(text => text.x >= rectLeft && text.x <= rectRight
&& text.y >= rectTop && text.y <= rectBottom);
for(const text of textsWithRect) {
text.x = text.x + perRectDistance * j;
}
// recalculate the position of the points
const startBindingLines = allIndividualArrows.filter(el => (el.startBinding||{}).elementId === rect.id);
for(startBindingLine of startBindingLines) {
recalculateStartPointOfLine(startBindingLine, rect);
}
const endBindingLines = allIndividualArrows.filter(el => (el.endBinding||{}).elementId === rect.id);
for(endBindingLine of endBindingLines) {
recalculateEndPointOfLine(endBindingLine, rect);
}
}
}
}
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView(false, false);
function recalculateStartPointOfLine(line, el) {
const aX = el.x + el.width/2;
const bX = line.x + line.points[1][0];
const aY = el.y + el.height/2;
const bY = line.y + line.points[1][1];
line.startBinding.gap = 8;
line.startBinding.focus = 0;
const intersectA = ea.intersectElementWithLine(
el,
[bX, bY],
[aX, aY],
line.startBinding.gap
);
if(intersectA.length > 0) {
line.points[0] = [0, 0];
for(var i = 1; i<line.points.length; i++) {
line.points[i][0] -= intersectA[0][0] - line.x;
line.points[i][1] -= intersectA[0][1] - line.y;
}
line.x = intersectA[0][0];
line.y = intersectA[0][1];
}
}
function recalculateEndPointOfLine(line, el) {
const aX = el.x + el.width/2;
const bX = line.x + line.points[line.points.length-2][0];
const aY = el.y + el.height/2;
const bY = line.y + line.points[line.points.length-2][1];
line.endBinding.gap = 8;
line.endBinding.focus = 0;
const intersectA = ea.intersectElementWithLine(
el,
[bX, bY],
[aX, aY],
line.endBinding.gap
);
if(intersectA.length > 0) {
line.points[line.points.length - 1] = [intersectA[0][0] - line.x, intersectA[0][1] - line.y];
}
/*
![](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 allIndividualArrows = ea.getMaximumGroups(ea.getViewElements())
.reduce((result, group) => (group.length === 1 && (group[0].type === 'arrow')) ?
[...result, group[0]] : result, []);
const groupWidths = topGroups
.map((g) => {
if(g.length === 1 && (g[0].type === 'arrow' || g[0].type === 'line')) {
// ignore individual lines
return { minLeft: 0, maxRight: 0 };
}
return 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];
const rectLeft = rect.x;
const rectTop = rect.y;
const rectRight = rect.x + rect.width;
const rectBottom = rect.y + rect.height;
rect.x = rect.x + perRectDistance * j - perRectDistance / 2;
rect.width += perRectDistance;
const textsWithRect = texts.filter(text => text.x >= rectLeft && text.x <= rectRight
&& text.y >= rectTop && text.y <= rectBottom);
for(const text of textsWithRect) {
text.x = text.x + perRectDistance * j;
}
// recalculate the position of the points
const startBindingLines = allIndividualArrows.filter(el => (el.startBinding||{}).elementId === rect.id);
for(startBindingLine of startBindingLines) {
recalculateStartPointOfLine(startBindingLine, rect);
}
const endBindingLines = allIndividualArrows.filter(el => (el.endBinding||{}).elementId === rect.id);
for(endBindingLine of endBindingLines) {
recalculateEndPointOfLine(endBindingLine, rect);
}
}
}
}
ea.copyViewElementsToEAforEditing(elements);
await ea.addElementsToView(false, false);
function recalculateStartPointOfLine(line, el) {
const aX = el.x + el.width/2;
const bX = line.x + line.points[1][0];
const aY = el.y + el.height/2;
const bY = line.y + line.points[1][1];
line.startBinding.gap = 8;
line.startBinding.focus = 0;
const intersectA = ea.intersectElementWithLine(
el,
[bX, bY],
[aX, aY],
line.startBinding.gap
);
if(intersectA.length > 0) {
line.points[0] = [0, 0];
for(var i = 1; i<line.points.length; i++) {
line.points[i][0] -= intersectA[0][0] - line.x;
line.points[i][1] -= intersectA[0][1] - line.y;
}
line.x = intersectA[0][0];
line.y = intersectA[0][1];
}
}
function recalculateEndPointOfLine(line, el) {
const aX = el.x + el.width/2;
const bX = line.x + line.points[line.points.length-2][0];
const aY = el.y + el.height/2;
const bY = line.y + line.points[line.points.length-2][1];
line.endBinding.gap = 8;
line.endBinding.focus = 0;
const intersectA = ea.intersectElementWithLine(
el,
[bX, bY],
[aX, aY],
line.endBinding.gap
);
if(intersectA.length > 0) {
line.points[line.points.length - 1] = [intersectA[0][0] - line.x, intersectA[0][1] - line.y];
}
}

View File

@@ -1,133 +1,133 @@
/*
![](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 allIndividualArrows = ea.getMaximumGroups(ea.getViewElements())
.reduce((result, group) => (group.length === 1 && (group[0].type === 'arrow' || group[0].type === 'line')) ?
[...result, group[0]] : result, []);
const groupWidths = topGroups
.map((g) => {
if(g.length === 1 && (g[0].type === 'arrow' || g[0].type === 'line')) {
// ignore individual lines
return { minLeft: 0, maxRight: 0 };
}
return 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 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;
// recalculate the position of the points
const startBindingLines = allIndividualArrows.filter(el => (el.startBinding||{}).elementId === rect.id);
for(startBindingLine of startBindingLines) {
recalculateStartPointOfLine(startBindingLine, rect);
}
const endBindingLines = allIndividualArrows.filter(el => (el.endBinding||{}).elementId === rect.id);
for(endBindingLine of endBindingLines) {
recalculateEndPointOfLine(endBindingLine, rect);
}
}
}
}
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView(false, false);
function recalculateStartPointOfLine(line, el) {
const aX = el.x + el.width/2;
const bX = line.x + line.points[1][0];
const aY = el.y + el.height/2;
const bY = line.y + line.points[1][1];
line.startBinding.gap = 8;
line.startBinding.focus = 0;
const intersectA = ea.intersectElementWithLine(
el,
[bX, bY],
[aX, aY],
line.startBinding.gap
);
if(intersectA.length > 0) {
line.points[0] = [0, 0];
for(var i = 1; i<line.points.length; i++) {
line.points[i][0] -= intersectA[0][0] - line.x;
line.points[i][1] -= intersectA[0][1] - line.y;
}
line.x = intersectA[0][0];
line.y = intersectA[0][1];
}
}
function recalculateEndPointOfLine(line, el) {
const aX = el.x + el.width/2;
const bX = line.x + line.points[line.points.length-2][0];
const aY = el.y + el.height/2;
const bY = line.y + line.points[line.points.length-2][1];
line.endBinding.gap = 8;
line.endBinding.focus = 0;
const intersectA = ea.intersectElementWithLine(
el,
[bX, bY],
[aX, aY],
line.endBinding.gap
);
if(intersectA.length > 0) {
line.points[line.points.length - 1] = [intersectA[0][0] - line.x, intersectA[0][1] - line.y];
}
/*
![](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 allIndividualArrows = ea.getMaximumGroups(ea.getViewElements())
.reduce((result, group) => (group.length === 1 && (group[0].type === 'arrow' || group[0].type === 'line')) ?
[...result, group[0]] : result, []);
const groupWidths = topGroups
.map((g) => {
if(g.length === 1 && (g[0].type === 'arrow' || g[0].type === 'line')) {
// ignore individual lines
return { minLeft: 0, maxRight: 0 };
}
return 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 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;
// recalculate the position of the points
const startBindingLines = allIndividualArrows.filter(el => (el.startBinding||{}).elementId === rect.id);
for(startBindingLine of startBindingLines) {
recalculateStartPointOfLine(startBindingLine, rect);
}
const endBindingLines = allIndividualArrows.filter(el => (el.endBinding||{}).elementId === rect.id);
for(endBindingLine of endBindingLines) {
recalculateEndPointOfLine(endBindingLine, rect);
}
}
}
}
ea.copyViewElementsToEAforEditing(elements);
await ea.addElementsToView(false, false);
function recalculateStartPointOfLine(line, el) {
const aX = el.x + el.width/2;
const bX = line.x + line.points[1][0];
const aY = el.y + el.height/2;
const bY = line.y + line.points[1][1];
line.startBinding.gap = 8;
line.startBinding.focus = 0;
const intersectA = ea.intersectElementWithLine(
el,
[bX, bY],
[aX, aY],
line.startBinding.gap
);
if(intersectA.length > 0) {
line.points[0] = [0, 0];
for(var i = 1; i<line.points.length; i++) {
line.points[i][0] -= intersectA[0][0] - line.x;
line.points[i][1] -= intersectA[0][1] - line.y;
}
line.x = intersectA[0][0];
line.y = intersectA[0][1];
}
}
function recalculateEndPointOfLine(line, el) {
const aX = el.x + el.width/2;
const bX = line.x + line.points[line.points.length-2][0];
const aY = el.y + el.height/2;
const bY = line.y + line.points[line.points.length-2][1];
line.endBinding.gap = 8;
line.endBinding.focus = 0;
const intersectA = ea.intersectElementWithLine(
el,
[bX, bY],
[aX, aY],
line.endBinding.gap
);
if(intersectA.length > 0) {
line.points[line.points.length - 1] = [intersectA[0][0] - line.x, intersectA[0][1] - line.y];
}
}

View File

@@ -1,146 +1,146 @@
/*
![](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 allIndividualArrows = ea.getMaximumGroups(ea.getViewElements())
.reduce((result, group) => (group.length === 1 && (group[0].type === 'arrow' || group[0].type === 'line')) ?
[...result, group[0]] : result, []);
const groupHeights = topGroups
.map((g) => {
if(g.length === 1 && (g[0].type === 'arrow' || g[0].type === 'line')) {
// ignore individual lines
return { minTop: 0, maxBottom: 0 };
}
return 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];
const rectLeft = rect.x;
const rectTop = rect.y;
const rectRight = rect.x + rect.width;
const rectBottom = rect.y + rect.height;
rect.y = rect.y + perRectDistance * j - perRectDistance / 2;
rect.height += perRectDistance;
const textsWithRect = texts.filter(text => text.x >= rectLeft && text.x <= rectRight
&& text.y >= rectTop && text.y <= rectBottom);
for(const text of textsWithRect) {
text.y = text.y + perRectDistance * j;
}
// recalculate the position of the points
const startBindingLines = allIndividualArrows.filter(el => (el.startBinding||{}).elementId === rect.id);
for(startBindingLine of startBindingLines) {
recalculateStartPointOfLine(startBindingLine, rect);
}
const endBindingLines = allIndividualArrows.filter(el => (el.endBinding||{}).elementId === rect.id);
for(endBindingLine of endBindingLines) {
recalculateEndPointOfLine(endBindingLine, rect);
}
}
}
}
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView(false, false);
function recalculateStartPointOfLine(line, el) {
const aX = el.x + el.width/2;
const bX = line.x + line.points[1][0];
const aY = el.y + el.height/2;
const bY = line.y + line.points[1][1];
line.startBinding.gap = 8;
line.startBinding.focus = 0;
const intersectA = ea.intersectElementWithLine(
el,
[bX, bY],
[aX, aY],
line.startBinding.gap
);
if(intersectA.length > 0) {
line.points[0] = [0, 0];
for(var i = 1; i<line.points.length; i++) {
line.points[i][0] -= intersectA[0][0] - line.x;
line.points[i][1] -= intersectA[0][1] - line.y;
}
line.x = intersectA[0][0];
line.y = intersectA[0][1];
}
}
function recalculateEndPointOfLine(line, el) {
const aX = el.x + el.width/2;
const bX = line.x + line.points[line.points.length-2][0];
const aY = el.y + el.height/2;
const bY = line.y + line.points[line.points.length-2][1];
line.endBinding.gap = 8;
line.endBinding.focus = 0;
const intersectA = ea.intersectElementWithLine(
el,
[bX, bY],
[aX, aY],
line.endBinding.gap
);
if(intersectA.length > 0) {
line.points[line.points.length - 1] = [intersectA[0][0] - line.x, intersectA[0][1] - line.y];
}
/*
![](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 allIndividualArrows = ea.getMaximumGroups(ea.getViewElements())
.reduce((result, group) => (group.length === 1 && (group[0].type === 'arrow' || group[0].type === 'line')) ?
[...result, group[0]] : result, []);
const groupHeights = topGroups
.map((g) => {
if(g.length === 1 && (g[0].type === 'arrow' || g[0].type === 'line')) {
// ignore individual lines
return { minTop: 0, maxBottom: 0 };
}
return 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];
const rectLeft = rect.x;
const rectTop = rect.y;
const rectRight = rect.x + rect.width;
const rectBottom = rect.y + rect.height;
rect.y = rect.y + perRectDistance * j - perRectDistance / 2;
rect.height += perRectDistance;
const textsWithRect = texts.filter(text => text.x >= rectLeft && text.x <= rectRight
&& text.y >= rectTop && text.y <= rectBottom);
for(const text of textsWithRect) {
text.y = text.y + perRectDistance * j;
}
// recalculate the position of the points
const startBindingLines = allIndividualArrows.filter(el => (el.startBinding||{}).elementId === rect.id);
for(startBindingLine of startBindingLines) {
recalculateStartPointOfLine(startBindingLine, rect);
}
const endBindingLines = allIndividualArrows.filter(el => (el.endBinding||{}).elementId === rect.id);
for(endBindingLine of endBindingLines) {
recalculateEndPointOfLine(endBindingLine, rect);
}
}
}
}
ea.copyViewElementsToEAforEditing(elements);
await ea.addElementsToView(false, false);
function recalculateStartPointOfLine(line, el) {
const aX = el.x + el.width/2;
const bX = line.x + line.points[1][0];
const aY = el.y + el.height/2;
const bY = line.y + line.points[1][1];
line.startBinding.gap = 8;
line.startBinding.focus = 0;
const intersectA = ea.intersectElementWithLine(
el,
[bX, bY],
[aX, aY],
line.startBinding.gap
);
if(intersectA.length > 0) {
line.points[0] = [0, 0];
for(var i = 1; i<line.points.length; i++) {
line.points[i][0] -= intersectA[0][0] - line.x;
line.points[i][1] -= intersectA[0][1] - line.y;
}
line.x = intersectA[0][0];
line.y = intersectA[0][1];
}
}
function recalculateEndPointOfLine(line, el) {
const aX = el.x + el.width/2;
const bX = line.x + line.points[line.points.length-2][0];
const aY = el.y + el.height/2;
const bY = line.y + line.points[line.points.length-2][1];
line.endBinding.gap = 8;
line.endBinding.focus = 0;
const intersectA = ea.intersectElementWithLine(
el,
[bX, bY],
[aX, aY],
line.endBinding.gap
);
if(intersectA.length > 0) {
line.points[line.points.length - 1] = [intersectA[0][0] - line.x, intersectA[0][1] - line.y];
}
}

View File

@@ -1,131 +1,131 @@
/*
![](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 allLines = ea.getViewElements().filter(el => el.type === 'arrow' || el.type === 'line');
const allIndividualArrows = ea.getMaximumGroups(ea.getViewElements())
.reduce((result, group) => (group.length === 1 && (group[0].type === 'arrow' || group[0].type === 'line')) ?
[...result, group[0]] : result, []);
const groupHeights = topGroups
.map((g) => {
if(g.length === 1 && (g[0].type === 'arrow' || g[0].type === 'line')) {
// ignore individual lines
return { minTop: 0, maxBottom: 0 };
}
return 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 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;
// recalculate the position of the points
const startBindingLines = allIndividualArrows.filter(el => (el.startBinding||{}).elementId === rect.id);
for(startBindingLine of startBindingLines) {
recalculateStartPointOfLine(startBindingLine, rect);
}
const endBindingLines = allIndividualArrows.filter(el => (el.endBinding||{}).elementId === rect.id);
for(endBindingLine of endBindingLines) {
recalculateEndPointOfLine(endBindingLine, rect);
}
}
}
}
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView(false, false);
function recalculateStartPointOfLine(line, el) {
const aX = el.x + el.width/2;
const bX = line.x + line.points[1][0];
const aY = el.y + el.height/2;
const bY = line.y + line.points[1][1];
line.startBinding.gap = 8;
line.startBinding.focus = 0;
const intersectA = ea.intersectElementWithLine(
el,
[bX, bY],
[aX, aY],
line.startBinding.gap
);
if(intersectA.length > 0) {
line.points[0] = [0, 0];
for(var i = 1; i<line.points.length; i++) {
line.points[i][0] -= intersectA[0][0] - line.x;
line.points[i][1] -= intersectA[0][1] - line.y;
}
line.x = intersectA[0][0];
line.y = intersectA[0][1];
}
}
function recalculateEndPointOfLine(line, el) {
const aX = el.x + el.width/2;
const bX = line.x + line.points[line.points.length-2][0];
const aY = el.y + el.height/2;
const bY = line.y + line.points[line.points.length-2][1];
line.endBinding.gap = 8;
line.endBinding.focus = 0;
const intersectA = ea.intersectElementWithLine(
el,
[bX, bY],
[aX, aY],
line.endBinding.gap
);
if(intersectA.length > 0) {
line.points[line.points.length - 1] = [intersectA[0][0] - line.x, intersectA[0][1] - line.y];
}
/*
![](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 allLines = ea.getViewElements().filter(el => el.type === 'arrow' || el.type === 'line');
const allIndividualArrows = ea.getMaximumGroups(ea.getViewElements())
.reduce((result, group) => (group.length === 1 && (group[0].type === 'arrow' || group[0].type === 'line')) ?
[...result, group[0]] : result, []);
const groupHeights = topGroups
.map((g) => {
if(g.length === 1 && (g[0].type === 'arrow' || g[0].type === 'line')) {
// ignore individual lines
return { minTop: 0, maxBottom: 0 };
}
return 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 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;
// recalculate the position of the points
const startBindingLines = allIndividualArrows.filter(el => (el.startBinding||{}).elementId === rect.id);
for(startBindingLine of startBindingLines) {
recalculateStartPointOfLine(startBindingLine, rect);
}
const endBindingLines = allIndividualArrows.filter(el => (el.endBinding||{}).elementId === rect.id);
for(endBindingLine of endBindingLines) {
recalculateEndPointOfLine(endBindingLine, rect);
}
}
}
}
ea.copyViewElementsToEAforEditing(elements);
await ea.addElementsToView(false, false);
function recalculateStartPointOfLine(line, el) {
const aX = el.x + el.width/2;
const bX = line.x + line.points[1][0];
const aY = el.y + el.height/2;
const bY = line.y + line.points[1][1];
line.startBinding.gap = 8;
line.startBinding.focus = 0;
const intersectA = ea.intersectElementWithLine(
el,
[bX, bY],
[aX, aY],
line.startBinding.gap
);
if(intersectA.length > 0) {
line.points[0] = [0, 0];
for(var i = 1; i<line.points.length; i++) {
line.points[i][0] -= intersectA[0][0] - line.x;
line.points[i][1] -= intersectA[0][1] - line.y;
}
line.x = intersectA[0][0];
line.y = intersectA[0][1];
}
}
function recalculateEndPointOfLine(line, el) {
const aX = el.x + el.width/2;
const bX = line.x + line.points[line.points.length-2][0];
const aY = el.y + el.height/2;
const bY = line.y + line.points[line.points.length-2][1];
line.endBinding.gap = 8;
line.endBinding.focus = 0;
const intersectA = ea.intersectElementWithLine(
el,
[bX, bY],
[aX, aY],
line.endBinding.gap
);
if(intersectA.length > 0) {
line.points[line.points.length - 1] = [intersectA[0][0] - line.x, intersectA[0][1] - line.y];
}
}

View File

@@ -69,4 +69,4 @@ for(var i=0; i<groups.length; i++) {
}
}
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView(false, false);
await ea.addElementsToView(false, false);

View File

@@ -1,126 +1,126 @@
/*
![](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-inner-distance.png)
This script arranges selected elements and groups with a fixed inner distance.
Tips: You can use the `Box Selected Elements` and `Dimensions` scripts to create rectangles of the desired size, then use the `Change shape of selected elements` script to convert the rectangles to ellipses, and then use the `Fixed inner distance` script regains a desired inner distance.
Inspiration: #394
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.21")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
settings = ea.getScriptSettings();
//set default values on first run
if(!settings["Default distance"]) {
settings = {
"Prompt for distance?": true,
"Default distance" : {
value: 10,
description: "Fixed horizontal distance between centers"
},
"Remember last distance?": false
};
ea.setScriptSettings(settings);
}
let distanceStr = settings["Default distance"].value.toString();
const rememberLastDistance = settings["Remember last distance?"];
if(settings["Prompt for distance?"]) {
distanceStr = await utils.inputPrompt("distance?","number",distanceStr);
}
const borders = ["top", "bottom", "left", "right"];
const fromBorder = await utils.suggester(borders, borders, "from border?");
if(!fromBorder) {
return;
}
const distance = parseInt(distanceStr);
if(isNaN(distance)) {
return;
}
if(rememberLastDistance) {
settings["Default distance"].value = distance;
ea.setScriptSettings(settings);
}
const elements=ea.getViewSelectedElements();
const topGroups = ea.getMaximumGroups(elements)
.filter(els => !(els.length === 1 && els[0].type ==="arrow")); // ignore individual arrows
if(topGroups.length <= 1) {
new Notice("At least 2 or more elements or groups should be selected.");
return;
}
if(fromBorder === 'top') {
const groups = topGroups.sort((lha,rha) => Math.min(...lha.map(t => t.y)) - Math.min(...rha.map(t => t.y)));
const firstGroupTop = Math.min(...groups[0].map(el => el.y));
for(var i=0; i<groups.length; i++) {
if(i > 0) {
const curGroup = groups[i];
const moveDistance = distance * i;
for(const curEl of curGroup) {
curEl.y = firstGroupTop + moveDistance;
}
}
}
}
else if(fromBorder === 'bottom') {
const groups = topGroups.sort((lha,rha) => Math.min(...lha.map(t => t.y + t.height)) - Math.min(...rha.map(t => t.y + t.height))).reverse();
const firstGroupBottom = Math.max(...groups[0].map(el => el.y + el.height));
for(var i=0; i<groups.length; i++) {
if(i > 0) {
const curGroup = groups[i];
const moveDistance = distance * i;
for(const curEl of curGroup) {
curEl.y = firstGroupBottom - moveDistance - curEl.height;
}
}
}
}
else if(fromBorder === 'left') {
const groups = topGroups.sort((lha,rha) => Math.min(...lha.map(t => t.x)) - Math.min(...rha.map(t => t.x)));
const firstGroupLeft = Math.min(...groups[0].map(el => el.x));
for(var i=0; i<groups.length; i++) {
if(i > 0) {
const curGroup = groups[i];
const moveDistance = distance * i;
for(const curEl of curGroup) {
curEl.x = firstGroupLeft + moveDistance;
}
}
}
}
else if(fromBorder === 'right') {
const groups = topGroups.sort((lha,rha) => Math.min(...lha.map(t => t.x + t.width)) - Math.min(...rha.map(t => t.x + t.width))).reverse();
const firstGroupRight = Math.max(...groups[0].map(el => el.x + el.width));
for(var i=0; i<groups.length; i++) {
if(i > 0) {
const curGroup = groups[i];
const moveDistance = distance * i;
for(const curEl of curGroup) {
curEl.x = firstGroupRight - moveDistance - curEl.width;
}
}
}
}
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView(false, false);
/*
![](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-inner-distance.png)
This script arranges selected elements and groups with a fixed inner distance.
Tips: You can use the `Box Selected Elements` and `Dimensions` scripts to create rectangles of the desired size, then use the `Change shape of selected elements` script to convert the rectangles to ellipses, and then use the `Fixed inner distance` script regains a desired inner distance.
Inspiration: #394
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.21")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
settings = ea.getScriptSettings();
//set default values on first run
if(!settings["Default distance"]) {
settings = {
"Prompt for distance?": true,
"Default distance" : {
value: 10,
description: "Fixed horizontal distance between centers"
},
"Remember last distance?": false
};
ea.setScriptSettings(settings);
}
let distanceStr = settings["Default distance"].value.toString();
const rememberLastDistance = settings["Remember last distance?"];
if(settings["Prompt for distance?"]) {
distanceStr = await utils.inputPrompt("distance?","number",distanceStr);
}
const borders = ["top", "bottom", "left", "right"];
const fromBorder = await utils.suggester(borders, borders, "from border?");
if(!fromBorder) {
return;
}
const distance = parseInt(distanceStr);
if(isNaN(distance)) {
return;
}
if(rememberLastDistance) {
settings["Default distance"].value = distance;
ea.setScriptSettings(settings);
}
const elements=ea.getViewSelectedElements();
const topGroups = ea.getMaximumGroups(elements)
.filter(els => !(els.length === 1 && els[0].type ==="arrow")); // ignore individual arrows
if(topGroups.length <= 1) {
new Notice("At least 2 or more elements or groups should be selected.");
return;
}
if(fromBorder === 'top') {
const groups = topGroups.sort((lha,rha) => Math.min(...lha.map(t => t.y)) - Math.min(...rha.map(t => t.y)));
const firstGroupTop = Math.min(...groups[0].map(el => el.y));
for(var i=0; i<groups.length; i++) {
if(i > 0) {
const curGroup = groups[i];
const moveDistance = distance * i;
for(const curEl of curGroup) {
curEl.y = firstGroupTop + moveDistance;
}
}
}
}
else if(fromBorder === 'bottom') {
const groups = topGroups.sort((lha,rha) => Math.min(...lha.map(t => t.y + t.height)) - Math.min(...rha.map(t => t.y + t.height))).reverse();
const firstGroupBottom = Math.max(...groups[0].map(el => el.y + el.height));
for(var i=0; i<groups.length; i++) {
if(i > 0) {
const curGroup = groups[i];
const moveDistance = distance * i;
for(const curEl of curGroup) {
curEl.y = firstGroupBottom - moveDistance - curEl.height;
}
}
}
}
else if(fromBorder === 'left') {
const groups = topGroups.sort((lha,rha) => Math.min(...lha.map(t => t.x)) - Math.min(...rha.map(t => t.x)));
const firstGroupLeft = Math.min(...groups[0].map(el => el.x));
for(var i=0; i<groups.length; i++) {
if(i > 0) {
const curGroup = groups[i];
const moveDistance = distance * i;
for(const curEl of curGroup) {
curEl.x = firstGroupLeft + moveDistance;
}
}
}
}
else if(fromBorder === 'right') {
const groups = topGroups.sort((lha,rha) => Math.min(...lha.map(t => t.x + t.width)) - Math.min(...rha.map(t => t.x + t.width))).reverse();
const firstGroupRight = Math.max(...groups[0].map(el => el.x + el.width));
for(var i=0; i<groups.length; i++) {
if(i > 0) {
const curGroup = groups[i];
const moveDistance = distance * i;
for(const curEl of curGroup) {
curEl.x = firstGroupRight - moveDistance - curEl.width;
}
}
}
}
ea.copyViewElementsToEAforEditing(elements);
await ea.addElementsToView(false, false);

View File

@@ -64,4 +64,4 @@ for(var i=0; i<groups.length; i++) {
}
}
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView(false, false);
await ea.addElementsToView(false, false);

View File

@@ -71,4 +71,4 @@ for(var i=0; i<groups.length; i++) {
}
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView(false, false);
await ea.addElementsToView(false, false);

View File

@@ -64,4 +64,4 @@ for(var i=0; i<groups.length; i++) {
}
}
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView(false, false);
await ea.addElementsToView(false, false);

View File

@@ -40,7 +40,7 @@ for (const el of ea.getElements()) {
el.backgroundColor = "#" + rgbToHexString(newRgb);
}
}
ea.addElementsToView(false, false);
await ea.addElementsToView(false, false);
function rgbToHexString(args) {
const integer =

View File

@@ -64,7 +64,7 @@ ea.getElements().forEach((el)=>{
}
}
});
ea.addElementsToView(false, false);
await ea.addElementsToView(false, false);
function colorNameToHex(color) {
const colors = {

View File

@@ -25,7 +25,7 @@ for(const arrow of selectedIndividualArrows) {
}
ea.copyViewElementsToEAforEditing(selectedIndividualArrows);
ea.addElementsToView();
await ea.addElementsToView(false,false);
function recalculateStartPointOfLine(line, el, elB) {
const aX = el.x + el.width/2;

View File

@@ -16,7 +16,7 @@ https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.h
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.21")) {
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.24")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
@@ -110,12 +110,8 @@ 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);
ea.selectElementsInView([ea.getElement(id)]);
ea.getExcalidrawAPI().zoomToFit(ea.getViewSelectedElements(),1);
//utility function
function notice(message) {

View File

@@ -0,0 +1,27 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-organic-line.jpg)
Converts selected freedraw lines such that pencil pressure will decrease from maximum to minimum from the beginning of the line to its end. The resulting line is placed at the back of the layers, under all other items. Helpful when drawing organic mindmaps.
```javascript
*/
let elements = ea.getViewSelectedElements().filter((el)=>["freedraw","line","arrow"].includes(el.type));
if(elements.length === 0) {
elements = ea.getViewSelectedElements();
const len = elements.length;
if(len === 0 || ["freedraw","line","arrow"].includes(elements[len].type)) {
return;
}
elements = [elements[len]];
}
elements.forEach((el)=>{
el.simulatePressure = false;
el.type = "freedraw";
el.pressures = [];
const len = el.points.length;
for(i=0;i<len;i++)
el.pressures.push((len-i)/len);
});
ea.copyViewElementsToEAforEditing(elements);
await ea.addElementsToView(false,false);
elements.forEach((el)=>ea.moveViewElementToZIndex(el.id,0));

View File

@@ -15,18 +15,19 @@ Open the script you are interested in and save it to your Obsidian Vault includi
|Title|Description|Icon|Contributor|
|----|----|----|----|
|[Add Connector Point](Add%20Connector%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)|
|[Add Link and Open Page](Add%20Link%20and%20Open%20Page.md)|Prompt for a file in the vault. Add a link above or below (based on settings) the selected element, to the selected file. If no file is selected then the script creates a new file following the default filename defined for excalidraw embeds.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-link-and-open.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Add Next Step in Process](Add%20Next%20Step%20in%20Process.md)|This script will prompt you for the title of the process step, then will create a stick note with the text. If an element is selected then the script will connect this new step with an arrow to the previous step (the selected element). If no element is selected, then the script assumes this is the first step in the process and will only output the sticky note with the text that was entered.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-process-step.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Add Link to Existing File and Open](Add%20Link%20to%20Existing%20File%20and%20Open.md)|Prompts for a file from the vault. Adds a link to the selected element pointing to the selected file. You can control in settings to open the file in the current active pane or an adjacent pane.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-link-and-open.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Add Link to New Page and Open](Add%20Link%20and%20Open%20Page.md)|Prompts for filename. Offers option to create and open a new Markdown or Excalidraw document. Adds link pointing to the new file, to the selected objects in the drawing. You can control in settings to open the file in the current active pane or an adjacent pane.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-link-to-new-page-and-pen.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Add Next Step in Process](Add%20Link%20to%20New%20Page%20and%20Open.md)|This script will prompt you for the title of the process step, then will create a stick note with the text. If an element is selected then the script will connect this new step with an arrow to the previous step (the selected element). If no element is selected, then the script assumes this is the first step in the process and will only output the sticky note with the text that was entered.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-process-step.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Box Each Selected Groups](Box%20Each%20Selected%20Groups.md)|This script will add encapsulating boxes around each of the currently selected groups in Excalidraw.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-box-each-selected-groups.png)|[@1-2-3](https://github.com/1-2-3)|
|[Box Selected Elements](Box%20Selected%20Elements.md)|This script will add an encapsulating box around the currently selected elements in Excalidraw.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-box-elements.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[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)|
|[Change shape of selected elements](Change%20shape%20of%20selected%20elements.md)|The script allows you to change the shape of selected Rectangles, Diamonds and Ellipses|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-change-shape.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[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 freedraw to line](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Convert%20freedraw%20to%20line.md)|Convert selected freedraw objects into editable lines. This will allow you to adjust your drawings by dragging line points and will also allow you to select shape fill in case of enclosed lines. You can adjust conversion point density in settings|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-convert-freedraw-to-line.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Convert selected text elements to sticky notes](Convert%20selected%20text%20elements%20to%20sticky%20notes.md)|Converts selected plain text elements to sticky notes with transparent background and transparent stroke color. Essentially converts text element into a wrappable format.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-textelement-to-transparent-stickynote.png)|[@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)|
|[Copy Selected Element Styles to Global](Copy%20Selected%20Element%20Styles%20to%20Global)|This script will copy styles of any selected element into Excalidraw's global styles.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-copy-selected-element-styles-to-global.png)|[@1-2-3](https://github.com/1-2-3)|
|[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)|
|[Set Dimensions](Set%20Dimensions.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)|
@@ -37,16 +38,23 @@ Open the script you are interested in and save it to your Obsidian Vault includi
|[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 between centers](Fixed%20vertical%20distance%20between%20centers.md)|This script arranges the selected elements vertically with a fixed center spacing.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fixed-vertical-distance-between-centers.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)|
|[Set Font Family](Set%20Font%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)|
|[Set Grid](Set%20Grid.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)|
|[Set stroke width of selected elements](Set%20Stroke%20Width%20of%20Selected%20Elements.md)|This script will set the stroke width of selected elements. This is helpful, for example, when you scale freedraw sketches and want to reduce or increase their line width.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-stroke-width.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Normalize Selected Arrows](Normalize%20Selected%20Arrows.md)|This script will reset the start and end positions of the selected arrows. The arrow will point to the center of the connected box and will have a gap of 8px from the box.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-normalize-selected-arrows.png)|[@1-2-3](https://github.com/1-2-3)|
|[OCR - Optical Character Recognition](OCR%20-%20Optical%20Character%20Recognition.md)|The script will 1) send the selected image file to [taskbone.com](https://taskbone.com) to exctract the text from the image, and 2) will add the text to your drawing as a text element.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-ocr.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Organic Line](Organic%20Line.md)|Converts selected freedraw lines such that pencil pressure will decrease from maximum to minimum from the beginning of the line to its end. The resulting line is placed at the back of the layers, under all other items. Helpful when drawing organic mindmaps.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-organic-line.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Repeat Elements](Repeat%20Elements.md)|This script will detect the difference between 2 selected elements, including position, size, angle, stroke and background color, and create several elements that repeat these differences based on the number of repetitions entered by the user.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-repeat-elements.png)|[@1-2-3](https://github.com/1-2-3)|
|[Reverse arrows](Reverse%20arrows.md)|Reverse the direction of **arrows** within the scope of selected elements.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-reverse-arrow.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Scribble Helper](Scribble%20Helper.md)|iOS scribble helper for better handwriting experience with text elements. If no elements are selected then the creates a text element at pointer position and you can use the edit box to modify the text with scribble. If a text element is selected then opens the input prompt where you can modify this text with scribble.|![]('https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-scribble-helper.jpg')|[@zsviczian](https://github.com/zsviczian)|
|[Select Elements of Type](Select%20Elements%20of%20Type.md)|Prompts you with a list of the different element types in the active image. Only elements of the selected type will be selected on the canvas. If nothing is selected when running the script, then the script will process all the elements on the canvas. If some elements are selected when the script is executed, then the script will only process the selected elements.<br>The script is useful when, for example, you want to bring to front all the arrows, or want to change the color of all the text elements, etc.|![]('https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-select-element-of-type.jpg')|[@zsviczian](https://github.com/zsviczian)|
|[Set background color of unclosed line object by adding a shadow clone](Set%20background%20color%20of%20unclosed%20line%20object%20by%20adding%20a%20shadow%20clone.md)|Use this script to set the background color of unclosed (i.e. open) line objects by creating a clone of the object. The script will set the stroke color of the clone to transparent and will add a straight line to close the object. Use settings to define the default background color, the fill style, and the strokeWidth of the clone. By default the clone will be grouped with the original object, you can disable this also in settings.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-dimensions.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Set Dimensions](Set%20Dimensions.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)|
|[Set Font Family](Set%20Font%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)|
|[Set Grid](Set%20Grid.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)|
|[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)|
|[Set stroke width of selected elements](Set%20Stroke%20Width%20of%20Selected%20Elements.md)|This script will set the stroke width of selected elements. This is helpful, for example, when you scale freedraw sketches and want to reduce or increase their line width.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-stroke-width.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Split text by lines](Split%20text%20by%20lines.md)|Split lines of text into separate text elements for easier reorganization|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-split-lines.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Set Text Alignment](Set%20Text%20Alignment.md)|Sets text alignment of text block (cetner, right, left). Useful if you want to set a keyboard shortcut for selecting text alignment.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-text-align.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Toggle Fullscreen on Mobile](Toggle%20Fullscreen%20on%20Mobile.md)|Hides Obsidian workspace leaf padding and header (based on option in settings, default is "hide header" = false) which will take Excalidraw to full screen. ⚠ Note that if the header is not visible, it will be very difficult to invoke the command palette to end full screen. Only hide the header if you have a keyboard or you've practiced opening command palette!|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/ea-toggle-fullscreen.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[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,378 @@
/*
![](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-repeat-elements.png)
This script will detect the difference between 2 selected elements, including position, size, angle, stroke and background color, and create several elements that repeat these differences based on the number of repetitions entered by the user.
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
let repeatNum = parseInt(await utils.inputPrompt("repeat times?","number","5"));
if(!repeatNum) {
new Notice("Please enter a number.");
return;
}
const selectedElements = ea.getViewSelectedElements().sort((lha,rha) =>
lha.x === rha.x? (lha.y === rha.y?
(lha.width === rha.width?
(lha.height - rha.height) : lha.width - rha.width)
: lha.y - rha.y) : lha.x - rha.x);
if(selectedElements.length !== 2) {
new Notice("Please select 2 elements.");
return;
}
if(selectedElements[0].type !== selectedElements[1].type) {
new Notice("The selected elements must be of the same type.");
return;
}
const xDistance = selectedElements[1].x - selectedElements[0].x;
const yDistance = selectedElements[1].y - selectedElements[0].y;
const widthDistance = selectedElements[1].width - selectedElements[0].width;
const heightDistance = selectedElements[1].height - selectedElements[0].height;
const angleDistance = selectedElements[1].angle - selectedElements[0].angle;
const bgColor1 = colorNameToHex(selectedElements[0].backgroundColor);
const rgbBgColor1 = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(bgColor1);
const bgColor2 = colorNameToHex(selectedElements[1].backgroundColor);
const rgbBgColor2 = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(bgColor2);
let bgHDistance = 0;
let bgSDistance = 0;
let bgLDistance = 0;
if(rgbBgColor1 && rgbBgColor2) {
const bgHsl1 = rgbToHsl([parseInt(rgbBgColor1[1], 16), parseInt(rgbBgColor1[2], 16), parseInt(rgbBgColor1[3], 16)]);
const bgHsl2 = rgbToHsl([parseInt(rgbBgColor2[1], 16), parseInt(rgbBgColor2[2], 16), parseInt(rgbBgColor2[3], 16)]);
bgHDistance = bgHsl2[0] - bgHsl1[0];
bgSDistance = bgHsl2[1] - bgHsl1[1];
bgLDistance = bgHsl2[2] - bgHsl1[2];
}
const strokeColor1 = colorNameToHex(selectedElements[0].strokeColor);
const rgbStrokeColor1 = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(strokeColor1);
const strokeColor2 = colorNameToHex(selectedElements[1].strokeColor);
const rgbStrokeColor2 = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(strokeColor2);
let strokeHDistance = 0;
let strokeSDistance = 0;
let strokeLDistance = 0;
if(rgbStrokeColor1 && rgbStrokeColor2) {
const strokeHsl1 = rgbToHsl([parseInt(rgbStrokeColor1[1], 16), parseInt(rgbStrokeColor1[2], 16), parseInt(rgbStrokeColor1[3], 16)]);
const strokeHsl2 = rgbToHsl([parseInt(rgbStrokeColor2[1], 16), parseInt(rgbStrokeColor2[2], 16), parseInt(rgbStrokeColor2[3], 16)]);
strokeHDistance = strokeHsl2[0] - strokeHsl1[0];
strokeSDistance = strokeHsl2[1] - strokeHsl1[1];
strokeLDistance = strokeHsl2[2] - strokeHsl1[2];
}
ea.copyViewElementsToEAforEditing(selectedElements);
for(let i=0; i<repeatNum; i++) {
const newEl = ea.cloneElement(selectedElements[1]);
ea.elementsDict[newEl.id] = newEl;
newEl.x += xDistance * (i + 1);
newEl.y += yDistance * (i + 1);
newEl.angle += angleDistance * (i + 1);
const originWidth = newEl.width;
const originHeight = newEl.height;
const newWidth = newEl.width + widthDistance * (i + 1);
const newHeight = newEl.height + heightDistance * (i + 1);
if(newWidth >= 0 && newHeight >= 0) {
if(newEl.type === 'arrow' || newEl.type === 'line' || newEl.type === 'freedraw') {
const minX = Math.min(...newEl.points.map(pt => pt[0]));
const minY = Math.min(...newEl.points.map(pt => pt[1]));
for(let j = 0; j < newEl.points.length; j++) {
if(newEl.points[j][0] > minX) {
newEl.points[j][0] = newEl.points[j][0] + ((newEl.points[j][0] - minX) / originWidth) * (newWidth - originWidth);
}
if(newEl.points[j][1] > minY) {
newEl.points[j][1] = newEl.points[j][1] + ((newEl.points[j][1] - minY) / originHeight) * (newHeight - originHeight);
}
}
}
else {
newEl.width = newWidth;
newEl.height = newHeight;
}
}
if(rgbBgColor1 && rgbBgColor2) {
const bgHsl2 = rgbToHsl([parseInt(rgbBgColor2[1], 16), parseInt(rgbBgColor2[2], 16), parseInt(rgbBgColor2[3], 16)]);
const newBgH = bgHsl2[0] + bgHDistance * (i + 1);
const newBgS = bgHsl2[1] + bgSDistance * (i + 1);
const newBgL = bgHsl2[2] + bgLDistance * (i + 1);
if(newBgH >= 0 && newBgH <= 360 && newBgS >= 0 && newBgS <= 100 && newBgL >= 0 && newBgL <= 100) {
const newBgRgb = hslToRgb([newBgH, newBgS, newBgL]);
newEl.backgroundColor = "#" + rgbToHexString(newBgRgb);
}
}
if(rgbStrokeColor1 && rgbStrokeColor2) {
const strokeHsl2 = rgbToHsl([parseInt(rgbStrokeColor2[1], 16), parseInt(rgbStrokeColor2[2], 16), parseInt(rgbStrokeColor2[3], 16)]);
const newStrokeH = strokeHsl2[0] + strokeHDistance * (i + 1);
const newStrokeS = strokeHsl2[1] + strokeSDistance * (i + 1);
const newStrokeL = strokeHsl2[2] + strokeLDistance * (i + 1);
if(newStrokeH >= 0 && newStrokeH <= 360 && newStrokeS >= 0 && newStrokeS <= 100 && newStrokeL >= 0 && newStrokeL <= 100) {
const newStrokeRgb = hslToRgb([newStrokeH, newStrokeS, newStrokeL]);
newEl.strokeColor = "#" + rgbToHexString(newStrokeRgb);
}
}
}
await ea.addElementsToView(false, false, true);
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

@@ -20,4 +20,4 @@ elements.forEach((el)=>{
el.endArrowhead = start;
});
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView();
ea.addElementsToView(false,false);

View File

@@ -0,0 +1,29 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-scribble-helper.jpg)
iOS scribble helper for better handwriting experience with text elements. If no elements are selected then the creates a text element at pointer position and you can use the edit box to modify the text with scribble. If a text element is selected then opens the input prompt where you can modify this text with scribble.
```javascript
*/
elements = ea.getViewSelectedElements().filter(el=>el.type==="text");
if(elements.length > 1) {
new Notice ("Select only 1 or 0 text elements.")
return;
}
const text = await utils.inputPrompt("Edit text","",(elements.length === 1)?elements[0].rawText:"");
if(!text) return;
if(elements.length === 1) {
ea.copyViewElementsToEAforEditing(elements);
ea.getElements()[0].originalText = text;
ea.getElements()[0].text = text;
ea.getElements()[0].rawText = text;
await ea.addElementsToView(false,false);
return;
}
ea.addText(0,0,text);
await ea.addElementsToView(true, false, true);

View File

@@ -0,0 +1,47 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-select-element-of-type.jpg)
Prompts you with a list of the different element types in the active image. Only elements of the selected type will be selected on the canvas. If nothing is selected when running the script, then the script will process all the elements on the canvas. If some elements are selected when the script is executed, then the script will only process the selected elements.
The script is useful when, for example, you want to bring to front all the arrows, or want to change the color of all the text elements, etc.
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.24")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
let elements = ea.getViewSelectedElements();
if(elements.length === 0) elements = ea.getViewElements();
if(elements.length === 0) {
new Notice("There are no elements in the view");
return;
}
typeSet = new Set();
elements.forEach(el=>typeSet.add(el.type));
let elementType = Array.from(typeSet)[0];
if(typeSet.size > 1) {
elementType = await utils.suggester(
Array.from(typeSet).map((item) => {
switch(item) {
case "line": return "— line";
case "ellipse": return "○ ellipse";
case "rectangle": return "□ rectangle";
case "diamond": return "◇ diamond";
case "arrow": return "→ arrow";
case "freedraw": return "✎ freedraw";
case "image": return "🖼 image";
case "text": return "A text";
default: return item;
}
}),
Array.from(typeSet)
);
}
if(!elementType) return;
ea.selectElementsInView(elements.filter(el=>el.type === elementType));

View File

@@ -36,4 +36,4 @@ el.y = size[1];
el.width = size[2];
el.height = size[3];
ea.copyViewElementsToEAforEditing([el]);
ea.addElementsToView();
ea.addElementsToView(false,false);

View File

@@ -15,4 +15,4 @@ font = parseInt(await utils.suggester(font,["1","2","3"]));
if (isNaN(font)) return;
elements.forEach((el)=>el.fontFamily = font);
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView();
ea.addElementsToView(false,false);

View File

@@ -50,4 +50,4 @@ for(el of elements) { //doing for instead of .forEach due to await inputPrompt
};
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView();
ea.addElementsToView(false,false);

View File

@@ -13,4 +13,4 @@ width = await utils.inputPrompt("Width?","number",width);
const elements=ea.getViewSelectedElements();
ea.copyViewElementsToEAforEditing(elements);
ea.getElements().forEach((el)=>el.strokeWidth=width);
ea.addElementsToView();
ea.addElementsToView(false,false);

View File

@@ -14,4 +14,4 @@ let align = ["left","right","center"];
align = await utils.suggester(align,align);
elements.forEach((el)=>el.textAlign = align);
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView();
ea.addElementsToView(false,false);

View File

@@ -0,0 +1,80 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-set-background-color-of-unclosed-line.jpg)
Use this script to set the background color of unclosed (i.e. open) line objects by creating a clone of the object. The script will set the stroke color of the clone to transparent and will add a straight line to close the object. Use settings to define the default background color, the fill style, and the strokeWidth of the clone. By default the clone will be grouped with the original object, you can disable this also in settings.
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.26")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
settings = ea.getScriptSettings();
//set default values on first run
if(!settings["Background Color"]) {
settings = {
"Background Color" : {
value: "DimGray",
description: "Default background color of the 'shadow' object. Any valid html css color value",
},
"Fill Style": {
value: "hachure",
valueset: ["hachure","cross-hatch","solid"],
description: "Default fill style of the 'shadow' object."
},
"Inherit fill stroke width": {
value: true,
description: "This will impact the densness of the hachure or cross-hatch fill. Use the stroke width of the line object for which the shadow is created. If set to false, the script will use a stroke width of 2."
},
"Group 'shadow' with original": {
value: true,
description: "If the toggle is on then the shadow object that is created will be grouped with the unclosed original object."
}
};
ea.setScriptSettings(settings);
}
const inheritStrokeWidth = settings["Inherit fill stroke width"].value;
const backgroundColor = settings["Background Color"].value;
const fillStyle = settings["Fill Style"].value;
const shouldGroup = settings["Group 'shadow' with original"].value;
const elements = ea.getViewSelectedElements().filter(el=>el.type==="line");
if(elements.length === 0) {
new Notice("No line object is selected");
}
ea.copyViewElementsToEAforEditing(elements);
elementsToMove = [];
elements.forEach((el)=>{
const newEl = ea.cloneElement(el);
ea.elementsDict[newEl.id] = newEl;
newEl.roughness = 1;
if(!inheritStrokeWidth) newEl.strokeWidth = 2;
newEl.strokeColor = "transparent";
newEl.backgroundColor = backgroundColor;
newEl.fillStyle = fillStyle;
const i = el.points.length-1;
newEl.points.push([
//adding an extra point close to the last point in case distance is long from last point to origin and there is a sharp bend. This will avoid a spike due to a tight curve.
el.points[i][0]*0.9,
el.points[i][1]*0.9,
]);
newEl.points.push([0,0]);
if(shouldGroup) ea.addToGroup([el.id,newEl.id]);
elementsToMove.push({fillId: newEl.id, shapeId: el.id});
});
await ea.addElementsToView(false,false);
elementsToMove.forEach((x)=>{
const viewElements = ea.getViewElements();
ea.moveViewElementToZIndex(
x.fillId,
viewElements.indexOf(viewElements.filter(el=>el.id === x.shapeId)[0])-1
)
});
ea.selectElementsInView(ea.getElements());

View File

@@ -23,5 +23,5 @@ elements.forEach((el)=>{
ea.addText(el.x,el.y+i*el.height/text.length,text[i]);
}
});
ea.addElementsToView();
ea.addElementsToView(false,false);
ea.deleteViewElements(elements);

View File

@@ -0,0 +1,49 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/ea-toggle-fullscreen.jpg)
Hides Obsidian workspace leaf padding and header (based on option in settings, default is "hide header" = true) which will take Excalidraw to full screen. ⚠ Note that if the header is not visible, it will be very difficult to invoke the command palette to end full screen. Only hide the header if you have a keyboard or you've practiced opening command palette!
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.21")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
settings = ea.getScriptSettings();
if(!settings["Hide header"]) {
settings = {
"Hide header": {
value: false,
},
...settings
};
await ea.setScriptSettings(settings);
}
if(!settings["Hide header"].description) {
settings["Hide header"].description = "⚠ Note that if the header is not visible, it will be very difficult to invoke the command palette to end full screen. Only hide the header if you have a keyboard or you've practiced opening command palette!";
await ea.setScriptSettings(settings);
}
const hideHeader = settings["Hide header"].value;
const newStylesheet = document.createElement("style");
newStylesheet.id = "excalidraw-full-screen";
newStylesheet.textContent = `
.workspace-leaf-content .view-content {
padding: 0px !important;
}
${hideHeader?`
.view-header {
height: 1px !important;
}`:""}
.status-bar {
display: none !important;
}
`;
const oldStylesheet = document.getElementById(newStylesheet.id);
if(oldStylesheet) document.head.removeChild(oldStylesheet);
else document.head.appendChild(newStylesheet);

View File

@@ -26,12 +26,14 @@ I would love to include your contribution in the script library. If you have a s
# List of available scripts
- [[#Add Connector Point]]
- [[#Add Link and Open Page]]
- [[#Add Link to Existing File and Open]]
- [[#Add Link to New Page and Open]]
- [[#Add Next Step in Process]]
- [[#Box Each Selected Groups]]
- [[#Box Selected Elements]]
- [[#Change shape of selected elements]]
- [[#Connect elements]]
- [[#Convert freedraw to line]]
- [[#Convert selected text elements to sticky notes]]
- [[#Convert text to link with folder and alias]]
- [[#Copy Selected Element Styles to Global]]
@@ -51,7 +53,12 @@ I would love to include your contribution in the script library. If you have a s
- [[#Modify background color opacity]]
- [[#Normalize Selected Arrows]]
- [[#OCR - Optical Character Recognition]]
- [[#Organic Line]]
- [[#Repeat Elements]]
- [[#Reverse arrows]]
- [[#Scribble Helper]]
- [[#Select Elements of Type]]
- [[#Set background color of unclosed line object by adding a shadow clone]]
- [[#Set Dimensions]]
- [[#Set Font Family]]
- [[#Set Grid]]
@@ -59,6 +66,7 @@ I would love to include your contribution in the script library. If you have a s
- [[#Set Stroke Width of Selected Elements]]
- [[#Set Text Alignment]]
- [[#Split text by lines]]
- [[#Toggle Fullscreen on Mobile]]
- [[#Transfer TextElements to Excalidraw markdown metadata]]
- [[#Zoom to Fit Selected Elements]]
@@ -68,11 +76,17 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Add%20Connector%20Point.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will add a small circle to the top left of each text element in the selection and add the text and the "connector point" to a group. You can use the connector points to link text elements with an arrow (in for example a Wardley Map).<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-bullet-point.jpg'></td></tr></table>
## Add Link and Open Page
## Add Link to Existing File and Open
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Link%20and%20Open%20Page.md
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Link%20to%20Existing%20File%20and%20Open.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Add%20Link%20and%20Open%20Page.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Prompt for a file in the vault. Add a link above or below (based on settings) the selected element, to the selected file. If no file is selected then the script creates a new file following the default filename defined for excalidraw embeds. Creates empty markdown file by default, this can be changed to creating a drawing by default via settings.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-link-and-open.jpg'></td></tr></table>
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Add%20Link%20to%20Existing%20File%20and%20Open.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Prompts for a file from the vault. Adds a link to the selected element pointing to the selected file. You can control in settings to open the file in the current active pane or an adjacent pane.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-link-and-open.jpg'></td></tr></table>
## Add Link to New Page and Open
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Link%20to%20New%20Page%20and%20Open.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Add%20Link%20to%20New%20Page%20and%20Open.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Prompts for filename. Offers option to create and open a new Markdown or Excalidraw document. Adds link pointing to the new file, to the selected objects in the drawing. You can control in settings to open the file in the current active pane or an adjacent pane.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-link-to-new-page-and-pen.jpg'></td></tr></table>
## Add Next Step in Process
```excalidraw-script-install
@@ -104,6 +118,12 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Connect%20elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will connect two objects with an arrow. If either of the objects are a set of grouped elements (e.g. a text element grouped with an encapsulating rectangle), the script will identify these groups, and connect the arrow to the largest object in the group (assuming you want to connect the arrow to the box around the text element).<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-connect-elements.jpg'></td></tr></table>
## Convert freedraw to line
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Convert%20freedraw%20to%20line.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Convert%20freedraw%20to%20line.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Convert selected freedraw objects into editable lines. This will allow you to adjust your drawings by dragging line points and will also allow you to select shape fill in case of enclosed lines. You can adjust conversion point density in settings.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-convert-freedraw-to-line.jpg'></td></tr></table>
## Convert selected text elements to sticky notes
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Convert%20selected%20text%20elements%20to%20sticky%20notes.md
@@ -218,12 +238,42 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/OCR%20-%20Optical%20Character%20Recognition.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">REQUIRES EXCALIDRAW 1.5.15<br>The script will 1) send the selected image file to [taskbone.com](https://taskbone.com) to exctract the text from the image, and 2) will add the text to your drawing as a text element.<br><mark>⚠ Note that you will need to manually paste your token into the script after the first run! ⚠</mark><br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-ocr.jpg'><br><iframe width="560" height="315" src="https://www.youtube.com/embed/W2NMzR8s4eE" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></td></tr></table>
## Organic Line
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Organic%20Line.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Organic%20Line.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Converts selected freedraw lines such that pencil pressure will decrease from maximum to minimum from the beginning of the line to its end. The resulting line is placed at the back of the layers, under all other items. Helpful when drawing organic mindmaps.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-organic-line.jpg'></td></tr></table>
## Repeat Elements
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Repeat%20Elements.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Repeat%20Elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will detect the difference between 2 selected elements, including position, size, angle, stroke and background color, and create several elements that repeat these differences based on the number of repetitions entered by the user.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-repeat-elements.png'></td></tr></table>
## Reverse arrows
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Reverse%20arrows.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Reverse%20arrows.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Reverse the direction of **arrows** within the scope of selected elements.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-reverse-arrow.jpg'></td></tr></table>
## Scribble Helper
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Scribble%20Helper.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Scribble%20Helper.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">iOS scribble helper for better handwriting experience with text elements. If no elements are selected then the creates a text element at pointer position and you can use the edit box to modify the text with scribble. If a text element is selected then opens the input prompt where you can modify this text with scribble.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-scribble-helper.jpg'></td></tr></table>
## Select Elements of Type
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Select%20Elements%20of%20Type.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Select%20Elements%20of%20Type.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Prompts you with a list of the different element types in the active image. Only elements of the selected type will be selected on the canvas. If nothing is selected when running the script, then the script will process all the elements on the canvas. If some elements are selected when the script is executed, then the script will only process the selected elements.<br>The script is useful when, for example, you want to bring to front all the arrows, or want to change the color of all the text elements, etc.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-select-element-of-type.jpg'></td></tr></table>
## Set background color of unclosed line object by adding a shadow clone
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20background%20color%20of%20unclosed%20line%20object%20by%20adding%20a%20shadow%20clone.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20background%20color%20of%20unclosed%20line%20object%20by%20adding%20a%20shadow%20clone.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Use this script to set the background color of unclosed (i.e. open) line objects by creating a clone of the object. The script will set the stroke color of the clone to transparent and will add a straight line to close the object. Use settings to define the default background color, the fill style, and the strokeWidth of the clone. By default the clone will be grouped with the original object, you can disable this also in settings.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-set-background-color-of-unclosed-line.jpg'></td></tr></table>
## Set Dimensions
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Dimensions.md
@@ -266,6 +316,12 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/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>
## Toggle Fullscreen on Mobile
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Toggle%20Fullscreen%20on%20Mobile.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Toggle%20Fullscreen%20on%20Mobile.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Hides Obsidian workspace leaf padding and header (based on option in settings, default is "hide header" = false) which will take Excalidraw to full screen. ⚠ Note that if the header is not visible, it will be very difficult to invoke the command palette to end full screen. Only hide the header if you have a keyboard or you've practiced opening command palette!<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/ea-toggle-fullscreen.jpg'></td></tr></table>
## Transfer TextElements to Excalidraw markdown metadata
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Transfer%20TextElements%20to%20Excalidraw%20markdown%20metadata.md

View File

@@ -1,4 +1,3 @@
{
"minifyWhitespace": true,
"minifySyntax":true
"minify": true
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

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

View File

@@ -1,23 +1,25 @@
{
"name": "obsidian-excalidraw-plugin",
"version": "1.3.21",
"version": "1.6.13",
"description": "This is an Obsidian.md plugin that lets you view and edit Excalidraw drawings",
"main": "main.js",
"scripts": {
"dev": "cross-env NODE_ENV=development rollup --config rollup.config.js -w",
"build": "cross-env NODE_ENV=production rollup --config rollup.config.js",
"build": "cross-env NODE_ENV=production rollup --config rollup.config.js && terser main.js --compress toplevel=true,passes=2 --output main.js",
"code:fix": "eslint --max-warnings=0 --ext .ts,.tsx ./src --fix"
},
"keywords": [],
"author": "",
"license": "MIT",
"dependencies": {
"@zsviczian/excalidraw": "0.10.0-obsidian-37",
"@zsviczian/excalidraw": "0.11.0-obsidian-1",
"monkey-around": "^2.3.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "^5.0.0",
"roughjs": "^4.5.2"
"roughjs": "^4.5.2",
"lz-string": "^1.4.4",
"@types/lz-string": "^1.3.34"
},
"devDependencies": {
"@babel/core": "^7.16.12",
@@ -31,12 +33,14 @@
"@types/js-beautify": "^1.13.3",
"@types/node": "^15.12.4",
"@types/react-dom": "^17.0.11",
"@popperjs/core": "^2.11.2",
"cross-env": "^7.0.3",
"html2canvas": "^1.4.0",
"nanoid": "^3.1.31",
"obsidian": "^0.13.21",
"rollup": "^2.66.0",
"rollup-plugin-visualizer": "^5.5.4",
"rollup-plugin-terser": "^7.0.2",
"tslib": "^2.3.1",
"typescript": "^4.5.5",
"eslint-config-prettier": "8.3.0",

View File

@@ -5,6 +5,7 @@ import { env } from "process";
import babel from '@rollup/plugin-babel';
import replace from "@rollup/plugin-replace";
import visualizer from "rollup-plugin-visualizer";
import { terser } from "rollup-plugin-terser";
const isProd = (process.env.NODE_ENV === "production");
console.log("Is production", isProd);
@@ -15,12 +16,10 @@ export default {
dir: '.',
sourcemap: 'inline',
format: 'cjs',
exports: 'default'
exports: 'default',
},
external: ['obsidian'],
plugins: [
typescript({inlineSources: !isProd}),
nodeResolve({ browser: true, preferBuiltins: true }),
replace({
preventAssignment: true,
"process.env.NODE_ENV": JSON.stringify(env.NODE_ENV),
@@ -29,6 +28,11 @@ export default {
exclude: "node_modules/**"
}),
commonjs(),
nodeResolve({ browser: true, preferBuiltins: true }),
typescript({inlineSources: !isProd}),
visualizer(),
]
...isProd ? [
terser({toplevel: true, compress: {passes: 2}})
] : []
],
};

View File

@@ -53,6 +53,7 @@ export class EmbeddedFile {
public mimeType: MimeType = "application/octet-stream";
public size: Size = { height: 0, width: 0 };
public linkParts: LinkParts;
/*public isHyperlink: boolean = false;*/
constructor(plugin: ExcalidrawPlugin, hostPath: string, imgPath: string) {
this.plugin = plugin;
@@ -60,6 +61,12 @@ export class EmbeddedFile {
}
public resetImage(hostPath: string, imgPath: string) {
/*if(imgPath.startsWith("https://") || imgPath.startsWith("http://")) {
this.img=imgPath;
this.imgInverted=imgPath;
this.isHyperlink = true;
return;
}*/
this.imgInverted = this.img = "";
this.mtime = 0;
this.linkParts = getLinkParts(imgPath);
@@ -133,6 +140,9 @@ export class EmbeddedFile {
}
public getImage(isDark: boolean) {
/*if(this.isHyperlink) {
return this.img;
}*/
if (!this.file) {
return "";
}

View File

@@ -15,6 +15,7 @@ import {
VIEW_TYPE_EXCALIDRAW,
MAX_IMAGE_SIZE,
PLUGIN_ID,
COLOR_NAMES,
} from "./constants";
import {
//debug,
@@ -229,6 +230,15 @@ export interface ExcalidrawAutomate {
//recommended use:
//if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.20")) {new Notice("message");return;}
verifyMinimumPluginVersion(requiredVersion: string): boolean;
selectElementsInView(elements: ExcalidrawElement[]): void; //sets selection in view
generateElementId(): string; //returns an 8 character long random id
cloneElement(element: ExcalidrawElement): ExcalidrawElement; //Returns a clone of the element with a new id
moveViewElementToZIndex(elementId:number, newZIndex:number): void; //Moves the element to a specific position in the z-index
hexStringToRgb(color: string):number[];
rgbToHexString(color: number[]):string;
hslToRgb(color: number[]):number[];
rgbToHsl(color:number[]):number[];
colorNameToHex(color:string):string;
}
declare let window: any;
@@ -1137,10 +1147,11 @@ export async function initExcalidrawAutomate(
copyViewElementsToEAforEditing(elements: ExcalidrawElement[]): void {
elements.forEach((el) => {
this.elementsDict[el.id] = {
version: el.version + 1,
...el,
};
});
version: el.version + 1,
updated: Date.now(),
versionNonce: Math.floor(Math.random()*1000000000),
};});
},
viewToggleFullScreen(forceViewMode: boolean = false): void {
if (this.plugin.app.isMobile) {
@@ -1162,14 +1173,9 @@ export async function initExcalidrawAutomate(
commitToHistory: false,
});
}
if (
document.fullscreenElement ===
(this.targetView as ExcalidrawView).contentEl
) {
document.exitFullscreen();
} else {
(this.targetView as ExcalidrawView).contentEl.requestFullscreen();
}
const view = this.targetView as ExcalidrawView;
if(view.isFullscreen()) view.exitFullscreen();
else view.gotoFullscreen();
},
connectObjectWithViewSelectedElement(
objectA: string,
@@ -1301,6 +1307,155 @@ export async function initExcalidrawAutomate(
const manifest = this.plugin.app.plugins.manifests[PLUGIN_ID];
return manifest.version >= requiredVersion;
},
selectElementsInView(elements: ExcalidrawElement[]):void {
if (!this.targetView || !this.targetView?._loaded) {
errorMessage("targetView not set", "selectElementsInView()");
return;
}
if (!elements || elements.length===0) {
return;
}
const API = this.getExcalidrawAPI();
API.selectElements(elements);
},
generateElementId(): string {
return nanoid();
},
cloneElement(element: ExcalidrawElement): ExcalidrawElement{
const newEl = JSON.parse(JSON.stringify(element));
newEl.id = nanoid();
return newEl;
},
moveViewElementToZIndex(elementId:number, newZIndex:number): void {
if (!this.targetView || !this.targetView?._loaded) {
errorMessage("targetView not set", "moveViewElementToZIndex()");
return;
}
const API = this.getExcalidrawAPI();
const elements = this.getViewElements();
const elementToMove = elements.filter((el:any)=>el.id===elementId);
if (elementToMove.length === 0) {
errorMessage(`Element (id: ${elementId}) not found`, "moveViewElementToZIndex");
return;
}
if (newZIndex >= elements.length) {
API.bringToFront(elementToMove);
return;
}
if (newZIndex < 0) {
API.sendToBack(elementToMove);
return;
}
const oldZIndex = elements.indexOf(elementToMove[0]);
elements.splice(newZIndex, 0, elements.splice(oldZIndex, 1)[0]);
API.updateScene({
elements: elements,
commitToHistory: true,
});
},
hexStringToRgb(color: string):number[] {
const res = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(color);
return [parseInt(res[1], 16), parseInt(res[2], 16), parseInt(res[3], 16)];
},
rgbToHexString(color: number[]):string {
const colorInt =
((Math.round(color[0]) & 0xff) << 16) +
((Math.round(color[1]) & 0xff) << 8) +
(Math.round(color[2]) & 0xff);
const colorStr = colorInt.toString(16).toLowerCase();
return "#"+"000000".substring(colorStr.length) + colorStr;
},
hslToRgb(color: number[]):number[] {
const h = color[0] / 360;
const s = color[1] / 100;
const l = color[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;
},
rgbToHsl(color:number[]):number[] {
const r = color[0] / 255;
const g = color[1] / 255;
const b = color[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];
},
colorNameToHex(color:string):string {
if (COLOR_NAMES.has(color.toLowerCase().trim())) {
return COLOR_NAMES.get(color.toLowerCase().trim());
}
return color.trim();
}
};
await initFonts();
return window.ExcalidrawAutomate;
@@ -1349,10 +1504,12 @@ function boxedElement(
strokeSharpness: ea.style.strokeSharpness,
seed: Math.floor(Math.random() * 100000),
version: 1,
versionNonce: 1,
versionNonce: Math.floor(Math.random()*1000000000),
updated: Date.now(),
isDeleted: false,
groupIds: [] as any,
boundElements: [] as any,
link: null as string,
};
}
@@ -1390,6 +1547,9 @@ export function measureText(
fontSize: number,
fontFamily: number,
) {
//following odd error with mindmap on iPad while synchornizing with desktop.
if(!fontSize) fontSize = 20;
if(!fontFamily) fontFamily = 1;
const line = document.createElement("div");
const body = document.body;
line.style.position = "absolute";
@@ -1585,6 +1745,7 @@ export async function createSVG(
exportSettings?.withBackground ?? plugin.settings.exportWithBackground,
withTheme: exportSettings?.withTheme ?? plugin.settings.exportWithTheme,
},
plugin.settings.exportPaddingSVG,
);
if (template?.hasSVGwithBitmap) {
svg.setAttribute("hasbitmap", "true");

View File

@@ -1,410 +0,0 @@
export const EXCALIDRAW_AUTOMATE_INFO = [
{
field: "plugin",
desc: "The ExcalidrawPlugin object",
after: "",
alt: true,
},
{
field: "elementsDict",
desc: "The {} dictionary object, contains the ExcalidrawElements currently edited in Automate indexed by el.id",
after: '[""]',
alt: true,
},
{
field: "imagesDict",
desc: "the images files including DataURL, indexed by fileId",
after: '[""]',
alt: true,
},
{
field: "style.strokeColor",
desc: "[string]\n\nvalid css color. See https://www.w3schools.com/colors/default.asp for more.",
after: "",
alt: true,
},
{
field: "style.backgroundColor",
desc: "[string]\n\nvalid css color. See https://www.w3schools.com/colors/default.asp for more.",
after: "",
alt: true,
},
{
field: "style.angle",
desc: "[number]\n\nrotation of the object in radian",
after: "",
alt: true,
},
{
field: "style.fillStyle",
desc: "[string]\n\n'hachure' | 'cross-hatch' | 'solid'",
after: "",
alt: true,
},
{
field: "style.strokeWidth",
desc: "[number]",
after: "",
alt: true,
},
{
field: "style.strokeStyle",
desc: "[string]\n\n'solid' | 'dashed' | 'dotted'",
after: "",
alt: true,
},
{
field: "style.roughness",
desc: "[number]",
after: "",
alt: true,
},
{
field: "style.opacity",
desc: "[number]",
after: "",
alt: true,
},
{
field: "style.strokeSharpness",
desc: "[string]\n\n'round' | 'sharp'",
after: "",
alt: true,
},
{
field: "style.fontFamily",
desc: "[number]\n\n1: Virgil, 2:Helvetica, 3:Cascadia, 4:LocalFont",
after: "",
alt: true,
},
{
field: "style.fontSize",
desc: "[number]",
after: "",
alt: true,
},
{
field: "style.textAlign",
desc: "[string]\n\n'left' | 'right' | 'center'",
after: "",
alt: true,
},
{
field: "style.verticalAlign",
desc: "[string]\n\nfor future use, has no effect currently; 'top' | 'bottom' | 'middle'",
after: "",
alt: true,
},
{
field: "style.startArrowHead",
desc: "[string]\n\n'triangle' | 'dot' | 'arrow' | 'bar' | null",
after: "",
alt: true,
},
{
field: "style.endArrowHead",
desc: "[string]\n\n'triangle' | 'dot' | 'arrow' | 'bar' | null",
after: "",
alt: true,
},
{
field: "canvas.theme",
desc: "[string]\n\n'dark' | 'light'",
after: "",
alt: true,
},
{
field: "canvas.viewBackgroundColor",
desc: "[string]\n\nA valid css color.\nSee https://www.w3schools.com/colors/default.asp for more.",
after: "",
alt: true,
},
{
field: "canvas.gridSize",
desc: "[number]",
after: "",
alt: true,
},
{
field: "addToGroup",
desc: "addToGroup(objectIds: []): string;",
after: "",
alt: true,
},
{
field: "toCliboard",
desc: "toClipboard(templatePath?: string): void;\n\nCopies current elements using template to clipboard, ready to be pasted into an excalidraw canvas",
after: "",
alt: true,
},
{
field: "getElements",
desc: "getElements(): ExcalidrawElement[];\n\nGet all elements from ExcalidrawAutomate elementsDict",
after: "",
alt: true,
},
{
field: "create",
desc: 'create(params?: {\n filename?: string;\n foldername?: string;\n templatePath?: string;\n onNewPane?: boolean;\n frontmatterKeys?: {\n "excalidraw-plugin"?: "raw" | "parsed";\n "excalidraw-link-prefix"?: string;\n "excalidraw-link-brackets"?: boolean;\n "excalidraw-url-prefix"?: string;\n };\n}): Promise<string>;\n\nCreate a drawing and save it to filename.\nIf filename is null: default filename as defined in Excalidraw settings.\nIf folder is null: default folder as defined in Excalidraw settings\n',
after: "",
alt: true,
},
{
field: "createSVG",
desc: "createSVG(\n templatePath?: string,\n embedFont?: boolean,\n exportSettings?: ExportSettings,\n loader?: EmbeddedFilesLoader,\n theme?: string,\n): Promise<SVGSVGElement>;\n\nUse ExcalidrawAutomate.getExportSettings(boolean,boolean) to create an ExportSettings object.\nUse ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?) to create an EmbeddedFilesLoader object.",
after: "",
alt: true,
},
{
field: "createPNG",
desc: "createPNG(\n templatePath?: string,\n scale?: number,\n exportSettings?: ExportSettings,\n loader?: EmbeddedFilesLoader,\n theme?: string,\n): Promise<any>;\n\nUse ExcalidrawAutomate.getExportSettings(boolean,boolean) to create an ExportSettings object.\nUse ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?) to create an EmbeddedFilesLoader object.",
after: "",
alt: true,
},
{
field: "wrapText",
desc: "wrapText(text: string, lineLen: number): string;",
after: "",
alt: true,
},
{
field: "addRect",
desc: "addRect(topX: number, topY: number, width: number, height: number): string;",
after: "",
alt: true,
},
{
field: "addDiamond",
desc: "addDiamond(topX: number, topY: number, width: number, height: number): string;",
after: "",
alt: true,
},
{
field: "addEllipse",
desc: "addEllipse(topX: number, topY: number, width: number, height: number): string;",
after: "",
alt: true,
},
{
field: "addBlob",
desc: "addBlob(topX: number, topY: number, width: number, height: number): string;",
after: "",
alt: true,
},
{
field: "addText",
desc: 'addText(\n topX: number,\n topY: number,\n text: string,\n formatting?: {\n wrapAt?: number;\n width?: number;\n height?: number;\n textAlign?: string;\n box?: boolean | "box" | "blob" | "ellipse" | "diamond";\n boxPadding?: number;\n },\n id?: string,\n): string;\n\nif box is !null, then text will be boxed\nThe function returns the id of the TextElement. If the text element is boxed i.e. it is a sticky note, then the id of the container object',
after: "",
alt: true,
},
{
field: "addLine",
desc: "addLine(points: [[x: number, y: number]]): string;",
after: "",
alt: true,
},
{
field: "addArrow",
desc: "addArrow(\n points: [[x: number, y: number]],\n formatting?: {\n startArrowHead?: string;\n endArrowHead?: string;\n startObjectId?: string;\n endObjectId?: string;\n },\n): string;",
after: "",
alt: true,
},
{
field: "addImage",
desc: "addImage(topX: number, topY: number, imageFile: TFile): Promise<string>;",
after: "",
alt: true,
},
{
field: "addLaTex",
desc: "addLaTex(topX: number, topY: number, tex: string): Promise<string>;",
after: "",
alt: true,
},
{
field: "connectObjects",
desc: 'connectObjects(\n objectA: string,\n connectionA: ConnectionPoint,\n objectB: string,\n connectionB: ConnectionPoint,\n formatting?: {\n numberOfPoints?: number;\n startArrowHead?: string;\n endArrowHead?: string;\n padding?: number;\n },\n): void;\n\ntype ConnectionPoint = "top" | "bottom" | "left" | "right" | null\nWhen null is passed as ConnectionPoint then Excalidraw will automatically decide\nnumberOfPoints is the number of points on the line. Default is 0 i.e. line will only have a start and end point.\nArrowHead: "triangle"|"dot"|"arrow"|"bar"|null',
after: "",
alt: true,
},
{
field: "clear",
desc: "clear(): void;\n\nClears elementsDict and imagesDict only",
after: "",
alt: true,
},
{
field: "reset",
desc: "reset(): void;\n\nclear() + reset all style values to default",
after: "",
alt: true,
},
{
field: "isExcalidrawFile",
desc: "isExcalidrawFile(f: TFile): boolean;\n\nReturns true if MD file is an Excalidraw file",
after: "",
alt: true,
},
{
field: "targetView",
desc: "targetView: ExcalidrawView;\n\nThe Obsidian view currently edited",
after: "",
alt: true,
},
{
field: "setView",
desc: 'setView(view: ExcalidrawView | "first" | "active"): ExcalidrawView;',
after: "",
alt: true,
},
{
field: "getExcalidrawAPI",
desc: "getExcalidrawAPI(): any;\n\nhttps://github.com/excalidraw/excalidraw/tree/master/src/packages/excalidraw#ref",
after: "",
alt: true,
},
{
field: "getViewElements",
desc: "getViewElements(): ExcalidrawElement[];\n\nget elements in View",
after: "",
alt: true,
},
{
field: "deleteViewElements",
desc: "deleteViewElements(el: ExcalidrawElement[]): boolean;",
after: "",
alt: true,
},
{
field: "getViewSelectedElement",
desc: "getViewSelectedElement(): ExcalidrawElement;\n\nget the selected element in the view, if more are selected, get the first",
after: "",
alt: true,
},
{
field: "getViewSelectedElements",
desc: "getViewSelectedElements(): ExcalidrawElement[];",
after: "",
alt: true,
},
{
field: "getViewFileForImageElement",
desc: "getViewFileForImageElement(el: ExcalidrawElement): TFile | null;\n\nReturns the TFile file handle for the image element",
after: "",
alt: true,
},
{
field: "copyViewElementsToEAforEditing",
desc: "copyViewElementsToEAforEditing(elements: ExcalidrawElement[]): void;\n\nCopies elements from view to elementsDict for editing",
after: "",
alt: true,
},
{
field: "viewToggleFullScreen",
desc: "viewToggleFullScreen(forceViewMode?: boolean): void;",
after: "",
alt: true,
},
{
field: "connectObjectWithViewSelectedElement",
desc: 'connectObjectWithViewSelectedElement(\n objectA: string,\n connectionA: ConnectionPoint,\n connectionB: ConnectionPoint,\n formatting?: {\n numberOfPoints?: number;\n startArrowHead?: string;\n endArrowHead?: string;\n padding?: number;\n },\n): boolean;\n\nConnect an object to the selected element in the view\nSee tooltip for connectObjects for details',
after: "",
alt: true,
},
{
field: "addElementsToView",
desc: "addElementsToView(\n repositionToCursor?: boolean,\n save?: boolean,\n newElementsOnTop?: boolean,\n): Promise<boolean>;\n\nAdds elements from elementsDict to the current view\nrepositionToCursor: default is false\nsave: default is true\nnewElementsOnTop: default is false, i.e. the new elements get to the bottom of the stack\nnewElementsOnTop controls whether elements created with ExcalidrawAutomate are added at the bottom of the stack or the top of the stack of elements already in the view\nNote that elements copied to the view with copyViewElementsToEAforEditing retain their position in the stack of elements in the view even if modified using EA",
after: "",
alt: true,
},
{
field: "onDropHook",
desc: 'onDropHook(data: {\n ea: ExcalidrawAutomate;\n event: React.DragEvent<HTMLDivElement>;\n draggable: any;\n type: "file" | "text" | "unknown";\n payload: {\n files: TFile[];\n text: string; //string\n };\n excalidrawFile: TFile;\n view: ExcalidrawView;\n pointerPosition: { x: number; y: number };\n}): boolean;\n\nIf set Excalidraw will call this function onDrop events.\nA return of true will stop the default onDrop processing in Excalidraw.\n\ndraggable is the Obsidian draggable object\nfiles is the array of dropped files\nexcalidrawFile is the file receiving the drop event\nview is the excalidraw view receiving the drop.\npointerPosition is the pointer position on canvas at the time of drop.',
after: "",
alt: true,
},
{
field: "mostRecentMarkdownSVG",
desc: "mostRecentMarkdownSVG: SVGSVGElement;\n\nMarkdown renderer will drop a copy of the most recent SVG here for debugging purposes",
after: "",
alt: true,
},
{
field: "getEmbeddedFilesLoader",
desc: "getEmbeddedFilesLoader(isDark?: boolean): EmbeddedFilesLoader;\n\nutility function to generate EmbeddedFilesLoader object",
after: "",
alt: true,
},
{
field: "getExportSettings",
desc: "getExportSettings(\n withBackground: boolean,\n withTheme: boolean,\n): ExportSettings;\n\nutility function to generate ExportSettings object",
after: "",
alt: true,
},
{
field: "getBoundingBox",
desc: "getBoundingBox(elements: ExcalidrawElement[]): {\n topX: number;\n topY: number;\n width: number;\n height: number;\n};\n\nGets the bounding box of elements. The bounding box is the box encapsulating all of the elements completely.",
after: "",
alt: true,
},
{
field: "getMaximumGroups",
desc: "getMaximumGroups(elements: ExcalidrawElement[]): ExcalidrawElement[][];\n\nElements grouped by the highest level groups",
after: "",
alt: true,
},
{
field: "getLargestElement",
desc: "getLargestElement(elements: ExcalidrawElement[]): ExcalidrawElement;\n\nGets 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",
after: "",
alt: true,
},
{
field: "intersectElementWithLine",
desc: "intersectElementWithLine(\n element: ExcalidrawBindableElement,\n a: readonly [number, number],\n b: readonly [number, number],\n gap?: number,\n): Point[];\n\nIf gap is given, the element is inflated by this value.\nReturns 2 or 0 intersection points between line going through `a` and `b` and the `element`, in ascending order of distance from `a`.",
after: "",
alt: true,
},
{
field: "getLargestElement",
desc: "getLargestElement(elements: ExcalidrawElement[]): ExcalidrawElement;\n\nGets 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",
after: "",
alt: true,
},
{
field: "activeScript",
desc: "activeScript: string;\n\nMandatory to set before calling the get and set ScriptSettings functions. Set automatically by the ScriptEngine\nSee for more details: https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html",
after: "",
alt: true,
},
{
field: "getScriptSettings",
desc: "getScriptSettings(): {};\n\nReturns script settings. Saves settings in plugin settings, under the activeScript key. See for more details: https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html",
after: "",
alt: true,
},
{
field: "setScriptSettings",
desc: "setScriptSettings(settings: any): Promise<void>;\n\nSets script settings.\nSee for more details: https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html",
after: "",
alt: true,
},
{
field: "openFileInNewOrAdjacentLeaf",
desc: "openFileInNewOrAdjacentLeaf(file: TFile): WorkspaceLeaf;\n\nOpen a file in a new workspaceleaf or reuse an existing adjacent leaf depending on Excalidraw Plugin Settings",
after: "",
alt: true,
},
{
field: "measureText",
desc: "measureText(text: string): { width: number; height: number };\n\nMeasures text size based on current style settings",
after: "",
alt: true,
},
{
field: "verifyMinimumPluginVersion",
desc: 'verifyMinimumPluginVersion(requiredVersion: string): boolean;\n\nreturns true if plugin version is >= than required\nrecommended use:\nif(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.20")) {new Notice("message");return;}',
after: "",
alt: true,
},
];

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -6,13 +6,16 @@ import {
EditorSuggestTriggerInfo,
TFile,
} from "obsidian";
import { FRONTMATTER_KEYS_INFO } from "./constants";
import { EXCALIDRAW_AUTOMATE_INFO } from "./ExcalidrawAutomateFieldSuggestor";
import { FRONTMATTER_KEYS_INFO } from "./SuggestorInfo";
import {
EXCALIDRAW_AUTOMATE_INFO,
EXCALIDRAW_SCRIPTENGINE_INFO,
} from "./SuggestorInfo";
import type ExcalidrawPlugin from "./main";
export class FieldSuggestor extends EditorSuggest<string> {
plugin: ExcalidrawPlugin;
suggestEA: boolean;
suggestType: "ea" | "excalidraw" | "utils";
latestTriggerInfo: EditorSuggestTriggerInfo;
constructor(plugin: ExcalidrawPlugin) {
@@ -28,13 +31,19 @@ export class FieldSuggestor extends EditorSuggest<string> {
if (this.plugin.settings.fieldSuggestor) {
const sub = editor.getLine(cursor.line).substring(0, cursor.ch);
const match =
sub.match(/^excalidraw-(.*)$/)?.[1] ?? sub.match(/(^ea|\Wea)\.([\w\.]*)$/)?.[2];
sub.match(/^excalidraw-(.*)$/)?.[1] ??
sub.match(/(^ea|\Wea)\.([\w\.]*)$/)?.[2] ??
sub.match(/(^utils|\Wutils)\.([\w\.]*)$/)?.[2];
if (match !== undefined) {
this.suggestEA = !sub.match(/^excalidraw-(.*)$/);
this.suggestType = sub.match(/^excalidraw-(.*)$/)
? "excalidraw"
: sub.match(/(^ea|\Wea)\.([\w\.]*)$/)
? "ea"
: "utils";
this.latestTriggerInfo = {
end: cursor,
start: {
ch: cursor.ch-match.length,
ch: cursor.ch - match.length,
line: cursor.line,
},
query: match,
@@ -47,49 +56,68 @@ export class FieldSuggestor extends EditorSuggest<string> {
getSuggestions = (context: EditorSuggestContext) => {
const query = context.query.toLowerCase();
const isEA = this.suggestEA;
const keys = isEA ? EXCALIDRAW_AUTOMATE_INFO : FRONTMATTER_KEYS_INFO;
return keys.map((sug) => sug.field).filter((sug) =>
sug.toLowerCase().includes(query),
);
const keys =
this.suggestType === "ea"
? EXCALIDRAW_AUTOMATE_INFO
: this.suggestType === "utils"
? EXCALIDRAW_SCRIPTENGINE_INFO
: FRONTMATTER_KEYS_INFO;
return keys
.map((sug) => sug.field)
.filter((sug) => sug.toLowerCase().includes(query));
};
renderSuggestion(suggestion: string, el: HTMLElement): void {
const isEA = this.suggestEA;
const text = suggestion.replace(isEA ? "ea." : "excalidraw-", "");
const keys = isEA ? EXCALIDRAW_AUTOMATE_INFO : FRONTMATTER_KEYS_INFO;
el.createDiv({
text,
cls: "excalidraw-suggester-container",
attr: {
"aria-label": keys.find((f) => f.field === suggestion)?.desc,
"aria-label-position": "right",
"aria-label-classes": "excalidraw-suggester-label"
},
});
const text = suggestion.replace(
this.suggestType === "ea"
? "ea."
: this.suggestType === "utils"
? "utils."
: "excalidraw-",
"",
);
const keys =
this.suggestType === "ea"
? EXCALIDRAW_AUTOMATE_INFO
: this.suggestType === "utils"
? EXCALIDRAW_SCRIPTENGINE_INFO
: FRONTMATTER_KEYS_INFO;
const value = keys.find((f) => f.field === suggestion);
el.createEl("b", { text});
el.createEl("br");
if(value.code) {
el.createEl("code", { text: value.code });
}
if(value.desc) {
el.createDiv("div",el=>el.innerHTML=value.desc);
}
}
selectSuggestion(suggestion: string): void {
const { context } = this;
if (context) {
const isEA = this.suggestEA;
const keys = isEA ? EXCALIDRAW_AUTOMATE_INFO : FRONTMATTER_KEYS_INFO;
const keys =
this.suggestType === "ea"
? EXCALIDRAW_AUTOMATE_INFO
: this.suggestType === "utils"
? EXCALIDRAW_SCRIPTENGINE_INFO
: FRONTMATTER_KEYS_INFO;
const replacement = `${suggestion}${
keys.find((f) => f.field === suggestion)?.after
}`;
context.editor.replaceRange(
replacement,
this.latestTriggerInfo.start,
this.latestTriggerInfo.end
this.latestTriggerInfo.end,
);
if (this.latestTriggerInfo.start.ch === this.latestTriggerInfo.end.ch) {
// Dirty hack to prevent the cursor being at the
// beginning of the word after completion,
// beginning of the word after completion,
// Not sure what's the cause of this bug.
const cursor_pos = this.latestTriggerInfo.end;
cursor_pos.ch += replacement.length;
context.editor.setCursor(cursor_pos);
}
}
}
}
}

460
src/FolderSuggester.ts Normal file
View File

@@ -0,0 +1,460 @@
import {
FuzzyMatch,
TFile,
BlockCache,
HeadingCache,
CachedMetadata,
TextComponent,
App,
TFolder,
FuzzySuggestModal,
SuggestModal,
Scope
} from "obsidian";
import { t } from "./lang/helpers";
import { createPopper, Instance as PopperInstance } from "@popperjs/core";
class Suggester<T> {
owner: SuggestModal<T>;
items: T[];
suggestions: HTMLDivElement[];
selectedItem: number;
containerEl: HTMLElement;
constructor(
owner: SuggestModal<T>,
containerEl: HTMLElement,
scope: Scope
) {
this.containerEl = containerEl;
this.owner = owner;
containerEl.on(
"click",
".suggestion-item",
this.onSuggestionClick.bind(this)
);
containerEl.on(
"mousemove",
".suggestion-item",
this.onSuggestionMouseover.bind(this)
);
scope.register([], "ArrowUp", () => {
this.setSelectedItem(this.selectedItem - 1, true);
return false;
});
scope.register([], "ArrowDown", () => {
this.setSelectedItem(this.selectedItem + 1, true);
return false;
});
scope.register([], "Enter", (evt) => {
this.useSelectedItem(evt);
return false;
});
scope.register([], "Tab", (evt) => {
this.chooseSuggestion(evt);
return false;
});
}
chooseSuggestion(evt: KeyboardEvent) {
if (!this.items || !this.items.length) return;
const currentValue = this.items[this.selectedItem];
if (currentValue) {
this.owner.onChooseSuggestion(currentValue, evt);
}
}
onSuggestionClick(event: MouseEvent, el: HTMLDivElement): void {
event.preventDefault();
if (!this.suggestions || !this.suggestions.length) return;
const item = this.suggestions.indexOf(el);
this.setSelectedItem(item, false);
this.useSelectedItem(event);
}
onSuggestionMouseover(event: MouseEvent, el: HTMLDivElement): void {
if (!this.suggestions || !this.suggestions.length) return;
const item = this.suggestions.indexOf(el);
this.setSelectedItem(item, false);
}
empty() {
this.containerEl.empty();
}
setSuggestions(items: T[]) {
this.containerEl.empty();
const els: HTMLDivElement[] = [];
items.forEach((item) => {
const suggestionEl = this.containerEl.createDiv("suggestion-item");
this.owner.renderSuggestion(item, suggestionEl);
els.push(suggestionEl);
});
this.items = items;
this.suggestions = els;
this.setSelectedItem(0, false);
}
useSelectedItem(event: MouseEvent | KeyboardEvent) {
if (!this.items || !this.items.length) return;
const currentValue = this.items[this.selectedItem];
if (currentValue) {
this.owner.selectSuggestion(currentValue, event);
}
}
wrap(value: number, size: number): number {
return ((value % size) + size) % size;
}
setSelectedItem(index: number, scroll: boolean) {
const nIndex = this.wrap(index, this.suggestions.length);
const prev = this.suggestions[this.selectedItem];
const next = this.suggestions[nIndex];
if (prev) prev.removeClass("is-selected");
if (next) next.addClass("is-selected");
this.selectedItem = nIndex;
if (scroll) {
next.scrollIntoView(false);
}
}
}
export abstract class SuggestionModal<T> extends FuzzySuggestModal<T> {
items: T[] = [];
suggestions: HTMLDivElement[];
popper: PopperInstance;
//@ts-ignore
scope: Scope = new Scope(this.app.scope);
suggester: Suggester<FuzzyMatch<T>>;
suggestEl: HTMLDivElement;
promptEl: HTMLDivElement;
emptyStateText: string = "No match found";
limit: number = 100;
shouldNotOpen: boolean;
constructor(app: App, inputEl: HTMLInputElement, items: T[]) {
super(app);
this.inputEl = inputEl;
this.items = items;
this.suggestEl = createDiv("suggestion-container");
this.contentEl = this.suggestEl.createDiv("suggestion");
this.suggester = new Suggester(this, this.contentEl, this.scope);
this.scope.register([], "Escape", this.onEscape.bind(this));
this.inputEl.addEventListener("input", this.onInputChanged.bind(this));
this.inputEl.addEventListener("focus", this.onFocus.bind(this));
this.inputEl.addEventListener("blur", this.close.bind(this));
this.suggestEl.on(
"mousedown",
".suggestion-container",
(event: MouseEvent) => {
event.preventDefault();
}
);
}
empty() {
this.suggester.empty();
}
onInputChanged(): void {
if (this.shouldNotOpen) return;
const inputStr = this.modifyInput(this.inputEl.value);
const suggestions = this.getSuggestions(inputStr);
if (suggestions.length > 0) {
this.suggester.setSuggestions(suggestions.slice(0, this.limit));
} else {
this.onNoSuggestion();
}
this.open();
}
onFocus(): void {
this.shouldNotOpen = false;
this.onInputChanged();
}
modifyInput(input: string): string {
return input;
}
onNoSuggestion() {
this.empty();
this.renderSuggestion(
null,
this.contentEl.createDiv("suggestion-item")
);
}
open(): void {
// TODO: Figure out a better way to do this. Idea from Periodic Notes plugin
this.app.keymap.pushScope(this.scope);
document.body.appendChild(this.suggestEl);
this.popper = createPopper(this.inputEl, this.suggestEl, {
placement: "bottom-start",
modifiers: [
{
name: "offset",
options: {
offset: [0, 10]
}
},
{
name: "flip",
options: {
fallbackPlacements: ["top"]
}
}
]
});
}
onEscape(): void {
this.close();
this.shouldNotOpen = true;
}
close(): void {
// TODO: Figure out a better way to do this. Idea from Periodic Notes plugin
this.app.keymap.popScope(this.scope);
this.suggester.setSuggestions([]);
if (this.popper) {
this.popper.destroy();
}
this.suggestEl.detach();
}
createPrompt(prompts: HTMLSpanElement[]) {
if (!this.promptEl)
this.promptEl = this.suggestEl.createDiv("prompt-instructions");
let prompt = this.promptEl.createDiv("prompt-instruction");
for (let p of prompts) {
prompt.appendChild(p);
}
}
abstract onChooseItem(item: T, evt: MouseEvent | KeyboardEvent): void;
abstract getItemText(arg: T): string;
abstract getItems(): T[];
}
export class PathSuggestionModal extends SuggestionModal<
TFile | BlockCache | HeadingCache
> {
file: TFile;
files: TFile[];
text: TextComponent;
cache: CachedMetadata;
constructor(app: App, input: TextComponent, items: TFile[]) {
super(app, input.inputEl, items);
this.files = [...items];
this.text = input;
//this.getFile();
this.inputEl.addEventListener("input", this.getFile.bind(this));
}
getFile() {
const v = this.inputEl.value,
file = this.app.metadataCache.getFirstLinkpathDest(
v.split(/[\^#]/).shift() || "",
""
);
if (file == this.file) return;
this.file = file;
if (this.file)
this.cache = this.app.metadataCache.getFileCache(this.file);
this.onInputChanged();
}
getItemText(item: TFile | HeadingCache | BlockCache) {
if (item instanceof TFile) return item.path;
if (Object.prototype.hasOwnProperty.call(item, "heading")) {
return (<HeadingCache>item).heading;
}
if (Object.prototype.hasOwnProperty.call(item, "id")) {
return (<BlockCache>item).id;
}
}
onChooseItem(item: TFile | HeadingCache | BlockCache) {
if (item instanceof TFile) {
this.text.setValue(item.basename);
this.file = item;
this.cache = this.app.metadataCache.getFileCache(this.file);
} else if (Object.prototype.hasOwnProperty.call(item, "heading")) {
this.text.setValue(
this.file.basename + "#" + (<HeadingCache>item).heading
);
} else if (Object.prototype.hasOwnProperty.call(item, "id")) {
this.text.setValue(
this.file.basename + "^" + (<BlockCache>item).id
);
}
}
selectSuggestion({ item }: FuzzyMatch<TFile | BlockCache | HeadingCache>) {
let link: string;
if (item instanceof TFile) {
link = item.basename;
} else if (Object.prototype.hasOwnProperty.call(item, "heading")) {
link = this.file.basename + "#" + (<HeadingCache>item).heading;
} else if (Object.prototype.hasOwnProperty.call(item, "id")) {
link = this.file.basename + "^" + (<BlockCache>item).id;
}
this.text.setValue(link);
this.onClose();
this.close();
}
renderSuggestion(
result: FuzzyMatch<TFile | BlockCache | HeadingCache>,
el: HTMLElement
) {
let { item, match: matches } = result || {};
let content = el.createDiv({
cls: "suggestion-content"
});
if (!item) {
content.setText(this.emptyStateText);
content.parentElement.addClass("is-selected");
return;
}
if (item instanceof TFile) {
let pathLength = item.path.length - item.name.length;
const matchElements = matches.matches.map((m) => {
return createSpan("suggestion-highlight");
});
for (
let i = pathLength;
i < item.path.length - item.extension.length - 1;
i++
) {
let match = matches.matches.find((m) => m[0] === i);
if (match) {
let element = matchElements[matches.matches.indexOf(match)];
content.appendChild(element);
element.appendText(item.path.substring(match[0], match[1]));
i += match[1] - match[0] - 1;
continue;
}
content.appendText(item.path[i]);
}
el.createDiv({
cls: "suggestion-note",
text: item.path
});
} else if (Object.prototype.hasOwnProperty.call(item, "heading")) {
content.setText((<HeadingCache>item).heading);
content.prepend(
createSpan({
cls: "suggestion-flair",
text: `H${(<HeadingCache>item).level}`
})
);
} else if (Object.prototype.hasOwnProperty.call(item, "id")) {
content.setText((<BlockCache>item).id);
}
}
get headings() {
if (!this.file) return [];
if (!this.cache) {
this.cache = this.app.metadataCache.getFileCache(this.file);
}
return this.cache.headings || [];
}
get blocks() {
if (!this.file) return [];
if (!this.cache) {
this.cache = this.app.metadataCache.getFileCache(this.file);
}
return Object.values(this.cache.blocks || {}) || [];
}
getItems() {
const v = this.inputEl.value;
if (/#/.test(v)) {
this.modifyInput = (i) => i.split(/#/).pop();
return this.headings;
} else if (/\^/.test(v)) {
this.modifyInput = (i) => i.split(/\^/).pop();
return this.blocks;
}
return this.files;
}
}
export class FolderSuggestionModal extends SuggestionModal<TFolder> {
text: TextComponent;
cache: CachedMetadata;
folders: TFolder[];
folder: TFolder;
constructor(app: App, input: TextComponent, items: TFolder[]) {
super(app, input.inputEl, items);
this.folders = [...items];
this.text = input;
this.inputEl.addEventListener("input", () => this.getFolder());
}
getFolder() {
const v = this.inputEl.value,
folder = this.app.vault.getAbstractFileByPath(v);
if (folder == this.folder) return;
if (!(folder instanceof TFolder)) return;
this.folder = folder;
this.onInputChanged();
}
getItemText(item: TFolder) {
return item.path;
}
onChooseItem(item: TFolder) {
this.text.setValue(item.path);
this.folder = item;
}
selectSuggestion({ item }: FuzzyMatch<TFolder>) {
let link = item.path;
this.text.setValue(link);
this.onClose();
this.close();
}
renderSuggestion(result: FuzzyMatch<TFolder>, el: HTMLElement) {
let { item, match: matches } = result || {};
let content = el.createDiv({
cls: "suggestion-content"
});
if (!item) {
content.setText(this.emptyStateText);
content.parentElement.addClass("is-selected");
return;
}
let pathLength = item.path.length - item.name.length;
const matchElements = matches.matches.map((m) => {
return createSpan("suggestion-highlight");
});
for (let i = pathLength; i < item.path.length; i++) {
let match = matches.matches.find((m) => m[0] === i);
if (match) {
let element = matchElements[matches.matches.indexOf(match)];
content.appendChild(element);
element.appendText(item.path.substring(match[0], match[1]));
i += match[1] - match[0] - 1;
continue;
}
content.appendText(item.path[i]);
}
el.createDiv({
cls: "suggestion-note",
text: item.path
});
}
getItems() {
return this.folders;
}
}

View File

@@ -1,5 +1,6 @@
import { App, FuzzySuggestModal, TFile } from "obsidian";
import { IMAGE_TYPES } from "./constants";
import { fileURLToPath } from "url";
import { IMAGE_TYPES, REG_LINKINDEX_INVALIDCHARS } from "./constants";
import ExcalidrawView from "./ExcalidrawView";
import { t } from "./lang/helpers";
import ExcalidrawPlugin from "./main";
@@ -27,7 +28,9 @@ export class InsertImageDialog extends FuzzySuggestModal<TFile> {
getItems(): TFile[] {
return (this.app.vault.getFiles() || []).filter(
(f: TFile) =>
IMAGE_TYPES.contains(f.extension) || this.plugin.isExcalidrawFile(f),
(IMAGE_TYPES.contains(f.extension) || this.plugin.isExcalidrawFile(f)) &&
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/422
!f.path.match(REG_LINKINDEX_INVALIDCHARS),
);
}
@@ -42,7 +45,7 @@ export class InsertImageDialog extends FuzzySuggestModal<TFile> {
ea.canvas.theme = this.view.excalidrawAPI.getAppState().theme;
(async () => {
await ea.addImage(0, 0, item);
ea.addElementsToView(true, false);
ea.addElementsToView(true, false, true);
})();
}

View File

@@ -1,4 +1,5 @@
import { App, FuzzySuggestModal, TFile } from "obsidian";
import { REG_LINKINDEX_INVALIDCHARS } from "./constants";
import { t } from "./lang/helpers";
export class InsertLinkDialog extends FuzzySuggestModal<TFile> {
@@ -21,8 +22,9 @@ export class InsertLinkDialog extends FuzzySuggestModal<TFile> {
}
getItems(): any[] {
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/422
//@ts-ignore
return this.app.metadataCache.getLinkSuggestions();
return this.app.metadataCache.getLinkSuggestions().filter(x=>!x.path.match(REG_LINKINDEX_INVALIDCHARS));
}
getItemText(item: any): string {

View File

@@ -39,7 +39,7 @@ export class InsertMDDialog extends FuzzySuggestModal<TFile> {
ea.setView(this.view);
(async () => {
await ea.addImage(0, 0, item);
ea.addElementsToView(true, false);
ea.addElementsToView(true, false, true);
})();
}

View File

@@ -29,6 +29,12 @@ let plugin: ExcalidrawPlugin;
let vault: Vault;
let metadataCache: MetadataCache;
const getDefaultWidth = (plugin: ExcalidrawPlugin):string => {
const width = parseInt(plugin.settings.width);
if(isNaN(width) || width === 0) return "400";
return plugin.settings.width;
}
export const initializeMarkdownPostProcessor = (p: ExcalidrawPlugin) => {
plugin = p;
vault = p.app.vault;
@@ -218,7 +224,7 @@ const processInternalEmbeds = async (
if (file && file instanceof TFile && plugin.isExcalidrawFile(file)) {
attr.fwidth = maybeDrawing.getAttribute("width")
? maybeDrawing.getAttribute("width")
: plugin.settings.width;
: getDefaultWidth(plugin);
attr.fheight = maybeDrawing.getAttribute("height");
alt = maybeDrawing.getAttribute("alt");
if (alt == attr.fname) {
@@ -232,9 +238,9 @@ const processInternalEmbeds = async (
if (maybeDrawing.tagName.toLowerCase() == "span") {
alt = `|${alt}`;
}
//1:width, 2:height, 3:style 1 2 3
//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.fwidth = parts[1] ? parts[1] : getDefaultWidth(plugin);
attr.fheight = parts[2];
if (parts[3] != attr.fname) {
attr.style = `excalidraw-svg${parts[3] ? `-${parts[3]}` : ""}`;
@@ -269,7 +275,7 @@ const tmpObsidianWYSIWYG = async (
const attr: imgElementAttributes = {
fname: ctx.sourcePath,
fheight: "",
fwidth: plugin.settings.width,
fwidth: getDefaultWidth(plugin),
style: "excalidraw-svg",
};
@@ -328,7 +334,7 @@ const tmpObsidianWYSIWYG = async (
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.fwidth = parts[1] ? parts[1] : getDefaultWidth(plugin);
attr.fheight = parts[2];
if (parts[3] != attr.fname) {
attr.style = `excalidraw-svg${parts[3] ? `-${parts[3]}` : ""}`;
@@ -336,7 +342,7 @@ const tmpObsidianWYSIWYG = async (
}
if (!hasWidth && !hasHeight && !hasAttr) {
attr.fheight = "";
attr.fwidth = plugin.settings.width;
attr.fwidth = getDefaultWidth(plugin);
attr.style = "excalidraw-svg";
}
};

View File

@@ -6,7 +6,12 @@ import {
FuzzyMatch,
FuzzySuggestModal,
Instruction,
TFile,
Notice,
} from "obsidian";
import ExcalidrawView from "./ExcalidrawView";
import ExcalidrawPlugin from "./main";
import { getNewOrAdjacentLeaf, sleep } from "./Utils";
export class Prompt extends Modal {
private promptEl: HTMLInputElement;
@@ -71,6 +76,7 @@ export class GenericInputPrompt extends Modal {
private didSubmit: boolean = false;
private inputComponent: TextComponent;
private input: string;
private buttons: [{caption: string, action:Function}];
private readonly placeholder: string;
public static Prompt(
@@ -78,12 +84,14 @@ export class GenericInputPrompt extends Modal {
header: string,
placeholder?: string,
value?: string,
buttons?: [{caption: string, action: Function}],
): Promise<string> {
const newPromptModal = new GenericInputPrompt(
app,
header,
placeholder,
value,
buttons,
);
return newPromptModal.waitForClose;
}
@@ -93,10 +101,12 @@ export class GenericInputPrompt extends Modal {
private header: string,
placeholder?: string,
value?: string,
buttons?: [{caption: string, action:Function}],
) {
super(app);
this.placeholder = placeholder;
this.input = value;
this.buttons = buttons;
this.waitForClose = new Promise<string>((resolve, reject) => {
this.resolvePromise = resolve;
@@ -144,17 +154,34 @@ export class GenericInputPrompt extends Modal {
) {
const btn = new ButtonComponent(container);
btn.setButtonText(text).onClick(callback);
return btn;
}
private createButtonBar(mainContentContainer: HTMLDivElement) {
const buttonBarContainer: HTMLDivElement = mainContentContainer.createDiv();
this.createButton(
buttonBarContainer,
"Ok",
this.submitClickCallback,
).setCta().buttonEl.style.marginRight = "0";
if(this.buttons && this.buttons.length>0) {
let b=null;
for(const button of this.buttons) {
const btn = new ButtonComponent(buttonBarContainer);
btn.setButtonText(button.caption).onClick((evt: MouseEvent) => {
const res = button.action(this.input);
if(res) {
this.input=res;
}
this.submit();
});
b = b??btn;
}
if(b){
b.setCta().buttonEl.style.marginRight = "0";
}
} else {
this.createButton(
buttonBarContainer,
"Ok",
this.submitClickCallback,
).setCta().buttonEl.style.marginRight = "0";
}
this.createButton(buttonBarContainer, "Cancel", this.cancelClickCallback);
buttonBarContainer.style.display = "flex";
@@ -175,7 +202,6 @@ export class GenericInputPrompt extends Modal {
private submit() {
this.didSubmit = true;
this.close();
}
@@ -281,3 +307,152 @@ export class GenericSuggester extends FuzzySuggestModal<any> {
}
}
}
class MigrationPrompt extends Modal {
private plugin: ExcalidrawPlugin;
constructor(app: App, plugin: ExcalidrawPlugin) {
super(app);
this.plugin = plugin;
}
onOpen(): void {
this.titleEl.setText("Welcome to Excalidraw 1.2");
this.createForm();
}
onClose(): void {
this.contentEl.empty();
}
createForm(): void {
const div = this.contentEl.createDiv();
// div.addClass("excalidraw-prompt-div");
// div.style.maxWidth = "600px";
div.createEl("p", {
text: "This version comes with tons of new features and possibilities. Please read the description in Community Plugins to find out more.",
});
div.createEl("p", { text: "" }, (el) => {
el.innerHTML =
"Drawings you've created with version 1.1.x need to be converted to take advantage of the new features. You can also continue to use them in compatibility mode. " +
"During conversion your old *.excalidraw files will be replaced with new *.excalidraw.md files.";
});
div.createEl("p", { text: "" }, (el) => {
//files manually follow one of two options:
el.innerHTML =
"To convert your drawings you have the following options:<br><ul>" +
"<li>Click <code>CONVERT FILES</code> now to convert all of your *.excalidraw files, or if you prefer to make a backup first, then click <code>CANCEL</code>.</li>" +
"<li>In the Command Palette select <code>Excalidraw: Convert *.excalidraw files to *.excalidraw.md files</code></li>" +
"<li>Right click an <code>*.excalidraw</code> file in File Explorer and select one of the following options to convert files one by one: <ul>" +
"<li><code>*.excalidraw => *.excalidraw.md</code></li>" +
"<li><code>*.excalidraw => *.md (Logseq compatibility)</code>. This option will retain the original *.excalidraw file next to the new Obsidian format. " +
"Make sure you also enable <code>Compatibility features</code> in Settings for a full solution.</li></ul></li>" +
"<li>Open a drawing in compatibility mode and select <code>Convert to new format</code> from the <code>Options Menu</code></li></ul>";
});
div.createEl("p", {
text: "This message will only appear maximum 3 times in case you have *.excalidraw files in your Vault.",
});
const bConvert = div.createEl("button", { text: "CONVERT FILES" });
bConvert.onclick = () => {
this.plugin.convertExcalidrawToMD();
this.close();
};
const bCancel = div.createEl("button", { text: "CANCEL" });
bCancel.onclick = () => {
this.close();
};
}
}
export class NewFileActions extends Modal {
constructor (
private plugin: ExcalidrawPlugin,
private path: string,
private newPane: boolean,
private view: ExcalidrawView,
) {
super(plugin.app);
}
onOpen(): void {
this.createForm();
}
async onClose() {
}
openFile(file: TFile): void {
if(!file) return;
const leaf = this.newPane
? getNewOrAdjacentLeaf(this.plugin, this.view.leaf)
: this.view.leaf;
leaf.openFile(file);
this.app.workspace.setActiveLeaf(leaf, true, true);
}
createForm(): void {
this.titleEl.setText("New File");
this.contentEl.createDiv({
cls: "excalidraw-prompt-center",
text: "File does not exist. Do you want to create it?"
});
this.contentEl.createDiv({
cls: "excalidraw-prompt-center filepath",
text: this.path
});
this.contentEl.createDiv({cls: "excalidraw-prompt-center"}, (el) => {
//files manually follow one of two options:
el.style.textAlign = "right";
const checks = ():boolean => {
if(!this.path || this.path === "") {
new Notice ("Error: Filename for new file may not be empty");
return false;
}
if(!this.view.file) {
new Notice ("Unknown error. It seems as if your drawing was closed or the drawing file is missing");
return false;
}
return true;
}
const createFile = async (data:string):Promise<TFile> => {
if(!this.path.includes("/")) {
const re = new RegExp(`${this.view.file.name}$`,"g");
this.path = this.view.file.path.replace(re,this.path)
}
if(!this.path.match(/\.md$/)) {
this.path = this.path+".md";
}
const f = await this.app.vault.create(this.path,data)
return f;
}
const bMd = el.createEl("button", { text: "Create Markdown" });
bMd.onclick = async () => {
if(!checks) return;
const f = await createFile("");
this.openFile(f);
this.close();
};
const bEx = el.createEl("button", { text: "Create Excalidraw" });
bEx.onclick = async () => {
if(!checks) return;
const f = await createFile(await this.plugin.getBlankDrawing());
await sleep(200); //wait for metadata cache to update, so file opens as excalidraw
this.openFile(f);
this.close();
};
const bCancel = el.createEl("button", {
text: "Never Mind",
});
bCancel.onclick = () => {
this.close();
};
});
}
}

View File

@@ -166,10 +166,10 @@ export class ScriptEngine {
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction
const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor;
let result = null;
try {
//try {
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),
inputPrompt: (header: string, placeholder?: string, value?: string, buttons?: [{caption:string, action:Function}]) =>
ScriptEngine.inputPrompt(this.plugin.app, header, placeholder, value, buttons),
suggester: (
displayItems: string[],
items: any[],
@@ -184,10 +184,10 @@ export class ScriptEngine {
instructions,
),
});
} catch (e) {
/*} catch (e) {
new Notice(t("SCRIPT_EXECUTION_ERROR"), 4000);
errorlog({ script: this.plugin.ea.activeScript, error: e });
}
}*/
this.plugin.ea.activeScript = null;
return result;
}
@@ -197,9 +197,10 @@ export class ScriptEngine {
header: string,
placeholder?: string,
value?: string,
buttons?: [{caption:string, action:Function}],
) {
try {
return await GenericInputPrompt.Prompt(app, header, placeholder, value);
return await GenericInputPrompt.Prompt(app, header, placeholder, value, buttons);
} catch {
return undefined;
}

547
src/SuggestorInfo.ts Normal file
View File

@@ -0,0 +1,547 @@
import { FRONTMATTER_KEY, FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS, FRONTMATTER_KEY_CUSTOM_PREFIX, FRONTMATTER_KEY_CUSTOM_URL_PREFIX, FRONTMATTER_KEY_DEFAULT_MODE, FRONTMATTER_KEY_FONT, FRONTMATTER_KEY_FONTCOLOR, FRONTMATTER_KEY_MD_STYLE } from "./constants";
type SuggestorInfo = {
field: string,
code: string,
desc: string,
after: string
}
export const EXCALIDRAW_AUTOMATE_INFO:SuggestorInfo[] = [
{
field: "plugin",
code: null,
desc: "The ExcalidrawPlugin object",
after: "",
},
{
field: "elementsDict",
code: null,
desc: "The {} dictionary object, contains the ExcalidrawElements currently edited in Automate indexed by el.id",
after: '[""]',
},
{
field: "imagesDict",
code: null,
desc: "the images files including DataURL, indexed by fileId",
after: '[""]',
},
{
field: "style.strokeColor",
code: "[string]",
desc: "A valid css color. See <a onclick='window.open(\"https://www.w3schools.com/colors/default.asp\")'>W3 School Colors</a> for more.",
after: "",
},
{
field: "style.backgroundColor",
code: "[string]",
desc: "A valid css color. See <a onclick='window.open(\"https://www.w3schools.com/colors/default.asp\")'>W3 School Colors</a> for more.",
after: "",
},
{
field: "style.angle",
code: "[number]",
desc: "Rotation of the object in radian",
after: "",
},
{
field: "style.fillStyle",
code: "[string]",
desc: "'hachure' | 'cross-hatch' | 'solid'",
after: "",
},
{
field: "style.strokeWidth",
code: "[number]",
desc: null,
after: "",
},
{
field: "style.strokeStyle",
code: "[string]",
desc: "'solid' | 'dashed' | 'dotted'",
after: "",
},
{
field: "style.roughness",
code: "[number]",
desc: "0:Architect\n1:Artist\n2:Cartoonist",
after: "",
},
{
field: "style.opacity",
code: "[number]",
desc: "100: Fully opaque\n0: Fully transparent",
after: "",
},
{
field: "style.strokeSharpness",
code: "[string]",
desc: "'round' | 'sharp'",
after: "",
},
{
field: "style.fontFamily",
code: "[number]",
desc: "1: Virgil, 2:Helvetica, 3:Cascadia, 4:LocalFont",
after: "",
},
{
field: "style.fontSize",
code: "[number]",
desc: null,
after: "",
},
{
field: "style.textAlign",
code: "[string]",
desc: "'left' | 'right' | 'center'",
after: "",
},
{
field: "style.verticalAlign",
code: "[string]",
desc: "For future use, has no effect currently; 'top' | 'bottom' | 'middle'",
after: "",
},
{
field: "style.startArrowHead",
code: "[string]",
desc: "'triangle' | 'dot' | 'arrow' | 'bar' | null",
after: "",
},
{
field: "style.endArrowHead",
code: "[string]",
desc: "'triangle' | 'dot' | 'arrow' | 'bar' | null",
after: "",
},
{
field: "canvas.theme",
code: "[string]",
desc: "'dark' | 'light'",
after: "",
},
{
field: "canvas.viewBackgroundColor",
code: "[string]",
desc: "A valid css color.\nSee <a onclick='window.open(\"https://www.w3schools.com/colors/default.asp\")'>W3 School Colors</a> for more.",
after: "",
},
{
field: "canvas.gridSize",
code: "[number]",
desc: null,
after: "",
},
{
field: "addToGroup",
code: "addToGroup(objectIds: []): string;",
desc: null,
after: "",
},
{
field: "toCliboard",
code: "toClipboard(templatePath?: string): void;",
desc: "Copies current elements using template to clipboard, ready to be pasted into an excalidraw canvas",
after: "",
},
{
field: "getElements",
code: "getElements(): ExcalidrawElement[];",
desc: "Get all elements from ExcalidrawAutomate elementsDict",
after: "",
},
{
field: "getElement",
code: "getElement(id: string): ExcalidrawElement;",
desc: "Get single element from ExcalidrawAutomate elementsDict",
after: "",
},
{
field: "create",
code: 'create(params?: {filename?: string, foldername?: string, templatePath?: string, onNewPane?: boolean, frontmatterKeys?: { "excalidraw-plugin"?: "raw" | "parsed", "excalidraw-link-prefix"?: string, "excalidraw-link-brackets"?: boolean, "excalidraw-url-prefix"?: string,},}): Promise<string>;',
desc: 'Create a drawing and save it to filename.\nIf filename is null: default filename as defined in Excalidraw settings.\nIf folder is null: default folder as defined in Excalidraw settings\n',
after: "",
},
{
field: "createSVG",
code: "createSVG(templatePath?: string, embedFont?: boolean, exportSettings?: ExportSettings, loader?: EmbeddedFilesLoader, theme?: string,): Promise<SVGSVGElement>;",
desc: "Use ExcalidrawAutomate.getExportSettings(boolean,boolean) to create an ExportSettings object.\nUse ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?) to create an EmbeddedFilesLoader object.",
after: "",
},
{
field: "createPNG",
code: "createPNG(templatePath?: string, scale?: number, exportSettings?: ExportSettings, loader?: EmbeddedFilesLoader, theme?: string,): Promise<any>;",
desc: "Use ExcalidrawAutomate.getExportSettings(boolean,boolean) to create an ExportSettings object.\nUse ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?) to create an EmbeddedFilesLoader object.",
after: "",
},
{
field: "wrapText",
code: "wrapText(text: string, lineLen: number): string;",
desc: null,
after: "",
},
{
field: "addRect",
code: "addRect(topX: number, topY: number, width: number, height: number): string;",
desc: null,
after: "",
},
{
field: "addDiamond",
code: "addDiamond(topX: number, topY: number, width: number, height: number): string;",
desc: null,
after: "",
},
{
field: "addEllipse",
code: "addEllipse(topX: number, topY: number, width: number, height: number): string;",
desc: null,
after: "",
},
{
field: "addBlob",
code: "addBlob(topX: number, topY: number, width: number, height: number): string;",
desc: null,
after: "",
},
{
field: "addText",
code: 'addText(topX: number, topY: number, text: string, formatting?: {wrapAt?: number; width?: number; height?: number; textAlign?: string; box?: boolean | "box" | "blob" | "ellipse" | "diamond"; boxPadding?: number;}, id?: string,): string;',
desc: 'If box is !null, then text will be boxed\nThe function returns the id of the TextElement. If the text element is boxed i.e. it is a sticky note, then the id of the container object',
after: "",
},
{
field: "addLine",
code: "addLine(points: [[x: number, y: number]]): string;",
desc: null,
after: "",
},
{
field: "addArrow",
code: "addArrow(points: [[x: number, y: number]], formatting?: { startArrowHead?: string; endArrowHead?: string; startObjectId?: string; endObjectId?: string;},): string;",
desc: null,
after: "",
},
{
field: "addImage",
code: "addImage(topX: number, topY: number, imageFile: TFile): Promise<string>;",
desc: null,
after: "",
},
{
field: "addLaTex",
code: "addLaTex(topX: number, topY: number, tex: string): Promise<string>;",
desc: null,
after: "",
},
{
field: "connectObjects",
code: 'connectObjects(objectA: string, connectionA: ConnectionPoint, objectB: string, connectionB: ConnectionPoint, formatting?: {numberOfPoints?: number; startArrowHead?: string; endArrowHead?: string; padding?: number;},): void;',
desc: 'type ConnectionPoint = "top" | "bottom" | "left" | "right" | null\nWhen null is passed as ConnectionPoint then Excalidraw will automatically decide\nnumberOfPoints is the number of points on the line. Default is 0 i.e. line will only have a start and end point.\nArrowHead: "triangle"|"dot"|"arrow"|"bar"|null',
after: "",
},
{
field: "clear",
code: "clear(): void;",
desc: "Clears elementsDict and imagesDict only",
after: "",
},
{
field: "reset",
code: "reset(): void;",
desc: "clear() + reset all style values to default",
after: "",
},
{
field: "isExcalidrawFile",
code: "isExcalidrawFile(f: TFile): boolean;",
desc: "Returns true if MD file is an Excalidraw file",
after: "",
},
{
field: "targetView",
code: "targetView: ExcalidrawView;",
desc: "The Obsidian view currently edited",
after: "",
},
{
field: "setView",
code: 'setView(view: ExcalidrawView | "first" | "active"): ExcalidrawView;',
desc: null,
after: "",
},
{
field: "getExcalidrawAPI",
code: "getExcalidrawAPI(): any;",
desc: "<a onclick='window.open(\"https://github.com/excalidraw/excalidraw/tree/master/src/packages/excalidraw#ref\")'>Excalidraw API</a>",
after: "",
},
{
field: "getViewElements",
code: "getViewElements(): ExcalidrawElement[];",
desc: "Get elements in View",
after: "",
},
{
field: "deleteViewElements",
code: "deleteViewElements(el: ExcalidrawElement[]): boolean;",
desc: null,
after: "",
},
{
field: "getViewSelectedElement",
code: "getViewSelectedElement(): ExcalidrawElement;",
desc: "Get the selected element in the view, if more are selected, get the first",
after: "",
},
{
field: "getViewSelectedElements",
code: "getViewSelectedElements(): ExcalidrawElement[];",
desc: null,
after: "",
},
{
field: "getViewFileForImageElement",
code: "getViewFileForImageElement(el: ExcalidrawElement): TFile | null;",
desc: "Returns the TFile file handle for the image element",
after: "",
},
{
field: "copyViewElementsToEAforEditing",
code: "copyViewElementsToEAforEditing(elements: ExcalidrawElement[]): void;",
desc: "Copies elements from view to elementsDict for editing",
after: "",
},
{
field: "viewToggleFullScreen",
code: "viewToggleFullScreen(forceViewMode?: boolean): void;",
desc: null,
after: "",
},
{
field: "connectObjectWithViewSelectedElement",
code: "connectObjectWithViewSelectedElement(objectA: string, connectionA: ConnectionPoint, connectionB: ConnectionPoint, formatting?: {numberOfPoints?: number; startArrowHead?: string; endArrowHead?: string; padding?: number;},): boolean;",
desc: "Connect an object to the selected element in the view\nSee tooltip for connectObjects for details",
after: "",
},
{
field: "addElementsToView",
code: "addElementsToView(repositionToCursor?: boolean, save?: boolean, newElementsOnTop?: boolean,): Promise<boolean>;",
desc: "Adds elements from elementsDict to the current view\nrepositionToCursor: default is false\nsave: default is true\nnewElementsOnTop: default is false, i.e. the new elements get to the bottom of the stack\nnewElementsOnTop controls whether elements created with ExcalidrawAutomate are added at the bottom of the stack or the top of the stack of elements already in the view\nNote that elements copied to the view with copyViewElementsToEAforEditing retain their position in the stack of elements in the view even if modified using EA",
after: "",
},
{
field: "onDropHook",
code: 'onDropHook(data: {ea: ExcalidrawAutomate, event: React.DragEvent<HTMLDivElement>, draggable: any, type: "file" | "text" | "unknown", payload: {files: TFile[], text: string,}, excalidrawFile: TFile, view: ExcalidrawView, pointerPosition: { x: number, y: number},}): boolean;',
desc: 'If set Excalidraw will call this function onDrop events.\nA return of true will stop the default onDrop processing in Excalidraw.\n\ndraggable is the Obsidian draggable object\nfiles is the array of dropped files\nexcalidrawFile is the file receiving the drop event\nview is the excalidraw view receiving the drop.\npointerPosition is the pointer position on canvas at the time of drop.',
after: "",
},
{
field: "mostRecentMarkdownSVG",
code: "mostRecentMarkdownSVG: SVGSVGElement;",
desc: "Markdown renderer will drop a copy of the most recent SVG here for debugging purposes",
after: "",
},
{
field: "getEmbeddedFilesLoader",
code: "getEmbeddedFilesLoader(isDark?: boolean): EmbeddedFilesLoader;",
desc: "Utility function to generate EmbeddedFilesLoader object",
after: "",
},
{
field: "getExportSettings",
code: "getExportSettings(withBackground: boolean, withTheme: boolean,): ExportSettings;",
desc: "Utility function to generate ExportSettings object",
after: "",
},
{
field: "getBoundingBox",
code: "getBoundingBox(elements: ExcalidrawElement[]): {topX: number, topY: number, width: number, height: number,};",
desc: "Gets the bounding box of elements. The bounding box is the box encapsulating all of the elements completely.",
after: "",
},
{
field: "getMaximumGroups",
code: "getMaximumGroups(elements: ExcalidrawElement[]): ExcalidrawElement[][];",
desc: "Elements grouped by the highest level groups",
after: "",
},
{
field: "getLargestElement",
code: "getLargestElement(elements: ExcalidrawElement[]): ExcalidrawElement;",
desc: "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",
after: "",
},
{
field: "intersectElementWithLine",
code: "intersectElementWithLine(element: ExcalidrawBindableElement, a: readonly [number, number], b: readonly [number, number], gap?: number,): Point[];",
desc: "If gap is given, the element is inflated by this value.\nReturns 2 or 0 intersection points between line going through `a` and `b` and the `element`, in ascending order of distance from `a`.",
after: "",
},
{
field: "getLargestElement",
code: "getLargestElement(elements: ExcalidrawElement[]): ExcalidrawElement;",
desc: "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",
after: "",
},
{
field: "activeScript",
code: "activeScript: string;",
desc: "Mandatory to set before calling the get and set ScriptSettings functions. Set automatically by the ScriptEngine\nSee for more details: <a onclick='window.open(\"https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html\")'>Script Engine Help</a>",
after: "",
},
{
field: "getScriptSettings",
code: "getScriptSettings(): {};",
desc: "Returns script settings. Saves settings in plugin settings, under the activeScript key. See for more details: <a onclick='window.open(\"https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html\")'>Script Engine Help</a>",
after: "",
},
{
field: "setScriptSettings",
code: "setScriptSettings(settings: any): Promise<void>;",
desc: "Sets script settings.\nSee for more details: <a onclick='window.open(\"https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html\")'>Script Engine Help</a>",
after: "",
},
{
field: "openFileInNewOrAdjacentLeaf",
code: "openFileInNewOrAdjacentLeaf(file: TFile): WorkspaceLeaf;",
desc: "Open a file in a new workspaceleaf or reuse an existing adjacent leaf depending on Excalidraw Plugin Settings",
after: "",
},
{
field: "measureText",
code: "measureText(text: string): { width: number; height: number };",
desc: "Measures text size based on current style settings",
after: "",
},
{
field: "verifyMinimumPluginVersion",
code: 'verifyMinimumPluginVersion(requiredVersion: string): boolean;',
desc: 'Returns true if plugin version is >= than required\nrecommended use:\n<code>if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.20")) {new Notice("message");return;}<code>',
after: "",
},
{
field: "selectElementsInView",
code: "selectElementsInView(elements: ExcalidrawElement[]):void;",
desc: "Elements provided will be set as selected in the targetView.",
after: "",
},
{
field: "generateElementId",
code: "generateElementId(): string;",
desc: "Returns an 8 character long random id",
after: "",
},
{
field: "cloneElement",
code: "cloneElement(element: ExcalidrawElement): ExcalidrawElement;",
desc: "Returns a clone of the element with a new element id",
after: "",
},
{
field: "moveViewElementToZIndex",
code: "moveViewElementToZIndex(elementId:number, newZIndex:number): void;",
desc: "Moves the element to a specific position in the z-index",
after: "",
},
{
field: "hexStringToRgb",
code: "hexStringToRgb(color: string):number[];",
desc: "Converts a HEX color to an RGB number array. #FF0000 to [255,0,0]",
after: "",
},
{
field: "rgbToHexString",
code: "rgbToHexString(color: number[]):string;",
desc: "Converts an RGB number array to a HEX string. [255,0,0] to #FF0000",
after: "",
},
{
field: "hslToRgb",
code: "hslToRgb(color: number[]):number[];",
desc: "Converts an HSL number array to an RGB number array. [0,100,50] to [255,0,0]",
after: "",
},
{
field: "rgbToHsl",
code: "rgbToHsl(color:number[]):number[];",
desc: "Converts an RGB number array to an HSL number array. [255,0,0] to [0,100,50]",
after: "",
},
{
field: "colorNameToHex",
code: "colorNameToHex(color:string):string;",
desc: "Converts a CSS color name to its HEX color equivalent. 'White' to #FFFFFF",
after: "",
},
];
export const EXCALIDRAW_SCRIPTENGINE_INFO:SuggestorInfo[] = [
{
field: "inputPrompt",
code: "inputPrompt: (header: string, placeholder?: string, value?: string, buttons?: [{caption:string, action:Function}]);",
desc: "Opens a prompt that asks for an input.\nReturns a string with the input.\nYou need to await the result of inputPrompt.\n" +
"buttons.action(input: string) => string\nThe button action function will receive the actual input string. If action returns null, input will be unchanged. If action returns a string, input will receive that value when the promise is resolved. " +
"example:\n<code>let fileType = '';\nconst filename = await utils.inputPrompt (\n 'Filename',\n '',\n '',\n, [\n {\n caption: 'Markdown',\n action: ()=>{fileType='md';return;}\n },\n {\n caption: 'Excalidraw',\n action: ()=>{fileType='ex';return;}\n }\n ]\n);</code>",
after: "",
},
{
field: "suggester",
code: "suggester: (displayItems: string[], items: any[], hint?: string, instructions?:Instruction[]);",
desc: "Opens a suggester. Displays the displayItems and returns the corresponding item from items[]\nYou need to await the result of suggester.\nIf the user cancels (ESC), suggester will return undefined\nHint and instructions are optional\n\n<code>interface Instruction {command: string;purpose: string;}</code>",
after: "",
},
];
export const FRONTMATTER_KEYS_INFO:SuggestorInfo[] = [
{
field: "plugin",
code: null,
desc: "Denotes an excalidraw file. If key is not present, the file will not be recognized as an Excalidarw file. Valid values are 'parsed' and 'raw'",
after: ": parsed",
},
{
field: "link-prefix",
code: null,
desc: "Set custom prefix to denote text element containing a valid internal link. Set to empty string if you do not want to show a prefix",
after: ': "📍"',
},
{
field: "url-prefix",
code: null,
desc: "Set custom prefix to denote text element containing a valid external link. Set to empty string if you do not want to show a prefix",
after: ': "🌐"',
},
{
field: "link-brackets",
code: null,
desc: "Set to true, if you want to display [[square brackets]] around the links in Text Elements",
after: ": true",
},
{
field: "default-mode",
code: null,
desc: "Specifies how Excalidraw should open by default. Valid values are: view|zen",
after: ": view",
},
{
field: "font",
code: null,
desc: "This key applies to Markdown Embeds. You can control the appearance of the embedded markdown file on a file by file bases by adding the this front matter keys to your markdown document. Valid values are: Virgil|Cascadia|font_file_name.extension",
after: ": Virgil",
},
{
field: "font-color",
code: null,
desc: "This key applies to Markdown Embeds. You can control the appearance of the embedded markdown file on a file by file bases by adding the this front matter keys to your markdown document. Valid values are: css-color-name|#HEXcolor|any-other-html-standard-format",
after: ": SteelBlue",
},
{
field: "css",
code: null,
desc: 'This key applies to Markdown Embeds. You can control the appearance of the embedded markdown file on a file by file bases by adding the this front matter keys to your markdown document. Valid values are: "css-filename|css snippet"',
after: ': ""',
},
];

View File

@@ -5,6 +5,7 @@ import {
Notice,
request,
TAbstractFile,
TFile,
TFolder,
Vault,
WorkspaceLeaf,
@@ -20,6 +21,7 @@ import {
import ExcalidrawPlugin from "./main";
import { ExcalidrawElement } from "@zsviczian/excalidraw/types/element/types";
import { ExportSettings } from "./ExcalidrawView";
import { compressToBase64, decompressFromBase64 } from "lz-string";
declare module "obsidian" {
interface Workspace {
@@ -82,9 +84,9 @@ export function splitFolderAndFilename(filepath: string): {
basename: string;
} {
const lastIndex = filepath.lastIndexOf("/");
const filename = lastIndex == -1 ? filepath : filepath.substr(lastIndex + 1);
const filename = lastIndex == -1 ? filepath : filepath.substring(lastIndex + 1);
return {
folderpath: normalizePath(filepath.substr(0, lastIndex)),
folderpath: normalizePath(filepath.substring(0, lastIndex)),
filename,
basename: filename.replace(/\.[^/.]+$/, ""),
};
@@ -124,6 +126,11 @@ export function getIMGPathFromExcalidrawFile(
);
}
/*export function getBakPath(file:TFile):string {
const re = new RegExp(`${file.name}$`,"g");
return file.path.replace(re,`.${file.name}.bak`);
}*/
/**
* Create new file, if file already exists find first unique filename by adding a number to the end of the filename
* @param filename
@@ -277,8 +284,9 @@ export const viewportCoordsToSceneCoords = (
},
) => {
const invScale = 1 / zoom.value;
const x = (clientX - zoom.translation.x - offsetLeft) * invScale - scrollX;
const y = (clientY - zoom.translation.y - offsetTop) * invScale - scrollY;
const x = (clientX - offsetLeft) * invScale - scrollX;
const y = (clientY - offsetTop) * invScale - scrollY;
return { x, y };
};
@@ -404,6 +412,7 @@ export const getAttachmentsFolderAndFilePath = async (
export const getSVG = async (
scene: any,
exportSettings: ExportSettings,
padding: number,
): Promise<SVGSVGElement> => {
try {
return await exportToSvg({
@@ -416,7 +425,7 @@ export const getSVG = async (
...scene.appState,
},
files: scene.files,
exportPadding: 10,
exportPadding: padding,
});
} catch (error) {
return null;
@@ -542,6 +551,14 @@ export const getLinkParts = (fname: string): LinkParts => {
};
};
export const compress = (data:string):string => {
return compressToBase64(data).replace(/(.{1024})/g, "$1\n");
};
export const decompress = (data:string):string => {
return decompressFromBase64(data.replaceAll("\n","").replaceAll("\r",""));
};
export const errorlog = (data: {}) => {
console.error({ plugin: "Excalidraw", ...data });
};

View File

@@ -16,7 +16,7 @@ export const PLUGIN_ID = "obsidian-excalidraw-plugin";
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_LINKINDEX_INVALIDCHARS = /[<>:"\\|?*#]/g;
export const REG_BLOCK_REF_CLEAN =
/\+|\/|~|=|%|\(|\)|{|}|,|\.|\$|!|\?|;|\[|]|\^|#|\*|<|>|&|@|\||\\|"|:/g;
export const IMAGE_TYPES = ["jpeg", "jpg", "png", "gif", "svg"];
@@ -29,58 +29,7 @@ export const FRONTMATTER_KEY_DEFAULT_MODE = "excalidraw-default-mode";
export const FRONTMATTER_KEY_FONT = "excalidraw-font";
export const FRONTMATTER_KEY_FONTCOLOR = "excalidraw-font-color";
export const FRONTMATTER_KEY_MD_STYLE = "excalidraw-css";
export const FRONTMATTER_KEYS_INFO = [
{
field: FRONTMATTER_KEY,
desc: "Denotes an excalidraw file. If key is not present, the file will not be recognized as an Excalidarw file. Valid values are 'parsed' and 'raw'",
after: ": parsed",
alt: true,
},
{
field: FRONTMATTER_KEY_CUSTOM_PREFIX,
desc: "Set custom prefix to denote text element containing a valid internal link. Set to empty string if you do not want to show a prefix",
after: ': "📍"',
alt: true,
},
{
field: FRONTMATTER_KEY_CUSTOM_URL_PREFIX,
desc: "Set custom prefix to denote text element containing a valid external link. Set to empty string if you do not want to show a prefix",
after: ': "🌐"',
alt: true,
},
{
field: FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS,
desc: "Set to true, if you want to display [[square brackets]] around the links in Text Elements",
after: ": true",
alt: true,
},
{
field: FRONTMATTER_KEY_DEFAULT_MODE,
desc: "Specifies how Excalidraw should open by default. Valid values are: view|zen",
after: ": view",
alt: true,
},
{
field: FRONTMATTER_KEY_FONT,
desc: "This key applies to Markdown Embeds. You can control the appearance of the embedded markdown file on a file by file bases by adding the this front matter keys to your markdown document. Valid values are: Virgil|Cascadia|font_file_name.extension",
after: ": Virgil",
alt: true,
},
{
field: FRONTMATTER_KEY_FONTCOLOR,
desc: "This key applies to Markdown Embeds. You can control the appearance of the embedded markdown file on a file by file bases by adding the this front matter keys to your markdown document. Valid values are: css-color-name|#HEXcolor|any-other-html-standard-format",
after: ": SteelBlue",
alt: true,
},
{
field: FRONTMATTER_KEY_MD_STYLE,
desc: 'This key applies to Markdown Embeds. You can control the appearance of the embedded markdown file on a file by file bases by adding the this front matter keys to your markdown document. Valid values are: "css-filename|css snippet"',
after: ': ""',
alt: true,
},
];
export const LOCAL_PROTOCOL = "md://";
export const VIEW_TYPE_EXCALIDRAW = "excalidraw";
export const ICON_NAME = "excalidraw-icon";
export const MAX_COLORS = 5;
@@ -106,6 +55,148 @@ 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 COLOR_NAMES = new Map<string,string>();
COLOR_NAMES.set("aliceblue","#f0f8ff");
COLOR_NAMES.set("antiquewhite","#faebd7");
COLOR_NAMES.set("aqua","#00ffff");
COLOR_NAMES.set("aquamarine","#7fffd4");
COLOR_NAMES.set("azure","#f0ffff");
COLOR_NAMES.set("beige","#f5f5dc");
COLOR_NAMES.set("bisque","#ffe4c4");
COLOR_NAMES.set("black","#000000");
COLOR_NAMES.set("blanchedalmond","#ffebcd");
COLOR_NAMES.set("blue","#0000ff");
COLOR_NAMES.set("blueviolet","#8a2be2");
COLOR_NAMES.set("brown","#a52a2a");
COLOR_NAMES.set("burlywood","#deb887");
COLOR_NAMES.set("cadetblue","#5f9ea0");
COLOR_NAMES.set("chartreuse","#7fff00");
COLOR_NAMES.set("chocolate","#d2691e");
COLOR_NAMES.set("coral","#ff7f50");
COLOR_NAMES.set("cornflowerblue","#6495ed");
COLOR_NAMES.set("cornsilk","#fff8dc");
COLOR_NAMES.set("crimson","#dc143c");
COLOR_NAMES.set("cyan","#00ffff");
COLOR_NAMES.set("darkblue","#00008b");
COLOR_NAMES.set("darkcyan","#008b8b");
COLOR_NAMES.set("darkgoldenrod","#b8860b");
COLOR_NAMES.set("darkgray","#a9a9a9");
COLOR_NAMES.set("darkgreen","#006400");
COLOR_NAMES.set("darkkhaki","#bdb76b");
COLOR_NAMES.set("darkmagenta","#8b008b");
COLOR_NAMES.set("darkolivegreen","#556b2f");
COLOR_NAMES.set("darkorange","#ff8c00");
COLOR_NAMES.set("darkorchid","#9932cc");
COLOR_NAMES.set("darkred","#8b0000");
COLOR_NAMES.set("darksalmon","#e9967a");
COLOR_NAMES.set("darkseagreen","#8fbc8f");
COLOR_NAMES.set("darkslateblue","#483d8b");
COLOR_NAMES.set("darkslategray","#2f4f4f");
COLOR_NAMES.set("darkturquoise","#00ced1");
COLOR_NAMES.set("darkviolet","#9400d3");
COLOR_NAMES.set("deeppink","#ff1493");
COLOR_NAMES.set("deepskyblue","#00bfff");
COLOR_NAMES.set("dimgray","#696969");
COLOR_NAMES.set("dodgerblue","#1e90ff");
COLOR_NAMES.set("firebrick","#b22222");
COLOR_NAMES.set("floralwhite","#fffaf0");
COLOR_NAMES.set("forestgreen","#228b22");
COLOR_NAMES.set("fuchsia","#ff00ff");
COLOR_NAMES.set("gainsboro","#dcdcdc");
COLOR_NAMES.set("ghostwhite","#f8f8ff");
COLOR_NAMES.set("gold","#ffd700");
COLOR_NAMES.set("goldenrod","#daa520");
COLOR_NAMES.set("gray","#808080");
COLOR_NAMES.set("green","#008000");
COLOR_NAMES.set("greenyellow","#adff2f");
COLOR_NAMES.set("honeydew","#f0fff0");
COLOR_NAMES.set("hotpink","#ff69b4");
COLOR_NAMES.set("indianred","#cd5c5c");
COLOR_NAMES.set("indigo","#4b0082");
COLOR_NAMES.set("ivory","#fffff0");
COLOR_NAMES.set("khaki","#f0e68c");
COLOR_NAMES.set("lavender","#e6e6fa");
COLOR_NAMES.set("lavenderblush","#fff0f5");
COLOR_NAMES.set("lawngreen","#7cfc00");
COLOR_NAMES.set("lemonchiffon","#fffacd");
COLOR_NAMES.set("lightblue","#add8e6");
COLOR_NAMES.set("lightcoral","#f08080");
COLOR_NAMES.set("lightcyan","#e0ffff");
COLOR_NAMES.set("lightgoldenrodyellow","#fafad2");
COLOR_NAMES.set("lightgrey","#d3d3d3");
COLOR_NAMES.set("lightgreen","#90ee90");
COLOR_NAMES.set("lightpink","#ffb6c1");
COLOR_NAMES.set("lightsalmon","#ffa07a");
COLOR_NAMES.set("lightseagreen","#20b2aa");
COLOR_NAMES.set("lightskyblue","#87cefa");
COLOR_NAMES.set("lightslategray","#778899");
COLOR_NAMES.set("lightsteelblue","#b0c4de");
COLOR_NAMES.set("lightyellow","#ffffe0");
COLOR_NAMES.set("lime","#00ff00");
COLOR_NAMES.set("limegreen","#32cd32");
COLOR_NAMES.set("linen","#faf0e6");
COLOR_NAMES.set("magenta","#ff00ff");
COLOR_NAMES.set("maroon","#800000");
COLOR_NAMES.set("mediumaquamarine","#66cdaa");
COLOR_NAMES.set("mediumblue","#0000cd");
COLOR_NAMES.set("mediumorchid","#ba55d3");
COLOR_NAMES.set("mediumpurple","#9370d8");
COLOR_NAMES.set("mediumseagreen","#3cb371");
COLOR_NAMES.set("mediumslateblue","#7b68ee");
COLOR_NAMES.set("mediumspringgreen","#00fa9a");
COLOR_NAMES.set("mediumturquoise","#48d1cc");
COLOR_NAMES.set("mediumvioletred","#c71585");
COLOR_NAMES.set("midnightblue","#191970");
COLOR_NAMES.set("mintcream","#f5fffa");
COLOR_NAMES.set("mistyrose","#ffe4e1");
COLOR_NAMES.set("moccasin","#ffe4b5");
COLOR_NAMES.set("navajowhite","#ffdead");
COLOR_NAMES.set("navy","#000080");
COLOR_NAMES.set("oldlace","#fdf5e6");
COLOR_NAMES.set("olive","#808000");
COLOR_NAMES.set("olivedrab","#6b8e23");
COLOR_NAMES.set("orange","#ffa500");
COLOR_NAMES.set("orangered","#ff4500");
COLOR_NAMES.set("orchid","#da70d6");
COLOR_NAMES.set("palegoldenrod","#eee8aa");
COLOR_NAMES.set("palegreen","#98fb98");
COLOR_NAMES.set("paleturquoise","#afeeee");
COLOR_NAMES.set("palevioletred","#d87093");
COLOR_NAMES.set("papayawhip","#ffefd5");
COLOR_NAMES.set("peachpuff","#ffdab9");
COLOR_NAMES.set("peru","#cd853f");
COLOR_NAMES.set("pink","#ffc0cb");
COLOR_NAMES.set("plum","#dda0dd");
COLOR_NAMES.set("powderblue","#b0e0e6");
COLOR_NAMES.set("purple","#800080");
COLOR_NAMES.set("rebeccapurple","#663399");
COLOR_NAMES.set("red","#ff0000");
COLOR_NAMES.set("rosybrown","#bc8f8f");
COLOR_NAMES.set("royalblue","#4169e1");
COLOR_NAMES.set("saddlebrown","#8b4513");
COLOR_NAMES.set("salmon","#fa8072");
COLOR_NAMES.set("sandybrown","#f4a460");
COLOR_NAMES.set("seagreen","#2e8b57");
COLOR_NAMES.set("seashell","#fff5ee");
COLOR_NAMES.set("sienna","#a0522d");
COLOR_NAMES.set("silver","#c0c0c0");
COLOR_NAMES.set("skyblue","#87ceeb");
COLOR_NAMES.set("slateblue","#6a5acd");
COLOR_NAMES.set("slategray","#708090");
COLOR_NAMES.set("snow","#fffafa");
COLOR_NAMES.set("springgreen","#00ff7f");
COLOR_NAMES.set("steelblue","#4682b4");
COLOR_NAMES.set("tan","#d2b48c");
COLOR_NAMES.set("teal","#008080");
COLOR_NAMES.set("thistle","#d8bfd8");
COLOR_NAMES.set("tomato","#ff6347");
COLOR_NAMES.set("turquoise","#40e0d0");
COLOR_NAMES.set("violet","#ee82ee");
COLOR_NAMES.set("wheat","#f5deb3");
COLOR_NAMES.set("white","#ffffff");
COLOR_NAMES.set("whitesmoke","#f5f5f5");
COLOR_NAMES.set("yellow","#ffff00");
COLOR_NAMES.set("yellowgreen","#9acd32");
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"/>`;

View File

@@ -44,6 +44,8 @@ export default {
INSERT_LATEX:
"Insert LaTeX formula (e.g. \\binom{n}{k} = \\frac{n!}{k!(n-k)!})",
ENTER_LATEX: "Enter a valid LaTeX expression",
TRAY_MODE: "Toggle property-panel tray-mode",
SEARCH: "Search for text in drawing",
//ExcalidrawView.ts
INSTALL_SCRIPT_BUTTON: "Install or update Excalidraw Scripts",
@@ -59,11 +61,11 @@ export default {
TEXT_ELEMENT_EMPTY:
"No ImageElement is selected or TextElement is empty, or [[valid-link|alias]]</code> or <code>[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:
"Force-save to update transclusions in adjacent panes.\n(Please note, that autosave is always on)",
"Force-save to update transclusions in adjacent panes.\n(Check autosave settings in plugin settings.)",
RAW: "Change to PREVIEW mode (only effects text-elements with links or transclusions)",
PARSED:
"Change to RAW mode (only effects text-elements with links or transclusions)",
@@ -95,12 +97,23 @@ export default {
"You can access your scripts from Excalidraw via the Obsidian Command Palette. Assign " +
"hotkeys to your favorite scripts just like to any other Obsidian command. " +
"The folder may not be the root folder of your Vault. ",
AUTOSAVE_NAME: "Autosave",
COMPRESS_NAME: "Compress Excalidraw JSON in Markdown",
COMPRESS_DESC: "By enabling this feature Excalidraw will store the drawing JSON in a Base64 compressed " +
"format using the <a href='https://pieroxy.net/blog/pages/lz-string/index.html'>LZ-String</a> algorithm. " +
"This will reduce the chance of Excalidraw JSON cluttering your search results in Obsidian. " +
"As a side effect, this will also reduce the filesize of Excalidraw drawings. " +
"When you switch an Excalidraw drawing to Markdown view, using the options menu in Excalidraw, the file will " +
"be saved without compression, so that you can read and edit the JSON string. The drawing will be compressed again " +
"once you switch back to Excalidraw view. " +
"The setting only has effect 'point forward', meaning, existing drawings will not be effected by the setting " +
"until you open them and save them. ",
AUTOSAVE_NAME: "Enable Autosave",
AUTOSAVE_DESC:
"Automatically save the active drawing every 30 seconds. Save normally happens when you close Excalidraw or Obsidian, or move " +
"focus to another pane. In rare cases autosave may slightly disrupt your drawing flow. I created this feature with mobile " +
"phones in mind (I only have experience with Android), where 'swiping out Obsidian to close it' led to some data loss, and because " +
"I wasn't able to force save on application termination on mobiles. If you use Excalidraw on a desktop this is likely not needed.",
"Automatically save the active drawing, in case there are changes, every 15, 30 seconds, or 1, 2, 3, 4, or 5 minute. Save normally happens when you close Excalidraw or Obsidian, or move " +
"focus to another pane. I created this feature with mobile " +
"phones and tablets in mind, where 'swiping out Obsidian to close it' led to some data loss.",
AUTOSAVE_INTERVAL_NAME: "Interval for autosave",
AUTOSAVE_INTERVAL_DESC: "The time interval between saves. Autosave will skip if there are no changes in the drawing.",
FILENAME_HEAD: "Filename",
FILENAME_DESC:
"<p>The auto-generated filename consists of a prefix and a date. " +
@@ -171,6 +184,10 @@ export default {
"In PREVIEW mode, if the Text Element contains a URL link, precede the text with these characters. " +
"You can override this setting for a specific drawing by adding <code>"
}${FRONTMATTER_KEY_CUSTOM_URL_PREFIX}: "🌐 "</code> to the file's frontmatter.`,
HOVERPREVIEW_NAME: "Hover preview without CTRL/CMD key",
HOVERPREVIEW_DESC: "Toggle On: Hover preview for [[wiki links]] is shown immedately, without the need to hold the CTRL/CMD key.<br>Toggle Off: Hover preview is shown only when you hold the CTRL/CMD key while hovering the link.",
LINKOPACITY_NAME: "Opacity of link icon",
LINKOPACITY_DESC: "Opacity of the link indicator icon in the top right corner of an element. 1 is opaque, 0 is transparent.",
LINK_CTRL_CLICK_NAME:
"CTRL/CMD + CLICK on text with [[links]] or [](links) to open them",
LINK_CTRL_CLICK_DESC:
@@ -231,7 +248,7 @@ export default {
"When Obsidian is in light mode, Excalidraw will render light mode as well. You may want to switch 'Export image with background' off for a more Obsidian-integrated look and feel.",
EMBED_WIDTH_NAME: "Default width of embedded (transcluded) image",
EMBED_WIDTH_DESC:
"Only relevant if embed type is excalidraw. Has no effect on PNG and SVG embeds. The default width of an embedded drawing. You can specify a custom " +
"The default width of an embedded drawing. This applies to live preview edit and reading mode, as well as to hover previews. You can specify a custom " +
"width when embedding an image using the <code>![[drawing.excalidraw|100]]</code> or " +
"<code>[[drawing.excalidraw|100x100]]</code> format.",
EMBED_TYPE_NAME: "Type of file to insert into the document",
@@ -240,11 +257,15 @@ export default {
"or a PNG or an SVG copy. You need to enable auto-export PNG / SVG (see below under Export Settings) for those image types to be available in the dropdown. For drawings that do not have a " +
"a corresponding PNG or SVG readily available the command palette action will insert a broken link. You need to open the original drawing and initiate export manually. " +
"This option will not autogenerate PNG/SVG files, but will simply reference the already existing files.",
EMBED_WIKILINK_NAME: "Embed SVG or PNG as Wiki link",
EMBED_WIKILINK_DESC: "Toggle ON: Excalidraw will embed a [[wiki link]]. Toggle OFF: Excalidraw will embed a [markdown](link).",
EXPORT_PNG_SCALE_NAME: "PNG export image scale",
EXPORT_PNG_SCALE_DESC: "The size-scale of the exported PNG image",
EXPORT_BACKGROUND_NAME: "Export image with background",
EXPORT_BACKGROUND_DESC:
"If turned off, the exported image will be transparent.",
EXPORT_SVG_PADDING_NAME: "SVG Padding",
EXPORT_SVG_PADDING_DESC: "The padding (in pixels) around the exported SVG image. If you have curved lines close to the edge of the image they might get cropped during SVG export. You can increase this value to avoid cropping.",
EXPORT_THEME_NAME: "Export image with theme",
EXPORT_THEME_DESC:
"Export the image matching the dark/light theme of your drawing. If turned off, " +

View File

@@ -15,6 +15,8 @@ import {
loadMathJax,
Scope,
request,
MetadataCache,
FrontMatterCache,
} from "obsidian";
import {
BLANK_DRAWING,
@@ -42,7 +44,7 @@ import {
VIRGIL_DATAURL,
} from "./constants";
import ExcalidrawView, { TextMode } from "./ExcalidrawView";
import { getMarkdownDrawingSection } from "./ExcalidrawData";
import { changeThemeOfExcalidrawMD, getMarkdownDrawingSection } from "./ExcalidrawData";
import {
ExcalidrawSettings,
DEFAULT_SETTINGS,
@@ -70,6 +72,7 @@ import {
getNewUniqueFilepath,
isObsidianThemeDark,
log,
sleep,
} from "./Utils";
import { OneOffs } from "./OneOffs";
import { FileId } from "@zsviczian/excalidraw/types/element/types";
@@ -96,6 +99,7 @@ declare module "obsidian" {
}
export default class ExcalidrawPlugin extends Plugin {
private excalidrawFiles: Set<TFile> = new Set<TFile>();
public excalidrawFileModes: { [file: string]: string } = {};
private _loaded: boolean = false;
public settings: ExcalidrawSettings;
@@ -661,7 +665,9 @@ export default class ExcalidrawPlugin extends Plugin {
this.lastActiveExcalidrawFilePath != null
);
}
this.embedDrawing(this.lastActiveExcalidrawFilePath);
const file = this.app.vault.getAbstractFileByPath(this.lastActiveExcalidrawFilePath);
if(!(file instanceof TFile)) return false;
this.embedDrawing(file);
return true;
},
});
@@ -707,7 +713,7 @@ export default class ExcalidrawPlugin extends Plugin {
)
).folder;
const file = await this.createDrawing(filename, folder);
await this.embedDrawing(file.path);
await this.embedDrawing(file);
this.openDrawing(file, inNewPane);
};
@@ -754,6 +760,55 @@ export default class ExcalidrawPlugin extends Plugin {
},
});
this.addCommand({
id: "search-text",
name: t("SEARCH"),
checkCallback: (checking: boolean) => {
if (checking) {
return (
this.app.workspace.activeLeaf.view.getViewType() ===
VIEW_TYPE_EXCALIDRAW
);
}
const view = this.app.workspace.activeLeaf.view;
if (view instanceof ExcalidrawView) {
(async ()=>{
const ea = this.ea;
ea.reset();
ea.setView(view);
const elements = ea.getViewElements().filter(el=>el.type==="text");
if(elements.length === 0) return;
let text = await ScriptEngine.inputPrompt(this.app,"Search for","use quotation marks for exact match","");
if(!text) return;
const res = text.matchAll(/"(.*?)"/g)
let query:string[] = [];
let parts;
while (!(parts = res.next()).done) {
query.push(parts.value[1]);
}
text = text.replaceAll(/"(.*?)"/g, "");
query = query.concat(text.split(" ").filter(s=>s.length!==0));
const match = elements
.filter((el:any)=>query
.some(q=>el
.rawText
.toLowerCase()
.replaceAll("\n"," ")
.match(q.toLowerCase())
));
if(match.length === 0) {
new Notice("I could not find a matching text element");
return;
}
ea.selectElementsInView(match);
ea.getExcalidrawAPI().zoomToFit(match,this.settings.zoomToFitMaxLevel,0.05);
})();
return true;
}
return false;
},
});
this.addCommand({
id: "export-png",
name: t("EXPORT_PNG"),
@@ -870,6 +925,32 @@ export default class ExcalidrawPlugin extends Plugin {
},
});
this.addCommand({
id: "tray-mode",
name: t("TRAY_MODE"),
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 && view.excalidrawAPI) {
const st = view.excalidrawAPI.getAppState();
st.trayModeEnabled = !st.trayModeEnabled;
view.excalidrawAPI.updateScene({appState:st});
//placed in an async function because I need to load settings first
//just in case settings were updated via sync
(async()=>{
await this.loadSettings();
this.settings.defaultTrayMode = st.trayModeEnabled;
this.saveSettings();
})();
return true;
}
return false;
},
});
this.addCommand({
id: "insert-md",
name: t("INSERT_MD"),
@@ -913,7 +994,7 @@ export default class ExcalidrawPlugin extends Plugin {
ea.reset();
await ea.addLaTex(0, 0, formula);
ea.setView(view);
ea.addElementsToView(true, false);
ea.addElementsToView(true, false, true);
});
return true;
}
@@ -1224,14 +1305,9 @@ export default class ExcalidrawPlugin extends Plugin {
if (!(file instanceof TFile)) {
return;
}
const isExcalidarwFile =
//@ts-ignore
(file.unsafeCachedData &&
//@ts-ignore
file.unsafeCachedData.search(
/---[\r\n]+[\s\S]*excalidraw-plugin:\s*\w+[\r\n]+[\s\S]*---/gm,
) > -1) ||
file.extension == "excalidraw";
const isExcalidarwFile = this.excalidrawFiles.has(file);
this.updateFileCache(file,undefined,true);
if (!isExcalidarwFile) {
return;
}
@@ -1249,27 +1325,31 @@ export default class ExcalidrawPlugin extends Plugin {
//delete PNG and SVG files as well
if (self.settings.keepInSync) {
[".svg", ".png", ".excalidraw"].forEach(async (ext: string) => {
const imgPath = getIMGPathFromExcalidrawFile(file.path, ext);
const imgFile = self.app.vault.getAbstractFileByPath(
normalizePath(imgPath),
);
if (imgFile && imgFile instanceof TFile) {
await self.app.vault.delete(imgFile);
}
});
setTimeout(()=>{
[".svg", ".png", ".excalidraw"].forEach(async (ext: string) => {
const imgPath = getIMGPathFromExcalidrawFile(file.path, ext);
const imgFile = self.app.vault.getAbstractFileByPath(
normalizePath(imgPath),
);
if (imgFile && imgFile instanceof TFile) {
await self.app.vault.delete(imgFile);
}
});
},500);
}
};
self.registerEvent(self.app.vault.on("delete", deleteEventHandler));
//save open drawings when user quits the application
const quitEventHandler = async () => {
//Removing because it is not guaranteed to run, and frequently gets terminated mid flight, causing file consistency issues
/*const quitEventHandler = async () => {
const leaves = self.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
for (let i = 0; i < leaves.length; i++) {
await (leaves[i].view as ExcalidrawView).save(true);
}
};
self.registerEvent(self.app.workspace.on("quit", quitEventHandler));
self.registerEvent(self.app.workspace.on("quit", quitEventHandler));*/
//save Excalidraw leaf and update embeds when switching to another leaf
const activeLeafChangeEventHandler = async (leaf: WorkspaceLeaf) => {
@@ -1340,9 +1420,36 @@ export default class ExcalidrawPlugin extends Plugin {
activeLeafChangeEventHandler,
),
);
const metaCache:MetadataCache = self.app.metadataCache;
//@ts-ignore
metaCache.getCachedFiles().forEach((filename:string) => {
const fm = metaCache.getCache(filename)?.frontmatter;
if ((fm && Object.keys(fm).contains(FRONTMATTER_KEY)) ||
filename.match(/\.excalidraw$/)
) {
self.updateFileCache(
self.app.vault.getAbstractFileByPath(filename) as TFile, fm
);
}
});
this.registerEvent(metaCache.on("changed", (file, data, cache) => this.updateFileCache(file, cache?.frontmatter)));
});
}
updateFileCache(file: TFile, frontmatter?: FrontMatterCache, deleted: boolean = false) {
if(frontmatter) {
const isExcalidrawFile = Object.keys(frontmatter).contains(FRONTMATTER_KEY);
this.excalidrawFiles.add(file);
return;
}
if(!deleted && file.extension==="excalidraw") {
this.excalidrawFiles.add(file);
return;
}
this.excalidrawFiles.delete(file);
}
onunload() {
window.removeEventListener("keydown", this.onKeyDown, false);
window.removeEventListener("keyup", this.onKeyUp, false);
@@ -1370,22 +1477,38 @@ export default class ExcalidrawPlugin extends Plugin {
//this.saveSettings();
}
public async embedDrawing(data: string) {
public async embedDrawing(file: TFile) {
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
if (activeView) {
if (activeView && activeView.file) {
const data = this.app.metadataCache.fileToLinktext(
file,
activeView.file.path,
this.settings.embedType === "excalidraw"
)
const editor = activeView.editor;
if (this.settings.embedType === "excalidraw") {
editor.replaceSelection(`![[${data}]]`);
editor.replaceSelection(this.settings.embedWikiLink
? `![[${data}]]` : `![](${encodeURI(data)})`);
editor.focus();
return;
}
const filename = `${data.substring(
0,
data.lastIndexOf("."),
)}.${this.settings.embedType.toLowerCase()}`;
await this.app.vault.create(filename, "");
const filename = getIMGPathFromExcalidrawFile(
data,"."+this.settings.embedType.toLowerCase()
);
const filepath = getIMGPathFromExcalidrawFile(
file.path,"."+this.settings.embedType.toLowerCase()
);
const imgFile = this.app.vault.getAbstractFileByPath(filepath);
if(!imgFile) {
await this.app.vault.create(filepath, "");
await sleep(200);
}
editor.replaceSelection(
`![[${filename}]]\n%%[[${data}|🖋 Edit in Excalidraw]]%%`,
this.settings.embedWikiLink
? `![[${filename}]]\n%%[[${data}|🖋 Edit in Excalidraw]]%%`
: `![](${encodeURI(filename)})\n%%[🖋 Edit in Excalidraw](${encodeURI(data)})%%`,
);
editor.focus();
}
@@ -1459,7 +1582,7 @@ export default class ExcalidrawPlugin extends Plugin {
);
}
private async getBlankDrawing(): Promise<string> {
public async getBlankDrawing(): Promise<string> {
const template = this.app.metadataCache.getFirstLinkpathDest(
normalizePath(this.settings.templateFilePath),
"",
@@ -1469,9 +1592,9 @@ export default class ExcalidrawPlugin extends Plugin {
(template.extension == "md" && !this.settings.compatibilityMode) ||
(template.extension == "excalidraw" && this.settings.compatibilityMode)
) {
const data = await this.app.vault.read(template);
let data = await this.app.vault.read(template);
if (data) {
return data;
return this.settings.matchTheme ? changeThemeOfExcalidrawMD(data) : data;
}
}
}
@@ -1484,7 +1607,7 @@ export default class ExcalidrawPlugin extends Plugin {
this.settings.matchTheme && isObsidianThemeDark()
? DARK_BLANK_DRAWING
: BLANK_DRAWING;
return `${FRONTMATTER}\n${getMarkdownDrawingSection(blank)}`;
return `${FRONTMATTER}\n${getMarkdownDrawingSection(blank,this.settings.compress)}`;
}
/**
@@ -1515,7 +1638,7 @@ export default class ExcalidrawPlugin extends Plugin {
}
return (
outString +
getMarkdownDrawingSection(JSON.stringify(JSON_parse(data), null, "\t"))
getMarkdownDrawingSection(JSON.stringify(JSON_parse(data), null, "\t"),this.settings.compress)
);
}
@@ -1579,4 +1702,5 @@ export default class ExcalidrawPlugin extends Plugin {
const fileCache = f ? this.app.metadataCache.getFileCache(f) : null;
return !!fileCache?.frontmatter && !!fileCache.frontmatter[FRONTMATTER_KEY];
}
}

View File

@@ -58,7 +58,7 @@ export class OpenFileDialog extends FuzzySuggestModal<TFile> {
this.plugin.openDrawing(item, this.onNewPane);
break;
case openDialogAction.insertLinkToDrawing:
this.plugin.embedDrawing(item.path);
this.plugin.embedDrawing(item);
break;
}
}

View File

@@ -16,6 +16,9 @@ export interface ExcalidrawSettings {
embedUseExcalidrawFolder: boolean;
templateFilePath: string;
scriptFolderPath: string;
compress: boolean;
autosave: boolean;
autosaveInterval: number;
drawingFilenamePrefix: string;
drawingEmbedPrefixWithFilename: boolean;
drawingFilenameDateTime: string;
@@ -32,6 +35,8 @@ export interface ExcalidrawSettings {
showLinkBrackets: boolean;
linkPrefix: string;
urlPrefix: string;
hoverPreviewWithoutCTRL: boolean;
linkOpacity: number;
allowCtrlClick: boolean; //if disabled only the link button in the view header will open links
forceWrap: boolean;
pageTransclusionCharLimit: number;
@@ -39,11 +44,13 @@ export interface ExcalidrawSettings {
pngExportScale: number;
exportWithTheme: boolean;
exportWithBackground: boolean;
exportPaddingSVG: number;
keepInSync: boolean;
autoexportSVG: boolean;
autoexportPNG: boolean;
autoexportExcalidraw: boolean;
embedType: "excalidraw" | "PNG" | "SVG";
embedWikiLink: boolean,
syncExcalidraw: boolean;
compatibilityMode: boolean;
experimentalFileType: boolean;
@@ -66,6 +73,7 @@ export interface ExcalidrawSettings {
mdFontColor: string;
mdCSS: string;
scriptEngineSettings: {};
defaultTrayMode: boolean;
}
export const DEFAULT_SETTINGS: ExcalidrawSettings = {
@@ -73,6 +81,9 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
embedUseExcalidrawFolder: false,
templateFilePath: "Excalidraw/Template.excalidraw",
scriptFolderPath: "Excalidraw/Scripts",
compress: false,
autosave: true,
autosaveInterval: 15000,
drawingFilenamePrefix: "Drawing ",
drawingEmbedPrefixWithFilename: true,
drawingFilenameDateTime: "YYYY-MM-DD HH.mm.ss",
@@ -87,6 +98,8 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
zoomToFitMaxLevel: 2,
linkPrefix: "📍",
urlPrefix: "🌐",
hoverPreviewWithoutCTRL: false,
linkOpacity: 1,
openInAdjacentPane: false,
showLinkBrackets: true,
allowCtrlClick: true,
@@ -96,11 +109,13 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
pngExportScale: 1,
exportWithTheme: true,
exportWithBackground: true,
exportPaddingSVG: 10,
keepInSync: false,
autoexportSVG: false,
autoexportPNG: false,
autoexportExcalidraw: false,
embedType: "excalidraw",
embedWikiLink: true,
syncExcalidraw: false,
experimentalFileType: false,
experimentalFileTag: "✏️",
@@ -128,6 +143,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
mdFontColor: "Black",
mdCSS: "",
scriptEngineSettings: {},
defaultTrayMode: false,
};
const fragWithHTML = (html: string) =>
@@ -249,7 +265,19 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
}),
);
this.containerEl.createEl("h1", { text: t("FILENAME_HEAD") });
new Setting(containerEl)
.setName(t("COMPRESS_NAME"))
.setDesc(fragWithHTML(t("COMPRESS_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.compress)
.onChange(async (value) => {
this.plugin.settings.compress = value;
this.applySettingsUpdate();
}),
);
this.containerEl.createEl("h1", { text: t("FILENAME_HEAD") });
containerEl.createDiv("", (el) => {
el.innerHTML = t("FILENAME_DESC");
});
@@ -313,6 +341,40 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
}),
);
let autosaveDropdown: DropdownComponent;
new Setting(containerEl)
.setName(t("AUTOSAVE_NAME"))
.setDesc(fragWithHTML(t("AUTOSAVE_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.autosave)
.onChange(async (value) => {
this.plugin.settings.autosave = value;
autosaveDropdown.setDisabled(!value);
this.applySettingsUpdate();
}),
);
new Setting(containerEl)
.setName(t("AUTOSAVE_INTERVAL_NAME"))
.setDesc(fragWithHTML(t("AUTOSAVE_INTERVAL_DESC")))
.addDropdown(async (d: DropdownComponent) => {
autosaveDropdown = d;
d.addOption("15000", "15 seconds");
d.addOption("30000", "30 seconds");
d.addOption("60000", "1 minute");
d.addOption("120000", "2 minutes");
d.addOption("180000", "3 minutes");
d.addOption("240000", "4 minutes");
d.addOption("300000", "5 minutes");
d.setValue(this.plugin.settings.autosaveInterval.toString())
.onChange((value) => {
this.plugin.settings.autosaveInterval = parseInt(value);
this.applySettingsUpdate(true);
});
});
this.containerEl.createEl("h1", { text: t("DISPLAY_HEAD") });
new Setting(containerEl)
@@ -456,6 +518,39 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
this.applySettingsUpdate(true);
}),
);
let opacityText: HTMLDivElement;
new Setting(containerEl)
.setName(t("LINKOPACITY_NAME"))
.setDesc(fragWithHTML(t("LINKOPACITY_DESC")))
.addSlider((slider) =>
slider
.setLimits(0, 1, 0.05)
.setValue(this.plugin.settings.linkOpacity)
.onChange(async (value) => {
opacityText.innerText = ` ${value.toString()}`;
this.plugin.settings.linkOpacity = value;
this.applySettingsUpdate(true);
}),
)
.settingEl.createDiv("", (el) => {
opacityText = el;
el.style.minWidth = "2.3em";
el.style.textAlign = "right";
el.innerText = ` ${this.plugin.settings.linkOpacity.toString()}`;
});
new Setting(containerEl)
.setName(t("HOVERPREVIEW_NAME"))
.setDesc(fragWithHTML(t("HOVERPREVIEW_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.hoverPreviewWithoutCTRL)
.onChange(async (value) => {
this.plugin.settings.hoverPreviewWithoutCTRL = value;
this.applySettingsUpdate();
}),
);
new Setting(containerEl)
.setName(t("LINK_CTRL_CLICK_NAME"))
@@ -696,6 +791,18 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
});
});
new Setting(containerEl)
.setName(t("EMBED_WIKILINK_NAME"))
.setDesc(fragWithHTML(t("EMBED_WIKILINK_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.embedWikiLink)
.onChange(async (value) => {
this.plugin.settings.embedWikiLink = value;
this.applySettingsUpdate();
}),
);
let scaleText: HTMLDivElement;
new Setting(containerEl)
@@ -731,6 +838,28 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
}),
);
let exportPadding: HTMLDivElement;
new Setting(containerEl)
.setName(t("EXPORT_SVG_PADDING_NAME"))
.setDesc(fragWithHTML(t("EXPORT_SVG_PADDING_DESC")))
.addSlider((slider) =>
slider
.setLimits(0,50,5)
.setValue(this.plugin.settings.exportPaddingSVG)
.onChange(async (value) => {
exportPadding.innerText = ` ${value.toString()}`;
this.plugin.settings.exportPaddingSVG = value;
this.applySettingsUpdate();
}),
)
.settingEl.createDiv("", (el) => {
exportPadding = el;
el.style.minWidth = "2.3em";
el.style.textAlign = "right";
el.innerText = ` ${this.plugin.settings.exportPaddingSVG.toString()}`;
});
new Setting(containerEl)
.setName(t("EXPORT_THEME_NAME"))
.setDesc(fragWithHTML(t("EXPORT_THEME_DESC")))

View File

@@ -118,7 +118,7 @@ li[data-testid] {
}
.excalidraw-scriptengine-install td.label {
width: 11ch;
min-width: 11ch;
font-weight: bold;
padding-right: 5px;
}
@@ -136,6 +136,20 @@ li[data-testid] {
max-height:90%;
}
.excalidraw-suggester-label {
text-align: left;
.excalidraw-prompt-center {
text-align: center;
}
.excalidraw-prompt-center.filepath {
text-align: center;
font-weight: bold;
margin-bottom: 2em;
}
.excalidraw-dirty {
color: red;
}
.workspace-leaf-content .excalidraw-view {
padding: 0px 1px; /*1px so on ipad swipe in from left and right still works*/
}

View File

@@ -3,7 +3,7 @@
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"module": "es2015",
"target": "es2017",
"allowJs": true,
"noImplicitAny": true,

View File

@@ -1,4 +1,4 @@
{
"1.5.22": "0.12.16",
"1.6.14": "0.12.16",
"1.4.2": "0.11.13"
}

View File

@@ -1355,6 +1355,11 @@
"schema-utils" "^3.0.0"
"source-map" "^0.7.3"
"@popperjs/core@^2.11.2":
"integrity" "sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA=="
"resolved" "https://registry.npmjs.org/@popperjs/core/-/core-2.11.2.tgz"
"version" "2.11.2"
"@rollup/plugin-babel@^5.2.0", "@rollup/plugin-babel@^5.3.0":
"integrity" "sha512-9uIC8HZOnVLrLHxayq/PTzw+uS25E14KPUBh5ktF+18Mjo5yK0ToMMx6epY0uEgkjwJw0aBW4x2horYXh8juWw=="
"resolved" "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz"
@@ -1743,6 +1748,11 @@
"resolved" "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz"
"version" "0.0.29"
"@types/lz-string@^1.3.34":
"integrity" "sha512-j6G1e8DULJx3ONf6NdR5JiR2ZY3K3PaaqiEuKYkLQO0Czfi1AzrtjfnfCROyWGeDd5IVMKCwsgSmMip9OWijow=="
"resolved" "https://registry.npmjs.org/@types/lz-string/-/lz-string-1.3.34.tgz"
"version" "1.3.34"
"@types/mime@^1":
"integrity" "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw=="
"resolved" "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz"
@@ -2122,10 +2132,10 @@
"resolved" "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz"
"version" "4.2.2"
"@zsviczian/excalidraw@0.10.0-obsidian-37":
"integrity" "sha512-FssxK/xkDzsltu81aMTLkWVd0Te9EMV7H74K6GINF2M688rTk1Up5DGKIwI1fX8Zl+OobpmEBErctLLsQmb5jQ=="
"resolved" "https://registry.npmjs.org/@zsviczian/excalidraw/-/excalidraw-0.10.0-obsidian-37.tgz"
"version" "0.10.0-obsidian-37"
"@zsviczian/excalidraw@0.11.0-obsidian-1":
"integrity" "sha512-jW2vCnNvk8/0QDBEhnVZ2yImHXkbUISCTdb+KG3yWhU5rwn8nH6tQPJGxHfnr5dN5D5TPrguh9ScqdezoKOzUw=="
"resolved" "https://registry.npmjs.org/@zsviczian/excalidraw/-/excalidraw-0.11.0-obsidian-1.tgz"
"version" "0.11.0-obsidian-1"
dependencies:
"dotenv" "10.0.0"
@@ -5861,6 +5871,11 @@
dependencies:
"yallist" "^4.0.0"
"lz-string@^1.4.4":
"integrity" "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY="
"resolved" "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz"
"version" "1.4.4"
"magic-string@^0.25.0", "magic-string@^0.25.7":
"integrity" "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA=="
"resolved" "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz"
@@ -7477,7 +7492,7 @@
dependencies:
"glob" "^7.1.3"
"rollup-plugin-terser@^7.0.0":
"rollup-plugin-terser@^7.0.0", "rollup-plugin-terser@^7.0.2":
"integrity" "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ=="
"resolved" "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz"
"version" "7.0.2"