Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ca87741a5 | ||
|
|
463eb1d228 | ||
|
|
935febf337 | ||
|
|
e4c7fe7fc9 | ||
|
|
e98b1755ee | ||
|
|
bc71bde9b7 | ||
|
|
2dc10af004 | ||
|
|
46d21f53e5 | ||
|
|
44d9b44c88 | ||
|
|
5875f268f4 | ||
|
|
bc1120ac3a | ||
|
|
40a10620c1 | ||
|
|
28295deaa0 | ||
|
|
b783d9b992 | ||
|
|
98df940f52 | ||
|
|
ab3b84a765 | ||
|
|
5fc6905ac7 | ||
|
|
00c7a0e934 | ||
|
|
60ed7f21f6 | ||
|
|
9e0c5c48d4 | ||
|
|
7e76c2b693 | ||
|
|
dd9b363427 | ||
|
|
90b693151a | ||
|
|
d280c7e953 | ||
|
|
b227ad05fd | ||
|
|
cf07458fbb | ||
|
|
c191b7264d | ||
|
|
109fe05302 | ||
|
|
d55bb1a2c4 | ||
|
|
1362c7f416 | ||
|
|
dc718bbfe1 | ||
|
|
3ab2a3d7fc | ||
|
|
494556b501 | ||
|
|
76b767f505 | ||
|
|
8178328ce8 | ||
|
|
766bf25773 | ||
|
|
ccf81efa7b | ||
|
|
4e101f8786 | ||
|
|
05a7ee8270 | ||
|
|
0e5004ae08 | ||
|
|
77f103d46e | ||
|
|
e2ad92a19c | ||
|
|
0eb3bc6798 | ||
|
|
b11cf139a8 | ||
|
|
81ce31dfbf | ||
|
|
e3f7455511 | ||
|
|
321a5e8ea1 | ||
|
|
b16362a6fd | ||
|
|
a52f633755 | ||
|
|
65fe5282c4 | ||
|
|
4efa4866ef | ||
|
|
a11c195ea4 | ||
|
|
9d31de7674 | ||
|
|
bf9e6e0179 | ||
|
|
1638cb7fc7 | ||
|
|
96c4e9a56b | ||
|
|
bfffa9609f | ||
|
|
ba4e3652da | ||
|
|
bb0d49d94f | ||
|
|
55eb54d162 | ||
|
|
2c584b50ec | ||
|
|
830f51e1ec | ||
|
|
259176e7c8 | ||
|
|
4ae38c70bf | ||
|
|
0a2ef2d751 | ||
|
|
1d830526a7 | ||
|
|
71f1072822 | ||
|
|
8029c5d4cd | ||
|
|
962c6a71f7 | ||
|
|
3e0a6e839f | ||
|
|
7aa416766c | ||
|
|
3dae930201 | ||
|
|
379c2d0e52 | ||
|
|
d9a1113f25 | ||
|
|
d0d5677c81 | ||
|
|
a364c0fe45 | ||
|
|
bf9a917af3 | ||
|
|
7d98bf691c | ||
|
|
b927a48eb3 | ||
|
|
7f2af801a9 | ||
|
|
2c2bbc2d62 | ||
|
|
f66cf344da | ||
|
|
6c4169e9c9 | ||
|
|
92f8b68445 | ||
|
|
d0bfd834c8 |
14
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -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.
|
||||
|
||||
@@ -13,7 +13,8 @@ Please upgrade to Obsidian v0.12.19 or higher to get the latest release.
|
||||
|[](https://youtu.be/VRZVujfVab0)|[](https://youtu.be/D1iBYo1_jjc)|[](https://www.youtube.com/watch?v=_c_0zpBJ4Xc&)|
|
||||
|[](https://youtu.be/r08wk-58DPk)|[](https://youtu.be/tsecSfnTMow)|[](https://youtu.be/K6qZkTz8GHs)|
|
||||
|[](https://youtu.be/hePJcObHIso)|[](https://youtu.be/NOuddK6xrr8)|[](https://youtu.be/lzYdOQ6z8F0)|
|
||||
|[](https://youtu.be/eKFmrSQhFA4)|||
|
||||
|[](https://youtu.be/eKFmrSQhFA4)|[](https://youtu.be/qbPIAZguJeo)|[](https://youtu.be/2Y8OhkGiTHg)|
|
||||
|[](https://youtu.be/2v9TZmQNO8c)|[](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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -22,4 +22,4 @@ elements.forEach((el)=>{
|
||||
);
|
||||
ea.addToGroup([el.id,ellipseId]);
|
||||
});
|
||||
ea.addElementsToView();
|
||||
ea.addElementsToView(false,false);
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||

|
||||
|
||||
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);
|
||||
65
ea-scripts/Add Link to Existing File and Open.md
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||

|
||||
|
||||
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);
|
||||
91
ea-scripts/Add Link to New Page and Open.md
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||

|
||||
|
||||
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);
|
||||
@@ -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)]);
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -78,4 +78,4 @@ ea.connectObjects(
|
||||
numberOfPoints: linePoints
|
||||
}
|
||||
);
|
||||
ea.addElementsToView(false,true,true);
|
||||
ea.addElementsToView(false,false,true);
|
||||
64
ea-scripts/Convert freedraw to line.md
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||

|
||||
|
||||
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());
|
||||
@@ -48,4 +48,4 @@ elements.forEach((el)=>{
|
||||
el.containerId = id;
|
||||
});
|
||||
ea.copyViewElementsToEAforEditing(elements);
|
||||
ea.addElementsToView();
|
||||
ea.addElementsToView(false,false);
|
||||
@@ -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 =
|
||||
|
||||
@@ -1,56 +1,56 @@
|
||||
/*
|
||||
|
||||

|
||||
|
||||
Download this file and save to your Obsidian Vault including the first line, or open it in "Raw" and copy the entire contents to Obsidian.
|
||||
|
||||

|
||||
|
||||
This script 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();
|
||||
/*
|
||||
|
||||

|
||||
|
||||
Download this file and save to your Obsidian Vault including the first line, or open it in "Raw" and copy the entire contents to Obsidian.
|
||||
|
||||

|
||||
|
||||
This script 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);
|
||||
|
||||
@@ -1,146 +1,146 @@
|
||||
/*
|
||||
|
||||

|
||||
|
||||
Download this file and save to your Obsidian Vault including the first line, or open it in "Raw" and copy the entire contents to Obsidian.
|
||||
|
||||

|
||||
|
||||
This script 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];
|
||||
}
|
||||
/*
|
||||
|
||||

|
||||
|
||||
Download this file and save to your Obsidian Vault including the first line, or open it in "Raw" and copy the entire contents to Obsidian.
|
||||
|
||||

|
||||
|
||||
This script 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];
|
||||
}
|
||||
}
|
||||
@@ -1,133 +1,133 @@
|
||||
/*
|
||||
|
||||

|
||||
|
||||
Download this file and save to your Obsidian Vault including the first line, or open it in "Raw" and copy the entire contents to Obsidian.
|
||||
|
||||

|
||||
|
||||
This script 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];
|
||||
}
|
||||
/*
|
||||
|
||||

|
||||
|
||||
Download this file and save to your Obsidian Vault including the first line, or open it in "Raw" and copy the entire contents to Obsidian.
|
||||
|
||||

|
||||
|
||||
This script 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];
|
||||
}
|
||||
}
|
||||
@@ -1,146 +1,146 @@
|
||||
/*
|
||||
|
||||

|
||||
|
||||
Download this file and save to your Obsidian Vault including the first line, or open it in "Raw" and copy the entire contents to Obsidian.
|
||||
|
||||

|
||||
|
||||
This script 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];
|
||||
}
|
||||
/*
|
||||
|
||||

|
||||
|
||||
Download this file and save to your Obsidian Vault including the first line, or open it in "Raw" and copy the entire contents to Obsidian.
|
||||
|
||||

|
||||
|
||||
This script 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];
|
||||
}
|
||||
}
|
||||
@@ -1,131 +1,131 @@
|
||||
/*
|
||||
|
||||

|
||||
|
||||
Download this file and save to your Obsidian Vault including the first line, or open it in "Raw" and copy the entire contents to Obsidian.
|
||||
|
||||

|
||||
|
||||
This script 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];
|
||||
}
|
||||
/*
|
||||
|
||||

|
||||
|
||||
Download this file and save to your Obsidian Vault including the first line, or open it in "Raw" and copy the entire contents to Obsidian.
|
||||
|
||||

|
||||
|
||||
This script 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];
|
||||
}
|
||||
}
|
||||
@@ -69,4 +69,4 @@ for(var i=0; i<groups.length; i++) {
|
||||
}
|
||||
}
|
||||
ea.copyViewElementsToEAforEditing(elements);
|
||||
ea.addElementsToView(false, false);
|
||||
await ea.addElementsToView(false, false);
|
||||
|
||||
@@ -1,126 +1,126 @@
|
||||
/*
|
||||

|
||||
|
||||
Download this file and save to your Obsidian Vault including the first line, or open it in "Raw" and copy the entire contents to Obsidian.
|
||||
|
||||

|
||||
|
||||
This script 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);
|
||||
/*
|
||||

|
||||
|
||||
Download this file and save to your Obsidian Vault including the first line, or open it in "Raw" and copy the entire contents to Obsidian.
|
||||
|
||||

|
||||
|
||||
This script 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);
|
||||
|
||||
@@ -64,4 +64,4 @@ for(var i=0; i<groups.length; i++) {
|
||||
}
|
||||
}
|
||||
ea.copyViewElementsToEAforEditing(elements);
|
||||
ea.addElementsToView(false, false);
|
||||
await ea.addElementsToView(false, false);
|
||||
|
||||
@@ -71,4 +71,4 @@ for(var i=0; i<groups.length; i++) {
|
||||
}
|
||||
|
||||
ea.copyViewElementsToEAforEditing(elements);
|
||||
ea.addElementsToView(false, false);
|
||||
await ea.addElementsToView(false, false);
|
||||
@@ -64,4 +64,4 @@ for(var i=0; i<groups.length; i++) {
|
||||
}
|
||||
}
|
||||
ea.copyViewElementsToEAforEditing(elements);
|
||||
ea.addElementsToView(false, false);
|
||||
await ea.addElementsToView(false, false);
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -64,7 +64,7 @@ ea.getElements().forEach((el)=>{
|
||||
}
|
||||
}
|
||||
});
|
||||
ea.addElementsToView(false, false);
|
||||
await ea.addElementsToView(false, false);
|
||||
|
||||
function colorNameToHex(color) {
|
||||
const colors = {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
27
ea-scripts/Organic Line.md
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||

|
||||
|
||||
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));
|
||||
@@ -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.||[@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.||[@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.||[@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.||[@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.||[@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.||[@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.||[@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.||[@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).||[@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||[@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).||[@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||[@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.||[@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.||[@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.||[@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.||[@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.||[@zsviczian](https://github.com/zsviczian)|
|
||||
|[Elbow connectors](Elbow%20connectors.md)|This script converts the selected connectors to elbows.||[@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.||[@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.||[@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.||[@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.||[@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.||[@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.||[@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.||[@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.||[@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.||[@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.||[@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.||[@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.||[@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.||[@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.||[@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.||[@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.||[@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.||[@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.||[@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.||[@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.||[@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.||[@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.||[@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.||[@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||[@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.||[@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!||[@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.||[@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)|
|
||||
|
||||
378
ea-scripts/Repeat Elements.md
Normal file
@@ -0,0 +1,378 @@
|
||||
/*
|
||||
|
||||

|
||||
|
||||
Download this file and save to your Obsidian Vault including the first line, or open it in "Raw" and copy the entire contents to Obsidian.
|
||||
|
||||

|
||||
|
||||
This script will 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;
|
||||
}
|
||||
@@ -20,4 +20,4 @@ elements.forEach((el)=>{
|
||||
el.endArrowhead = start;
|
||||
});
|
||||
ea.copyViewElementsToEAforEditing(elements);
|
||||
ea.addElementsToView();
|
||||
ea.addElementsToView(false,false);
|
||||
29
ea-scripts/Scribble Helper.md
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||

|
||||
|
||||
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);
|
||||
47
ea-scripts/Select Elements of Type.md
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||

|
||||
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));
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||

|
||||
|
||||
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());
|
||||
@@ -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);
|
||||
49
ea-scripts/Toggle Fullscreen on Mobile.md
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||

|
||||
|
||||
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);
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
{
|
||||
"minifyWhitespace": true,
|
||||
"minifySyntax":true
|
||||
"minify": true
|
||||
}
|
||||
BIN
images/ea-toggle-fullscreen.jpg
Normal file
|
After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 102 KiB |
BIN
images/scripts-add-link-to-new-page-and-pen.jpg
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
images/scripts-convert-freedraw-to-line.jpg
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
images/scripts-organic-line.jpg
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
images/scripts-repeat-elements.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
images/scripts-scribble-helper.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
images/scripts-select-element-of-type.jpg
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
images/scripts-set-background-color-of-unclosed-line.jpg
Normal file
|
After Width: | Height: | Size: 45 KiB |
@@ -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",
|
||||
|
||||
12
package.json
@@ -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",
|
||||
|
||||
@@ -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}})
|
||||
] : []
|
||||
],
|
||||
};
|
||||
@@ -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 "";
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
];
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
})();
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
})();
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
};
|
||||
|
||||
189
src/Prompt.ts
@@ -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();
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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: ': ""',
|
||||
},
|
||||
];
|
||||
27
src/Utils.ts
@@ -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 });
|
||||
};
|
||||
|
||||
197
src/constants.ts
@@ -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"/>`;
|
||||
|
||||
@@ -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, " +
|
||||
|
||||
198
src/main.ts
@@ -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}]]` : `})`);
|
||||
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]]%%`
|
||||
: `})\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];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
131
src/settings.ts
@@ -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")))
|
||||
|
||||
20
styles.css
@@ -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*/
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
"baseUrl": ".",
|
||||
"inlineSourceMap": true,
|
||||
"inlineSources": true,
|
||||
"module": "ESNext",
|
||||
"module": "es2015",
|
||||
"target": "es2017",
|
||||
"allowJs": true,
|
||||
"noImplicitAny": true,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"1.5.22": "0.12.16",
|
||||
"1.6.14": "0.12.16",
|
||||
"1.4.2": "0.11.13"
|
||||
}
|
||||
|
||||
25
yarn.lock
@@ -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"
|
||||
|
||||