Compare commits

..

50 Commits

Author SHA1 Message Date
Zsolt Viczian
e708be8eda 1.7.6 2022-07-02 20:51:37 +02:00
Zsolt Viczian
ea39b8c6a1 1.7.5 2022-07-02 17:54:27 +02:00
Zsolt Viczian
e5fb705f0b 7.1.5 - embedding fixes 2022-07-02 14:07:43 +02:00
Zsolt Viczian
8046b5dc1f 1.7.4 2022-07-01 17:58:43 +02:00
Zsolt Viczian
08e4e1f131 1.7.3 beta release 2022-06-24 10:16:38 +02:00
Zsolt Viczian
108293ae5c minor cleanup 2022-06-23 18:16:27 +02:00
Zsolt Viczian
73528596d2 1.7.2 2022-06-23 07:19:17 +02:00
Zsolt Viczian
d2284b8d14 1.7.2 WIP 2022-06-22 23:50:19 +02:00
Zsolt Viczian
7cd3ec40c6 replaced activeLeaf, added openInPopout 2022-06-22 19:59:03 +02:00
Zsolt Viczian
80cbb41913 1.7.1 2022-06-18 20:28:23 +02:00
Zsolt Viczian
257f3d17ac 1.7.0 2022-06-17 22:26:26 +02:00
Zsolt Viczian
2e6b76b0f3 First round cleanup of document and window references 2022-06-17 09:36:57 +02:00
Zsolt Viczian
9030a77914 1.6.34 2022-06-16 19:38:46 +02:00
Zsolt Viczian
461b472f20 fix text detaches from container 2022-06-15 22:21:21 +02:00
zsviczian
6bacae16b2 Update README.md 2022-06-02 15:07:19 +02:00
zsviczian
fa0f388e4a Update README.md 2022-06-02 14:46:49 +02:00
zsviczian
afc4f614b7 #643 2022-06-02 13:24:24 +02:00
zsviczian
9e05493a9d #643 2022-06-02 13:23:18 +02:00
Zsolt Viczian
a20900749d 1.6.33 2022-05-28 17:45:35 +02:00
Zsolt Viczian
461eeafd80 fix update embed #637. new function: addLabelToLine() 2022-05-28 08:29:59 +02:00
Zsolt Viczian
5ff8caa04f 1.6.32 2022-05-23 21:42:16 +02:00
Zsolt Viczian
9051fe2c01 1.6.31 2022-05-21 15:54:43 +02:00
Zsolt Viczian
018e42b34b Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2022-05-18 21:46:18 +02:00
Zsolt Viczian
b29e79f2b4 1.6.30 2022-05-18 21:45:23 +02:00
zsviczian
1ce0051d4e Update directory-info.json 2022-05-16 13:40:20 +02:00
zsviczian
c44a4a91b3 Update Change shape of selected elements.md 2022-05-16 13:39:07 +02:00
zsviczian
2c5a4c01d2 Update index-new.md 2022-05-16 12:49:28 +02:00
zsviczian
668c4d62e9 Update directory-info.json 2022-05-16 12:48:06 +02:00
zsviczian
0ce4f5f16b Update Change shape of selected elements.md 2022-05-16 12:45:31 +02:00
zsviczian
56d0966d4e Add files via upload 2022-05-16 12:43:42 +02:00
Zsolt Viczian
e000c6bf39 1.6.29 2022-05-15 19:18:20 +02:00
Zsolt Viczian
feb5b576cb Worksapces 2022-05-14 21:53:16 +02:00
Zsolt Viczian
2bd1f68276 fixed workspaces 2022-05-14 21:52:55 +02:00
Zsolt Viczian
22d1a9b90a sync 2022-05-14 20:46:50 +02:00
Zsolt Viczian
00a6a3dcaf fixed null width, sync in progress 2022-05-13 22:20:27 +02:00
Zsolt Viczian
82061cd126 sync wip 2022-05-11 22:03:20 +02:00
Zsolt Viczian
4bdd095ff0 version.json 2022-05-08 10:38:05 +02:00
Zsolt Viczian
f40ad62291 1.6.28 2022-05-08 10:34:56 +02:00
Zsolt Viczian
de4fc602ca dataview onDrop improvement, getNewLeaf in openDrawing 2022-05-08 00:05:39 +02:00
Zsolt Viczian
f5faec8ac9 fix text element de-select on autosave 2022-05-04 18:52:41 +02:00
Zsolt Viczian
8834762004 1.6.27 2022-05-01 20:30:56 +02:00
Zsolt Viczian
48477c7ee9 onload-script, isExcalidrawView 2022-04-30 20:29:14 +02:00
Zsolt Viczian
f0f65fb9a3 1.6.26 2022-04-30 17:47:08 +02:00
Zsolt Viczian
26e12e8cec ready to release 2022-04-30 16:31:53 +02:00
Zsolt Viczian
fa2fe5e462 ExcalidrawAutomate to Class, published npmjs lib package 2022-04-21 22:29:14 +02:00
Zsolt Viczian
83a70828ae fixed tag error 2022-04-17 19:36:40 +02:00
Zsolt Viczian
b2e246bdf4 update the script 2022-04-17 14:10:35 +02:00
Zsolt Viczian
fd0f39c214 publish new TheBrain script 2022-04-17 14:09:16 +02:00
Zsolt Viczian
2862fbc983 Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2022-04-16 16:10:16 +02:00
Zsolt Viczian
5f42ed8f8d 1.6.25 2022-04-16 16:09:47 +02:00
43 changed files with 4940 additions and 2591 deletions

4
.gitignore vendored
View File

@@ -12,3 +12,7 @@ main.js
stats.html
hot-reload.bat
data.json
lib
#VSCode
.vscode

View File

@@ -17,6 +17,10 @@ Please upgrade to Obsidian v0.12.19 or higher to get the latest release.
|[![fourtfont](https://user-images.githubusercontent.com/14358394/149659524-2a4e0a24-40c9-4e66-a6b1-c92f3b88ecd5.jpg)](https://youtu.be/eKFmrSQhFA4)|[![thumbnail](https://user-images.githubusercontent.com/14358394/151705333-54e9ffd2-0bd7-4d02-b99e-0bd4e4708d4d.jpg)](https://youtu.be/qbPIAZguJeo)|[![Thumbnail](https://user-images.githubusercontent.com/14358394/152585752-7eb0371f-0bab-40f6-a194-3b48e5811735.jpg)](https://youtu.be/2Y8OhkGiTHg)|
|[![Thumbnail](https://user-images.githubusercontent.com/14358394/153676009-6f86b2d7-c248-49a2-b802-be21c6999e4f.jpg)](https://youtu.be/2v9TZmQNO8c)|[![Thumbnail](https://user-images.githubusercontent.com/14358394/154821232-a404b6cf-72fb-4ce4-9d53-619132dce491.jpg)](https://youtu.be/xHPGWR3m0c8)|[![Thumbnail](https://user-images.githubusercontent.com/14358394/156931428-b2269fd9-87bd-43ab-8558-5572f40dff93.jpg)](https://youtu.be/gMIKXyhS-dM)|
|[![thumbnail](https://user-images.githubusercontent.com/14358394/156931461-0979b821-315a-41dd-86f1-31d169b7c127.jpg)](https://youtu.be/Etskjw7a5zo)|[![Thumbnail](https://user-images.githubusercontent.com/14358394/158008902-12c6a851-237e-4edd-a631-d48e81c904b2.jpg)](https://youtu.be/4N6efq1DtH0)|[![thumbnail](https://user-images.githubusercontent.com/14358394/159369910-6371f08d-b5fa-454d-9c6c-948f7e7a7d26.jpg)](https://youtu.be/U2LkBRBk4LY)|
| [![6 strategies for linking your visual thoughts v4](https://user-images.githubusercontent.com/14358394/171635214-30533c45-94fa-436e-83a9-b2ec99f190e2.jpg)](https://youtu.be/qiKuqMcNWgU)| | |
# Key features
- The plugin aims to integrate Excalidraw seamlessly into Obsidian including Command Palette actions, File Explorer features, Option Menu commands, and the Ribbon Button.
@@ -87,14 +91,6 @@ Please upgrade to Obsidian v0.12.19 or higher to get the latest release.
- REQUIRES AN OBSIDIAN SYNC SUBSCRIPTION: Full drawing file history and synchronization between devices
- Multilanguage support: if you'd like to help out by translating the plugin, please get in contact with me.
# Known issues
- Mobile support
- Partially mitigated in 1.0.10 by the introduction of autosave: Your drawing will not be saved when you terminate the mobile app by closing the Obsidian task.
- Text elements "jumps off screen" when editing, if drawing is zoomed in and text element does not fit the visible screen area. I am working on a resolution.
# Tips and tricks
- [Ozan's Image in Editor Plugin](https://github.com/ozntel/oz-image-in-editor-obsidian). In a nice collaboration with Ozan, his Image-in-Editor plugin now supports Excalidraw. I recommend installing his plugin to display drawings also in Edit mode.
# Feedback, questions, ideas, problems
Join the conversation about the Excalidraw plugin on [forum.obsidian.md](https://forum.obsidian.md/t/excalidraw-full-featured-sketching-plugin-in-obsidian)
@@ -108,3 +104,9 @@ Please also help spread the word by sharing about the Obsidian Excalidraw Plugin
You can find me on Twitter [@zsviczian](https://twitter.com/zsviczian), and on my blog [zsolt.blog](https://zsolt.blog).
[<img style="float:left" src="https://user-images.githubusercontent.com/14358394/115450238-f39e8100-a21b-11eb-89d0-fa4b82cdbce8.png" width="200">](https://ko-fi.com/zsolt)
# Friends of Excalidraw
If you enjoy Excalidraw, consider giving [ExcaliBrain](https://github.com/zsviczian/excalibrain) a try (also available via Obsidian Community Plugins).
[![thumbnail](https://user-images.githubusercontent.com/14358394/169708346-9e41289d-9536-43ec-8f70-2d2ad2d369d6.png)](https://youtu.be/gOkniMkDPyM)

36
dev.postprocess.config.js Normal file
View File

@@ -0,0 +1,36 @@
import fs from'fs';
import LZString from 'lz-string';
const excalidraw_pkg = isProd
? fs.readFileSync("./node_modules/@zsviczian/excalidraw/dist/excalidraw.production.min.js", "utf8")
: fs.readFileSync("./node_modules/@zsviczian/excalidraw/dist/excalidraw.development.js", "utf8");
const react_pkg = isProd
? fs.readFileSync("./node_modules/react/umd/react.production.min.js", "utf8")
: fs.readFileSync("./node_modules/react/umd/react.development.js", "utf8");
const reactdom_pkg = isProd
? fs.readFileSync("./node_modules/react-dom/umd/react-dom.production.min.js", "utf8")
: fs.readFileSync("./node_modules/react-dom/umd/react-dom.development.js", "utf8");
const lzstring_pkg = fs.readFileSync("./node_modules/lz-string/libs/lz-string.min.js", "utf8")
const packageString = lzstring_pkg+'const EXCALIDRAW_PACKAGES = "' + LZString.compressToBase64(react_pkg + reactdom_pkg + excalidraw_pkg) +'";var ExcalidrawPackageLoader=(d=document)=>{const excalidraw_id = "excalidraw-script";if(!d.getElementById(excalidraw_id)){const script=d.createElement("script");script.type="text/javascript";script.id=excalidraw_id;script.text=LZString.decompressFromBase64(EXCALIDRAW_PACKAGES);d.body.appendChild(script);}};ExcalidrawPackageLoader();';
const mainjs = fs.readFileSync("main.js", "utf8")
fs.writeFileSync(
"main2.js",
mainjs
.replace('(require("react"));','')
.replace('"use strict";','"use strict";' + packageString),
{
encoding: "utf8",
flag: "w",
mode: 0o666
}
);
let config = {
};
export default config;

View File

@@ -11,8 +11,6 @@ if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.6.1")) {
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"]) {
@@ -60,8 +58,9 @@ 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 blank = await app.plugins.plugins["obsidian-excalidraw-plugin"].getBlankDrawing();
await app.vault.modify(file,blank);
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)}]]`;
@@ -88,4 +87,4 @@ if(openInCurrentPane) {
app.workspace.openLinkText(file.path,ea.targetView.file.path,false);
return;
}
ea.openFileInNewOrAdjacentLeaf(file);
ea.openFileInNewOrAdjacentLeaf(file);

View File

@@ -1,10 +1,13 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-change-shape.jpg)
The script allows you to change the shape of selected Rectangles, Diamonds and Ellipses.
The script allows you to change the shape and fill style of selected Rectangles, Diamonds, Ellipses, Lines, Arrows and Freedraw.
```javascript
*/
const fillStylesDispaly=["Dots (⚠ VERY SLOW performance on large objects!)","Zigzag","Zigzag-line", "Dashed", "Hachure", "Cross-hatch", "Solid"];
const fillStyles=["dots","zigzag","zigzag-line", "dashed", "hachure", "cross-hatch", "solid"];
const fillShapes=["ellipse","rectangle","diamond", "freedraw", "line"];
const boxShapesDispaly=["○ ellipse","□ rectangle","◇ diamond"];
const boxShapes=["ellipse","rectangle","diamond"];
const lineShapesDispaly=["- line","⭢ arrow"];
@@ -14,18 +17,27 @@ 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");
newShape = await utils.suggester(boxShapesDispaly, boxShapes, "Change shape of 'box' type elements in selection, press ESC to skip");
if(newShape) {
editedElements = elements;
elements.forEach(el=>el.type = newShape);
}
}
elements = ea.getViewSelectedElements().filter(el=>fillShapes.contains(el.type));
if (elements.length>0) {
newFillStyle = await utils.suggester(fillStylesDispaly, fillStyles, "Change the fill style of elements in selection, press ESC to skip");
if(newFillStyle) {
editedElements = editedElements.concat(elements.filter(e=>!editedElements.some(el=>el.id===e.id)));
elements.forEach(el=>el.fillStyle = newFillStyle);
}
}
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");
newShape = await utils.suggester(lineShapesDispaly, lineShapes, "Change shape of 'line' type elements in selection, press ESC to skip");
if(newShape) {
editedElements = editedElements.concat(elements);
editedElements = editedElements.concat(elements.filter(e=>!editedElements.some(el=>el.id===e.id)));
elements.forEach((el)=>{
el.type = newShape;
if(newShape === "arrow") {
@@ -37,4 +49,4 @@ if (elements.length>0) {
ea.copyViewElementsToEAforEditing(editedElements);
ea.addElementsToView(false,false);
ea.addElementsToView(false,false);

View File

@@ -55,7 +55,7 @@ Open the script you are interested in and save it to your Obsidian Vault includi
|[Set stroke width of selected elements](Set%20Stroke%20Width%20of%20Selected%20Elements.md)|This script will set the stroke width of selected elements. This is helpful, for example, when you scale freedraw sketches and want to reduce or increase their line width.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-stroke-width.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Split text by lines](Split%20text%20by%20lines.md)|Split lines of text into separate text elements for easier reorganization|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-split-lines.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Set Text Alignment](Set%20Text%20Alignment.md)|Sets text alignment of text block (cetner, right, left). Useful if you want to set a keyboard shortcut for selecting text alignment.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-text-align.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[TheBrain-navigation](TheBrain-navigation.md)|An Excalidraw based graph user interface for your Vault. Requires the [Breadcrumbs plugin](https://github.com/SkepticMystic/breadcrumbs) to be installed and configured as well. Generates a user interface similar to that of [TheBrain](https://TheBrain.com). Watch this introduction to this script on [YouTube](https://youtu.be/J4T5KHERH_o).|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/TheBrain.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[TheBrain-navigation](TheBrain-navigation.md)|An Excalidraw based graph user interface for your Vault. Requires the [Dataview plugin](https://github.com/blacksmithgu/obsidian-dataview). Generates a graph view similar to that of [TheBrain](https://TheBrain.com) plex. Watch introduction to this script on [YouTube](https://youtu.be/plYobK-VufM).|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/TheBrain.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Toggle Fullscreen on Mobile](Toggle%20Fullscreen%20on%20Mobile.md)|Hides Obsidian workspace leaf padding and header (based on option in settings, default is "hide header" = false) which will take Excalidraw to full screen. ⚠ Note that if the header is not visible, it will be very difficult to invoke the command palette to end full screen. Only hide the header if you have a keyboard or you've practiced opening command palette!|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/ea-toggle-fullscreen.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Transfer TextElements to Excalidraw markdown metadata](Transfer%20TextElements%20to%20Excalidraw%20markdown%20metadata.md)|The script will delete the selected text elements from the canvas and will copy the text from these text elements into the Excalidraw markdown file as metadata. This means, that the text will no longer be visible in the drawing, however you will be able to search for the text in Obsidian and find the drawing containing this image.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-text-to-metadata.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Zoom to Fit Selected Elements](Zoom%20to%20Fit%20Selected%20Elements.md)|Similar to Excalidraw standard SHIFT+2 feature: Zoom to fit selected elements, but with the ability to zoom to 1000%. Inspiration: [#272](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/272)||[@zsviczian](https://github.com/zsviczian)|

View File

@@ -1,21 +1,20 @@
/*
An Excalidraw based graph user interface for your Vault. Requires the [Dataview plugin](https://github.com/blacksmithgu/obsidian-dataview). Generates a graph view similar to that of [TheBrain](https://TheBrain.com) plex.
An Excalidraw based graph user interface for your Vault. Requires the [Breadcrumbs plugin](https://github.com/SkepticMystic/breadcrumbs) to be installed and configured as well. Generates a user interface similar to that of [TheBrain](https://TheBrain.com).
Watch introduction to this script on [YouTube](https://youtu.be/J4T5KHERH_o).
Watch introduction to this script on [YouTube](https://youtu.be/plYobK-VufM).
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/TheBrain.jpg)
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.6.24")) {
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.6.25")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
if(!BCAPI) {
new Notice("Breadcrumbs API not found! Install and activate the Breadcrumbs plugin",4000);
if(!window.DataviewAPI) {
new Notice ("Some features will only work if you have the Dataview plugin installed and enabled", 4000);
return;
}
@@ -25,11 +24,14 @@ const removeEventHandler = () => {
app.workspace.off(EVENT,window.brainGraphEventHandler);
if(isBoolean(window.excalidrawView?.linksAlwaysOpenInANewPane)) {
window.excalidrawView.linksAlwaysOpenInANewPane = false;
const ea = ExcalidrawAutomate;
ea.setView(window.excalidrawView);
if(ea.targetView?.getExcalidrawAPI) {
}
const ea = ExcalidrawAutomate;
ea.setView(window.excalidrawView);
if(ea.targetView?.excalidrawAPI) {
try {
ea.targetView.semaphores.saving = false;
ea.getExcalidrawAPI().updateScene({appState:{viewModeEnabled:false}});
}
} catch {}
}
delete window.excalidrawView;
delete window.excalidrawFile;
@@ -45,33 +47,35 @@ if(window.brainGraphEventHandler) {
}
//-------------------------------------------------------
// Settings
// Load Settings
//-------------------------------------------------------
let saveSettings = false;
settings = ea.getScriptSettings();
//set default values on first run
if(!settings["Max number of nodes/domain"]) {
settings = {
"Confirmation prompt at startup": {
value: true,
description: "Prompt me to confirm starting of the script because it will overwrite the current active drawing. " +
description: "Prompt me to confirm starting of the script because " +
"it will overwrite the current active drawing. " +
"You can disable this warning by turning off this switch"
},
"Max number of nodes/domain": {
value: 40,
value: 30,
description: "Maximum number of items to show in each domain: parents, children, siblings, jumps."
},
"Infer non-Breadcrumbs links": {
value: true,
description: "Links on the page are children, backlinks to the page are parents. Breadcrumbs take priority."
description: "Links on the page are children, backlinks to the page are " +
"parents. Breadcrumbs take priority. Inferred nodes have a dashed border."
},
"Hide attachments": {
value: true,
description: "Hide attachments. Will only have an effect if Infer non-Breadcrumbs links is turned on."
},
"Font family": {
value: "Code",
valueset: ["Hand-drawn","Normal","Code","Fourth (custom) Font"]
value: "Code",
valueset: ["Hand-drawn","Normal","Code","Fourth (custom) Font"]
},
"Stroke roughness": {
value: "Architect",
@@ -122,7 +126,7 @@ if(!settings["Max number of nodes/domain"]) {
description: "Any legal HTML color (#000000, rgb, color-name, etc.)."
},
"Central-node background color": {
value: "#dfaf16",
value: "#C49A13",
description: "Any legal HTML color (#000000, rgb, color-name, etc.)."
},
"Central-node color": {
@@ -154,8 +158,52 @@ if(!settings["Max number of nodes/domain"]) {
description: "Any legal HTML color (#000000, rgb, color-name, etc.)."
}
};
};
ea.setScriptSettings(settings);
saveSettings = true;
}
if(!settings["Display alias if available"]) {
settings["Display alias if available"] = {
value: true,
description: "Displays the page alias instead of the " +
"filename if it is specified in the page's front matter. "
};
saveSettings = true;
}
if(!settings["Graph settings JSON"]) {
settings["Graph settings JSON"] = {
height: "450px",
value: `{\n "breadcrumbs": {\n "down": ["children", "child"],\n "up": ["parents", "parent"],\n "jump": ["jump", "jumps"]\n },\n "tags": {\n "#excalidraw": {\n "nodeColor": "hsl(59, 80%, 77%)",\n "gateColor": "#fd7e14",\n "borderColor": "black",\n "backgroundColor": "rgba(50,50,50,0.5)",\n "prefix": "🎨 "\n },\n "#dnp": {\n "prefix": "🗓 "\n }\n }\n}`,
description: `This may contain two elements:
<ol>
<li>A specification of your breadcrumbs hierarchy. Note, that if you have the Breadcrumbs plugin installed and enabled then <code>TheBrain-navigation</code> script will take your hierarchy settings from Breadcrumbs. If Breadcrumbs is disabled, this specification will be used.</li>
<li>Formatting of nodes based on page tags. You can specify special formatting rules for tags. If multiple tags are present on the page the first matching a specification will be used. You may provide partial specifications as well. e.g. if you only specify <code>prefix</code>, the other attributes will follow your default settings.</li>
</ol>
<div style="user-select: text;background: #202020; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;"><pre style="margin: 0; line-height: 125%"><span style="color: #d0d0d0">{</span>
<span style="color: #6ab825; font-weight: bold">&quot;breadcrumbs&quot;</span><span style="color: #d0d0d0">:</span> <span style="color: #d0d0d0">{</span>
<span style="color: #6ab825; font-weight: bold">&quot;down&quot;</span><span style="color: #d0d0d0">:</span> <span style="color: #d0d0d0">[</span><span style="color: #ed9d13">&quot;children&quot;</span><span style="color: #d0d0d0">,</span> <span style="color: #ed9d13">&quot;child&quot;</span><span style="color: #d0d0d0">],</span>
<span style="color: #6ab825; font-weight: bold">&quot;up&quot;</span><span style="color: #d0d0d0">:</span> <span style="color: #d0d0d0">[</span><span style="color: #ed9d13">&quot;parents&quot;</span><span style="color: #d0d0d0">,</span> <span style="color: #ed9d13">&quot;parent&quot;</span><span style="color: #d0d0d0">],</span>
<span style="color: #6ab825; font-weight: bold">&quot;jump&quot;</span><span style="color: #d0d0d0">:</span> <span style="color: #d0d0d0">[</span><span style="color: #ed9d13">&quot;jump&quot;</span><span style="color: #d0d0d0">,</span> <span style="color: #ed9d13">&quot;jumps&quot;</span><span style="color: #d0d0d0">]</span>
<span style="color: #d0d0d0">},</span>
<span style="color: #6ab825; font-weight: bold">&quot;tags&quot;</span><span style="color: #d0d0d0">:</span> <span style="color: #d0d0d0">{</span>
<span style="color: #6ab825; font-weight: bold">&quot;#excalidraw&quot;</span><span style="color: #d0d0d0">:</span> <span style="color: #d0d0d0">{</span>
<span style="color: #6ab825; font-weight: bold">&quot;nodeColor&quot;</span><span style="color: #d0d0d0">:</span> <span style="color: #ed9d13">&quot;hsl(59, 80%, 77%)&quot;</span><span style="color: #d0d0d0">,</span>
<span style="color: #6ab825; font-weight: bold">&quot;gateColor&quot;</span><span style="color: #d0d0d0">:</span> <span style="color: #ed9d13">&quot;#fd7e14&quot;</span><span style="color: #d0d0d0">,</span>
<span style="color: #6ab825; font-weight: bold">&quot;borderColor&quot;</span><span style="color: #d0d0d0">:</span> <span style="color: #ed9d13">&quot;black&quot;</span><span style="color: #d0d0d0">,</span>
<span style="color: #6ab825; font-weight: bold">&quot;backgroundColor&quot;</span><span style="color: #d0d0d0">:</span> <span style="color: #ed9d13">&quot;rgba(50,50,50,0.5)&quot;</span><span style="color: #d0d0d0">,</span>
<span style="color: #6ab825; font-weight: bold">&quot;prefix&quot;</span><span style="color: #d0d0d0">:</span> <span style="color: #ed9d13">&quot;🎨 &quot;</span>
<span style="color: #d0d0d0">},</span>
<span style="color: #6ab825; font-weight: bold">&quot;#dnp&quot;</span><span style="color: #d0d0d0">:</span> <span style="color: #d0d0d0">{</span>
<span style="color: #6ab825; font-weight: bold">&quot;prefix&quot;</span><span style="color: #d0d0d0">:</span> <span style="color: #ed9d13">&quot;🗓 &quot;</span>
<span style="color: #d0d0d0">}</span>
<span style="color: #d0d0d0">}</span>
<span style="color: #d0d0d0">}</span>
</pre></div>`
};
saveSettings = true;
}
if(saveSettings) {
ea.setScriptSettings(settings);
}
const SHOW_CONFIRMATION_PROMPT = settings["Confirmation prompt at startup"].value;
const MAX_ITEMS = Math.floor(settings["Max number of nodes/domain"].value)??40;
@@ -192,13 +240,49 @@ const OBSIDIAN_NODE_BG_COLOR = settings["Non-breadcrumbs-node background color"]
const OBSIDIAN_NODE_COLOR = settings["Non-breadcrumbs-node color"].value;
const VIRTUAL_NODE_BG_COLOR = settings["Virtual-node background color"].value;
const VIRTUAL_NODE_COLOR = settings["Virtual-node color"].value;
const USE_ALIAS = settings["Display alias if available"].value;
let formattingJSON = {};
try {
formattingJSON = JSON.parse(settings["Graph settings JSON"].value);
} catch (e) {
new Notice("Error reading graph settings JSON, see developer console for more information",4000);
console.log(e);
};
const NODE_FORMATTING = formattingJSON?.tags??{};
const FORMATTED_TAGS = Object.keys(NODE_FORMATTING);
//-------------------------------------------------------
// Load breadcrumbs hierarchies
const HIERARCHIES = new Map();
if(window.BCAPI) { //read breadcrumbs if available
const getHierarchyFields = (direction) => {
const values = new Set(direction);
direction.forEach(
d => BCAPI.plugin.settings.userHiers.forEach(
h => h[d].forEach(
x => values.add(x)
)
)
);
return Array.from(values);
}
HIERARCHIES.set("down",getHierarchyFields(["down"]));
HIERARCHIES.set("up",getHierarchyFields(["up"]));
HIERARCHIES.set("jump",getHierarchyFields(["prev","next"]));
} else {
HIERARCHIES.set("down",formattingJSON?.breadcrumbs?.down??["down","child","children"]);
HIERARCHIES.set("up",formattingJSON?.breadcrumbs?.up??["up", "parent","parents"]);
HIERARCHIES.set("jump",formattingJSON?.breadcrumbs?.jump??["jump", "jumps", "next", "previous"]);
}
//-------------------------------------------------------
// Initialization
//-------------------------------------------------------
if(SHOW_CONFIRMATION_PROMPT) {
const result = await utils.inputPrompt("This will overwrite the current active drawing","type: 'ok' to Continue");
const result = await utils.inputPrompt(
"This will overwrite the current active drawing",
"type: 'ok' to Continue"
);
if(result !== "ok") return;
}
@@ -222,7 +306,9 @@ ea.getExcalidrawAPI().updateScene({
});
ea.style.strokeColor = NODE_COLOR;
ea.addText(0,0,"Open a document in another pane and click it to get started.\n\nIf you do not see the Breadcrumbs as expected,\ntry refreshing the index in BC matrix view.\n\nFor best experience enable 'Open in adjacent pane'\nin Excalidraw settings under 'Links and Transclusion'.", {textAlign:"center"});
ea.addText(0,0,"Open a document in another pane and click it to get started.\n\n" +
"For the best experience enable 'Open in adjacent pane'\nin Excalidraw settings " +
"under 'Links and Transclusion'.", {textAlign:"center"});
await ea.addElementsToView();
ea.getExcalidrawAPI().zoomToFit();
@@ -236,19 +322,16 @@ new Notice("Brain Graph On");
//-------------------------------------------------------
// Supporting functions and classes
//-------------------------------------------------------
const getMatrixNeighbours = (node) => {
try {
return BCAPI.getMatrixNeighbours(node);
} catch {
return null
}
const getFilenameFromPath = (path) => {
const mdFile = path.endsWith(".md");
const filename = path.substring(path.lastIndexOf("/")+1);
return mdFile ? filename.slice(0,-3) : filename;
}
const joinRealsAndImplieds = (data) => {
result = new Set();
data.reals.forEach(i=>result.add(i.to));
data.implieds.forEach(i=>result.add(i.to));
return Array.from(result);
const getExtension = (path) => {
const lastDot = path.lastIndexOf(".");
if(lastDot === -1) return "md";
return path.slice(lastDot+1);
}
const distinct = (data) => Array.from(new Set(data));
@@ -320,8 +403,26 @@ class Layout {
class Node {
constructor(spec) {
this.spec = spec;
const label = spec.file?.basename??spec.nodeTitle;
const dvPage = spec.file ? DataviewAPI?.page(spec.file.path) : null;
const tag = (dvPage?.file?.tags?.values??[]).filter(t=>FORMATTED_TAGS.some(x=>t.startsWith(x)))[0];
if(tag) {
const format = NODE_FORMATTING[FORMATTED_TAGS.filter(x=>tag.startsWith(x))[0]];
spec.gateColor = format.gateColor ?? spec.gateColor;
spec.backgroundColor = format.backgroundColor ?? spec.backgroundColor;
spec.nodeColor = format.nodeColor ?? spec.nodeColor;
spec.borderColor = format.borderColor ?? spec.borderColor;
spec.prefix = format.prefix;
}
this.spec = spec;
const aliases = (spec.file && USE_ALIAS)
? (dvPage?.file?.aliases?.values??[])
: [];
const label = (spec.prefix??"") + (aliases.length > 0
? aliases[0]
: (spec.file
? (spec.file.extension === "md" ? spec.file.basename : spec.file.name)
: spec.nodeTitle));
this.label = label.length > spec.maxLabelLength
? label.substring(0,spec.maxLabelLength-1) + "..."
: label;
@@ -355,6 +456,7 @@ class Node {
? this.spec.backgroundColor
: this.spec.virtualNodeBGColor;
box.strokeColor = this.spec.borderColor;
box.strokeStyle = this.spec.strokeStyle??"solid";
ea.style.strokeColor = this.spec.gateColor;
ea.style.backgroundColor = this.spec.hasJumps ? this.spec.gateColor : "transparent";
@@ -386,93 +488,205 @@ class Node {
}
const addNodes = (nodesMap, root, nodes, layout, options) => {
nodes.forEach(nodeTitle => {
nodes.forEach(filePath => {
const node = new Node({
nodeTitle,
file: app.metadataCache.getFirstLinkpathDest(nodeTitle,root.path),
nodeTitle: getFilenameFromPath(filePath),
file: app.vault.getAbstractFileByPath(filePath),
hasChildren: false,
hasParents: false,
hasJumps: false,
...options
});
nodesMap.set(nodeTitle,node);
nodesMap.set(filePath,node);
layout.nodes.push(node);
});
}
//-------------------------------------------------------
// Breadcrumbs workaround to handle full filepath
//-------------------------------------------------------
const getPathOrSelf = (link,host) => {
return (app.metadataCache.getFirstLinkpathDest(link,host)?.path)??link;
}
const readDVField = (field,file) => {
const res = new Set();
if(field.values) {
field.values.forEach(l=>{
if(l.type === "file") res.add(getPathOrSelf(l.path,file.path));
});
return Array.from(res);
}
if(field.path) {
return [getPathOrSelf(field.path,file.path)];
}
const m = field.matchAll(/[^[]*\[\[([^#\]\|]*)[^\]]*]]/g);
while(!(r=m.next()).done) {
if(r.value[1]) {
res.add(getPathOrSelf(r.value[1],file.path));
}
}
return Array.from(res);
}
const getDVFieldLinks = (page,dir,retSet=false) => {
const fields = HIERARCHIES.get(dir);
const links = new Set();
const processed = new Set();
fields.forEach(f => {
if(page[f] && !processed.has(f)) {
processed.add(f);
readDVField(page[f],page.file).forEach(l=>links.add(l))
};
});
return retSet ? links : Array.from(links);
}
//-------------------------------------------------------
// Event handler
//-------------------------------------------------------
window.brainGraphEventHandler = async (leaf) => {
//sleep for 100ms
//sleep for 100ms to give time for leaf.view.file to be set
await new Promise((resolve) => setTimeout(resolve, 100));
//-------------------------------------------------------
//terminate event handler if view no longer exists or file has changed
if(!window.excalidrawView?.file || window.excalidrawView.file.path !== window.excalidrawFile?.path) {
removeEventHandler();
return;
}
if(!leaf?.view?.file) return;
const rootFile = leaf.view.file;
if (rootFile.path === window.excalidrawFile.path) return; //brainview drawing is active
//need to reinitialize ea because in the event handler ea provided by the script engine will no longer be available
if(window.lastfilePath && window.lastfilePath === rootFile.path) return; //don't reload the file if it has not changed
window.lastfilePath = rootFile.path;
//-------------------------------------------------------
//I must reinitialize ea because in the event handler the script engine ea will no longer be available
ea = ExcalidrawAutomate;
ea.reset();
ea.setView(window.excalidrawView);
ea.style.fontFamily = FONT_FAMILY;
ea.style.roughness = STROKE_ROUGHNESS;
ea.style.strokeSharpness = STROKE_SHARPNESS;
if(!leaf?.view?.file) return;
const file = leaf.view.file;
if (file.path === window.excalidrawFile.path) return; //brainview drawing is active
if(window.lastfilePath && window.lastfilePath === file.path) return; //don't reload the file if it has not changed
window.lastfilePath = file.path;
ea.getExcalidrawAPI().updateScene({elements:[]});
const centralNodeTitle = file.extension === "md" ? file.basename : file.name;
ea.style.verticalAlign = "middle";
ea.style.strokeSharpness = "round";
ea.style.fillStyle = "solid";
const rootFile = app.metadataCache.getFirstLinkpathDest(centralNodeTitle,"")
const bc = getMatrixNeighbours(centralNodeTitle);
const parents = bc ? joinRealsAndImplieds(bc.up).filter(n=>n!==centralNodeTitle).slice(0,MAX_ITEMS) : [];
const children = bc ? joinRealsAndImplieds(bc.down).filter(n=>n!==centralNodeTitle).slice(0,MAX_ITEMS) : [];
const jumps = bc ? distinct(
joinRealsAndImplieds(bc.next)
.concat(joinRealsAndImplieds(bc.prev))
).filter(n=>n!==centralNodeTitle)
.slice(0,MAX_ITEMS) : [];
const siblings = bc ? joinRealsAndImplieds(bc.same).filter(n=>n!==centralNodeTitle).slice(0,MAX_ITEMS) : []; //see breadcrumbs settings to finetune siblings
bclinks = new Set(parents.concat(children).concat(jumps).concat(siblings).concat([centralNodeTitle]));
//adding links from the document, not explicitly declared as a breadcrumbs
//this code assumes unique filenames (like breadcrumbs, thus will not handle non unique files well)
//-------------------------------------------------------
//build list of nodes
const dvPage = DataviewAPI.page(rootFile.path); //workaround because
const parentsSet = dvPage
? getDVFieldLinks(dvPage,"up",true)
: new Set();
parentsSet.delete(rootFile.path);
const childrenSet = dvPage
? getDVFieldLinks(dvPage,"down",true)
: new Set();
childrenSet.delete(rootFile.path);
const jumpsSet = dvPage
? getDVFieldLinks(dvPage,"jump",true)
: new Set();
jumpsSet.delete(rootFile.path);
let backlinksSet = new Set(
Object.keys((app.metadataCache.getBacklinksForFile(rootFile)?.data)??{})
.map(l=>app.metadataCache.getFirstLinkpathDest(l,rootFile.path)?.path??l)
);
backlinksSet.forEach(l=>{
const page = DataviewAPI.page(l);
if(page) {
if(getDVFieldLinks(page,"down",true).has(rootFile.path)) {
parentsSet.add(l);
backlinksSet.delete(l);
return;
}
if(getDVFieldLinks(page,"up",true).has(rootFile.path)) {
childrenSet.add(l);
backlinksSet.delete(l);
return;
}
if(getDVFieldLinks(page,"jump", true).has(rootFile.path)) {
jumpsSet.add(l);
backlinksSet.delete(l);
return;
}
}
})
const parents = Array.from(parentsSet).slice(0,MAX_ITEMS);
const children = Array.from(childrenSet).slice(0,MAX_ITEMS);
const jumps = Array.from(jumpsSet).slice(0,MAX_ITEMS);
//adding links from the document, not explicitly declared as a breadcrumb
const forwardLinks = INCLUDE_OBSIDIAN_LINKS
? distinct(app.metadataCache
.getLinks()[rootFile.path]?.map(l=>app.metadataCache.getFirstLinkpathDest(l.link,rootFile.path))
.filter(f=>f && (!HIDE_ATTACHMENTS || f.extension === "md"))
.map(f=>f.extension === "md" ? f.basename : f.name)
.filter(l=>!bclinks.has(l))??[].slice(0,MAX_ITEMS))
.getLinks()[rootFile.path]?.map(
l=>app.metadataCache.getFirstLinkpathDest(l.link.match(/[^#]*/)[0],rootFile.path)??l.link.match(/[^#]*/)[0]
).filter(f=>f && (!HIDE_ATTACHMENTS || (f.extension??getExtension(f)) === "md"))
.map(f=>f.path??f)
.filter(l=>!parentsSet.has(l) && !childrenSet.has(l) && !jumpsSet.has(l) && l!==rootFile.path && !backlinksSet.has(l))
.slice(0,Math.max(MAX_ITEMS-children.length))
)
: [];
const forwardLinksSet = new Set(forwardLinks);
const forwardlinksSet = new Set(forwardLinks);
const backLinks = INCLUDE_OBSIDIAN_LINKS
? distinct(Object
.keys(app.metadataCache.getBacklinksForFile(rootFile)?.data??{})
.map(l=>app.metadataCache.getFirstLinkpathDest(l,rootFile.path))
.filter(f=>f && f.path !== window.excalidrawFile.path && (!HIDE_ATTACHMENTS || f.extension === "md"))
.map(f=>f.extension === "md" ? f.basename : f.name)
.filter(l=>!bclinks.has(l) && !forwardLinksSet.has(l))
.slice(0,MAX_ITEMS))
: [];
const backLinksSet = new Set(backLinks);
? Array.from(backlinksSet)
.filter(l=>!parentsSet.has(l) && !childrenSet.has(l) &&
!jumpsSet.has(l) && l!==rootFile.path && !forwardlinksSet.has(l))
.slice(0,Math.max(MAX_ITEMS-parents.length))
: [];
backlinksSet = new Set(backLinks);
const sharedParents = INCLUDE_OBSIDIAN_LINKS
? backLinks.concat(parents)
: parents;
const siblings = distinct(
sharedParents.map(p=>{
const page = DataviewAPI.page(p);
return page
? getDVFieldLinks(page,"down")
.filter(l=>!parentsSet.has(l) && !childrenSet.has(l) &&
!jumpsSet.has(l) && l!==rootFile.path &&
!forwardlinksSet.has(l) && !backlinksSet.has(l))
: [];
}).flat()
).slice(0,MAX_ITEMS);
const siblingsSet = new Set(siblings);
const inverseInferredSiblingsMap = new Map();
const inferredSiblings = INCLUDE_OBSIDIAN_LINKS
? distinct(
sharedParents.map(p=>{
f = app.vault.getAbstractFileByPath(p); //app.metadataCache.getFirstLinkpathDest(p,rootFile.path);
if(!f) {
return [];
}
const res = Object.keys((app.metadataCache.getBacklinksForFile(f)?.data)??{})
.map(l=>app.metadataCache.getFirstLinkpathDest(l,rootFile.path)?.path??l)
.filter(l=>!parentsSet.has(l) && !childrenSet.has(l) && !jumpsSet.has(l) &&
l!==rootFile.path && !forwardlinksSet.has(l) && !backlinksSet.has(l) && !siblingsSet.has(l));
res.forEach(r=>inverseInferredSiblingsMap.set(r,p));
return res;
}).flat()
).slice(0,Math.max(MAX_ITEMS-siblings.length))
: [];
const inferredSiblingsSet = new Set(inferredSiblings);
//-------------------------------------------------------
// Generate layout and nodes
const nodesMap = new Map();
const linksMap = new Map();
const lCenter = new Layout({
origoX: 0,
origoY: 0,
@@ -484,7 +698,7 @@ window.brainGraphEventHandler = async (leaf) => {
});
const manyChildren = (children.length + forwardLinks.length) >10;
const manySiblings = siblings.length > 10;
const manySiblings = (siblings.length + inferredSiblings.length) > 10;
const singleParent = (parents.length + backLinks.length) <= 1
const lChildren = new Layout({
@@ -544,7 +758,7 @@ window.brainGraphEventHandler = async (leaf) => {
backgroundColor: CENTRAL_NODE_BG_COLOR,
virtualNodeBGColor: VIRTUAL_NODE_BG_COLOR
});
nodesMap.set(rootFile.basename,rootNode);
nodesMap.set(rootFile.path,rootNode);
lCenter.nodes.push(rootNode);
addNodes(
@@ -582,14 +796,15 @@ window.brainGraphEventHandler = async (leaf) => {
padding: PADDING,
nodeColor: OBSIDIAN_NODE_COLOR,
gateColor: GATE_COLOR,
borderColor: "transparent",
borderColor: OBSIDIAN_NODE_BG_COLOR,
strokeStyle: "dotted",
strokeWidth: 3,
backgroundColor: OBSIDIAN_NODE_BG_COLOR,
virtualNodeColor: VIRTUAL_NODE_COLOR,
virtualNodeBGColor: VIRTUAL_NODE_BG_COLOR
}
);
addNodes(
nodesMap,
rootFile,
@@ -625,14 +840,15 @@ window.brainGraphEventHandler = async (leaf) => {
padding: PADDING,
nodeColor: OBSIDIAN_NODE_COLOR,
gateColor: GATE_COLOR,
borderColor: "transparent",
borderColor: OBSIDIAN_NODE_BG_COLOR,
strokeStyle: "dotted",
strokeWidth: 3,
backgroundColor: OBSIDIAN_NODE_BG_COLOR,
virtualNodeColor: VIRTUAL_NODE_COLOR,
virtualNodeBGColor: VIRTUAL_NODE_BG_COLOR
}
);
addNodes(
nodesMap,
rootFile,
@@ -675,32 +891,138 @@ window.brainGraphEventHandler = async (leaf) => {
}
);
Array.from(nodesMap.keys()).forEach(nodeTitle => {
const node = nodesMap.get(nodeTitle);
const bc = getMatrixNeighbours(nodeTitle);
const parent = forwardLinksSet.has(nodeTitle) ? [rootFile.basename] : [];
const parents = bc ? joinRealsAndImplieds(bc.up).concat(parent) : parent;
const child = backLinksSet.has(nodeTitle) ? [rootFile.basename] : []
const children = bc ? joinRealsAndImplieds(bc.down).concat(child) : child;
const jumps = bc ? distinct(joinRealsAndImplieds(bc.next).concat(joinRealsAndImplieds(bc.prev))) : [];
//siblings are left out in this case on purpose
addNodes(
nodesMap,
rootFile,
inferredSiblings,
lSiblings,
{
fontSize: DISTANT_FONT_SIZE,
jumpOnLeft: true,
maxLabelLength: MAX_LABEL_LENGTH,
gateRadius: GATE_RADIUS,
gateOffset: GATE_OFFSET,
padding: PADDING,
nodeColor: OBSIDIAN_NODE_COLOR,
gateColor: GATE_COLOR,
borderColor: OBSIDIAN_NODE_BG_COLOR,
strokeStyle: "dotted",
strokeWidth: 3,
backgroundColor: OBSIDIAN_NODE_BG_COLOR,
virtualNodeColor: VIRTUAL_NODE_COLOR,
virtualNodeBGColor: VIRTUAL_NODE_BG_COLOR
}
);
//-------------------------------------------------------
// Generate links for all displayed nodes
const separator = "?"; //any character disallowed in filenames
Array.from(nodesMap.keys()).forEach(filePath => {
const node = nodesMap.get(filePath);
const dvPage = DataviewAPI.page(filePath);
node.spec.hasChildren = children.length>0;
node.spec.hasParents = parents.length>0;
node.spec.hasJumps = jumps.length>0;
//-------------------------------------------------------
// Add links originating from node
const parent = forwardlinksSet.has(filePath)
? rootFile.path
: inverseInferredSiblingsMap.get(filePath);
const parents = dvPage
? (parent ? getDVFieldLinks(dvPage,"up",true).add(parent) : getDVFieldLinks(dvPage,"up",true))
: (parent ? new Set([parent]) : new Set());
const child = backlinksSet.has(filePath) ? rootFile.path : null
const children = dvPage
? (child ? getDVFieldLinks(dvPage,"down", true).add(child) : getDVFieldLinks(dvPage,"down", true))
: (child ? new Set([child]) : new Set());
const jumps = dvPage
? getDVFieldLinks(dvPage,"jump", true)
: new Set();
//siblings are left out in this case on purpose
const addLinks = (nodes,relation) => nodes.forEach(n => {
if(!nodesMap.has(n)) return;
if(linksMap.has(`${nodeTitle}/${n}`)) return;
linksMap.set(`${nodeTitle}/${n}`,relation);
linksMap.set(`${n}/${nodeTitle}`,null);
if(linksMap.has(filePath + separator + n)) return;
linksMap.set(filePath + separator + n,relation);
linksMap.set(n + separator + filePath,null);
});
addLinks(parents,["parent","child"]);
addLinks(children, ["child","parent"]);
addLinks(jumps, ["jump","jump"]);
//-------------------------------------------------------
//Set gate fill
//- if node has outgoing links
node.spec.hasChildren = node.spec.hasChildren || children.size>0;
node.spec.hasParents = node.spec.hasParents || parents.size>0;
node.spec.hasJumps = node.spec.hasJumps || jumps.size>0;
//- of the nodes on the other end of outgoing links
// this is required on top of backlinks, to handle
// nodes for referenced but not yet created pages
parents.forEach(p=>{
const n = nodesMap.get(p);
if(n) n.spec.hasChildren = true;
});
children.forEach(p=>{
const n = nodesMap.get(p);
if(n) n.spec.hasParents = true;
});
jumps.forEach(p=>{
const n = nodesMap.get(p);
if(n) n.spec.hasJumps = true;
});
//- if node has an incoming link from a node outside the visible graph
if(node.spec.file && !(node.spec.hasParents && node.spec.hasChildren && node.hasJumps)) {
const nodeBacklinks = new Set(
Object.keys((app.metadataCache.getBacklinksForFile(node.spec.file)?.data)??{})
.map(l=>app.metadataCache.getFirstLinkpathDest(l,filePath)?.path??l)
);
nodeBacklinks.forEach(l=>{
if(node.spec.hasParents && node.spec.hasChildren && node.hasJumps) return;
const page = DataviewAPI.page(l);
if(page) {
if(getDVFieldLinks(page,"down",true).has(filePath)) {
node.spec.hasParents = true;
return;
}
if(getDVFieldLinks(page,"up",true).has(filePath)) {
node.spec.hasChildren = true;
return;
}
if(getDVFieldLinks(page,"jump",true).has(filePath)) {
node.spec.hasJumps = true;
return;
}
if(INCLUDE_OBSIDIAN_LINKS) {
node.spec.hasParents = true;
return;
}
}
});
}
if(!node.spec.hasChildren && INCLUDE_OBSIDIAN_LINKS && node.spec.file) {
const forwardLinks = app.metadataCache
.getLinks()[filePath]?.map(
l=>app.metadataCache.getFirstLinkpathDest(l.link.match(/[^#]*/)[0],rootFile.path)??l.link.match(/[^#]*/)[0]
).filter(f=>f && (!HIDE_ATTACHMENTS || (f.extension??getExtension(f)) === "md"))
.map(f=>f.path??f)
.filter(l=>!parents.has(l) && !children.has(l) && !jumps.has(l) && l!==rootFile.path);
if(forwardLinks.length > 0) {
node.spec.hasChildren = true;
}
}
});
//-------------------------------------------------------
// Render
lCenter.render();
lParents.render();
lChildren.render();
@@ -716,7 +1038,7 @@ window.brainGraphEventHandler = async (leaf) => {
ea.style.strokeColor = LINK_COLOR;
for([key,value] of linksMap) {
if(value) {
const k=key.split("/");
const k=key.split(separator);
const gate1 = getGate(k[0],value[0]);
const gate2 = getGate(k[1],value[1]);
ea.connectObjects(gate1, null, gate2, null, { startArrowHead: null, endArrowHead: null });
@@ -732,8 +1054,13 @@ window.brainGraphEventHandler = async (leaf) => {
ea.getExcalidrawAPI().zoomToFit();
}
//-------------------------------------------------------
// Initiate event listner and trigger plex if file is open
//-------------------------------------------------------
app.workspace.on(EVENT, window.brainGraphEventHandler);
ea.targetView.semaphores.saving = true; //disable saving by setting this Excalidraw flag (not published API)
const mdLeaf = app.workspace.getLeavesOfType("markdown");
if(mdLeaf.length>0) {
window.brainGraphEventHandler(mdLeaf[0]);

File diff suppressed because one or more lines are too long

View File

@@ -112,7 +112,7 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Change%20shape%20of%20selected%20elements.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Change%20shape%20of%20selected%20elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script allows you to change the shape of selected Rectangles, Diamonds and Ellipses.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-change-shape.jpg'></td></tr></table>
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Change%20shape%20of%20selected%20elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script allows you to change the shape and fill style of selected Rectangles, Diamonds, Ellipses, Lines, Arrows and Freedraw.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-change-shape.jpg'></td></tr></table>
## Connect elements
```excalidraw-script-install
@@ -322,7 +322,7 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/TheBrain-navigation.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/TheBrain-navigation.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">An Excalidraw based graph user interface for your Vault. Requires the <a href='https://github.com/SkepticMystic/breadcrumbs'>Breadcrumbs plugin</a> to be installed and configured as well. Generates a user interface similar to that of <a href='https://TheBrain.com'>TheBrain</a>. Watch this introduction to this script on <a href='https://youtu.be/J4T5KHERH_o'>YouTube</a>.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/TheBrain.jpg'></td></tr></table>
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/TheBrain-navigation.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">An Excalidraw based graph user interface for your Vault. Requires the <a href="https://github.com/blacksmithgu/obsidian-dataview">Dataview plugin</a>. Generates a graph view similar to that of <a href="https://TheBrain.com">TheBrain</a> plex.<br>Watch an introduction to this script on <a href="https://youtu.be/plYobK-VufM">YouTube</a>.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/TheBrain.jpg'></td></tr></table>
## Transfer TextElements to Excalidraw markdown metadata
```excalidraw-script-install

1
foo Normal file
View File

@@ -0,0 +1 @@

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -1,8 +1,8 @@
{
"id": "obsidian-excalidraw-plugin",
"name": "Excalidraw",
"version": "1.6.24",
"minAppVersion": "0.12.16",
"version": "1.7.6",
"minAppVersion": "0.15.3",
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
"author": "Zsolt Viczian",
"authorUrl": "https://zsolt.blog",

View File

@@ -1,31 +1,39 @@
{
"name": "obsidian-excalidraw-plugin",
"version": "1.6.13",
"version": "1.7.6",
"description": "This is an Obsidian.md plugin that lets you view and edit Excalidraw drawings",
"main": "main.js",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"files": [
"lib/**/*"
],
"scripts": {
"dev": "cross-env NODE_ENV=development rollup --config rollup.config.js -w",
"build": "cross-env NODE_ENV=production rollup --config rollup.config.js && terser main.js --compress toplevel=true,passes=2 --output main.js",
"build": "cross-env NODE_ENV=production rollup --config rollup.config.js",
"lib": "cross-env NODE_ENV=lib rollup --config rollup.config.js -w",
"code:fix": "eslint --max-warnings=0 --ext .ts,.tsx ./src --fix"
},
"keywords": [],
"author": "",
"license": "MIT",
"dependencies": {
"@zsviczian/excalidraw": "0.11.0-obsidian-9",
"@types/lz-string": "^1.3.34",
"@zsviczian/excalidraw": "0.11.0-obsidian-25",
"clsx": "^1.1.1",
"lz-string": "^1.4.4",
"monkey-around": "^2.3.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "^5.0.0",
"roughjs": "^4.5.2",
"lz-string": "^1.4.4",
"@types/lz-string": "^1.3.34",
"clsx": "1.1.1"
"react-scripts": "^5.0.1",
"roughjs": "^4.5.2"
},
"devDependencies": {
"@babel/core": "^7.16.12",
"@babel/preset-env": "^7.16.11",
"@babel/preset-react": "^7.16.7",
"@excalidraw/eslint-config": "1.0.0",
"@excalidraw/prettier-config": "1.0.2",
"@popperjs/core": "^2.11.5",
"@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-commonjs": "^21.0.1",
"@rollup/plugin-node-resolve": "^13.1.3",
@@ -33,22 +41,25 @@
"@rollup/plugin-typescript": "^8.3.0",
"@types/js-beautify": "^1.13.3",
"@types/node": "^15.12.4",
"@types/react-dom": "^17.0.11",
"@popperjs/core": "^2.11.2",
"@types/react-dom": "^17.0.2",
"@zerollup/ts-transform-paths": "^1.7.18",
"cross-env": "^7.0.3",
"html2canvas": "^1.4.0",
"nanoid": "^3.1.31",
"obsidian": "^0.14.4",
"rollup": "^2.70.1",
"rollup-plugin-visualizer": "^5.6.0",
"rollup-plugin-terser": "^7.0.2",
"tslib": "^2.3.1",
"typescript": "^4.5.5",
"eslint-config-prettier": "8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"html2canvas": "^1.4.0",
"nanoid": "^4.0.0",
"obsidian": "^0.15.1",
"prettier": "^2.5.1",
"@excalidraw/eslint-config": "1.0.0",
"@excalidraw/prettier-config": "1.0.2"
"rollup": "^2.70.1",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-postprocess": "github:brettz9/rollup-plugin-postprocess#update",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.31.2",
"rollup-plugin-visualizer": "^5.6.0",
"rollup-plugin-web-worker-loader": "^1.6.1",
"tslib": "^2.3.1",
"ttypescript": "^1.5.13",
"typescript": "^4.5.5"
},
"resolutions": {
"@typescript-eslint/typescript-estree": "5.3.0"

View File

@@ -0,0 +1,32 @@
import fs from'fs';
import LZString from 'lz-string';
const excalidraw_pkg = fs.readFileSync("./node_modules/@zsviczian/excalidraw/dist/excalidraw.production.min.js", "utf8");
const react_pkg = fs.readFileSync("./node_modules/react/umd/react.production.min.js", "utf8");
const reactdom_pkg = fs.readFileSync("./node_modules/react-dom/umd/react-dom.production.min.js", "utf8");
const lzstring_pkg = fs.readFileSync("./node_modules/lz-string/libs/lz-string.min.js", "utf8");
const mainjs = fs.readFileSync("main.js", "utf8")
const packageString = lzstring_pkg+'const EXCALIDRAW_PACKAGES="' + LZString.compressToBase64(react_pkg + reactdom_pkg + excalidraw_pkg) +'";var ExcalidrawPackageLoader=(d=document)=>{if(!d.getElementById("excalidraw-script")){const script=d.createElement("script");script.type="text/javascript";script.id="excalidraw-script";script.text=LZString.decompressFromBase64(EXCALIDRAW_PACKAGES);d.body.appendChild(script);}};ExcalidrawPackageLoader();';
fs.writeFileSync(
"main2.js",
mainjs
.replace('(require("react"))','')
.replace('"use strict";','"use strict";' + packageString),
{
encoding: "utf8",
flag: "w",
mode: 0o666
}
);
export default ({
input: 'foo',
plugins: [],
output: [{
file: 'foo.js',
format: 'es'
}]
});

View File

@@ -5,19 +5,60 @@ import { env } from "process";
import babel from '@rollup/plugin-babel';
import replace from "@rollup/plugin-replace";
import { terser } from "rollup-plugin-terser";
import copy from "rollup-plugin-copy";
import ttypescript from "ttypescript";
import typescript2 from "rollup-plugin-typescript2";
import webWorker from "rollup-plugin-web-worker-loader";
import fs from'fs';
import LZString from 'lz-string';
import postprocess from 'rollup-plugin-postprocess';
const isProd = (process.env.NODE_ENV === "production");
console.log("Is production", isProd);
export default {
const excalidraw_pkg = isProd
? fs.readFileSync("./node_modules/@zsviczian/excalidraw/dist/excalidraw.production.min.js", "utf8")
: fs.readFileSync("./node_modules/@zsviczian/excalidraw/dist/excalidraw.development.js", "utf8");
const react_pkg = isProd
? fs.readFileSync("./node_modules/react/umd/react.production.min.js", "utf8")
: fs.readFileSync("./node_modules/react/umd/react.development.js", "utf8");
const reactdom_pkg = isProd
? fs.readFileSync("./node_modules/react-dom/umd/react-dom.production.min.js", "utf8")
: fs.readFileSync("./node_modules/react-dom/umd/react-dom.development.js", "utf8");
const lzstring_pkg = fs.readFileSync("./node_modules/lz-string/libs/lz-string.min.js", "utf8");
const manifestStr = fs.readFileSync("manifest.json", "utf-8");
const manifest = JSON.parse(manifestStr);
console.log(manifest.version);
const packageString = ';'+lzstring_pkg+'const EXCALIDRAW_PACKAGES = "' + LZString.compressToBase64(react_pkg + reactdom_pkg + excalidraw_pkg) + '";' +
'const {react, reactDOM, excalidrawLib} = window.eval.call(window, `(function() {' +
'${LZString.decompressFromBase64(EXCALIDRAW_PACKAGES)};' +
'return {react:React, reactDOM:ReactDOM, excalidrawLib: ExcalidrawLib};})();`);' +
'const PLUGIN_VERSION="'+manifest.version+'";';
const BASE_CONFIG = {
input: 'src/main.ts',
external: ['obsidian', '@zsviczian/excalidraw', 'react', 'react-dom'],
}
const getRollupPlugins = (tsconfig, ...plugins) =>
[
typescript2(tsconfig),
nodeResolve({ browser: true }),
commonjs(),
webWorker({ inline: true, forceInline: true, targetPlatform: "browser" }),
].concat(plugins);
const BUILD_CONFIG = {
...BASE_CONFIG,
output: {
dir: '.',
sourcemap: 'inline',
sourcemap: isProd?false:'inline',
format: 'cjs',
exports: 'default',
},
external: ['obsidian'],
plugins: [
replace({
preventAssignment: true,
@@ -27,10 +68,46 @@ export default {
exclude: "node_modules/**"
}),
commonjs(),
nodeResolve({ browser: true, preferBuiltins: true }),
nodeResolve({ browser: true, preferBuiltins: false }),
typescript({inlineSources: !isProd}),
...isProd ? [
terser({toplevel: true, compress: {passes: 2}})
] : []
...isProd
? [
terser({toplevel: false, compress: {passes: 2}}),
//!postprocess - the version available on npmjs does not work, need this update:
// npm install brettz9/rollup-plugin-postprocess#update --save-dev
// https://github.com/developit/rollup-plugin-postprocess/issues/10
postprocess([
[/,React=require\("react"\);/, packageString],
])
]
: [
postprocess([
[/var React = require\('react'\);/, packageString],
])
],
],
};
}
const LIB_CONFIG = {
...BASE_CONFIG,
input: "src/index.ts",
output: {
dir: "lib",
sourcemap: true,
format: "cjs",
name: "Excalidraw (Library)",
},
plugins: getRollupPlugins(
{ tsconfig: "tsconfig-lib.json", typescript: ttypescript },
copy({ targets: [{ src: "src/*.d.ts", dest: "lib/typings" }] })
),
}
let config = [];
if(process.env.NODE_ENV === "lib") {
config.push(LIB_CONFIG);
} else {
config.push(BUILD_CONFIG);
}
export default config;

View File

@@ -60,6 +60,8 @@ export class EmbeddedFile {
public mimeType: MimeType = "application/octet-stream";
public size: Size = { height: 0, width: 0 };
public linkParts: LinkParts;
private hostPath: string;
public attemptCounter: number = 0;
/*public isHyperlink: boolean = false;*/
constructor(plugin: ExcalidrawPlugin, hostPath: string, imgPath: string) {
@@ -77,6 +79,7 @@ export class EmbeddedFile {
this.imgInverted = this.img = "";
this.mtime = 0;
this.linkParts = getLinkParts(imgPath);
this.hostPath = hostPath;
if (!this.linkParts.path) {
new Notice(`Excalidraw Error\nIncorrect embedded filename: ${imgPath}`);
return;
@@ -92,16 +95,25 @@ export class EmbeddedFile {
hostPath,
);
if (!this.file) {
new Notice(
`Excalidraw Warning: could not find image file: ${imgPath}`,
5000,
);
if(this.attemptCounter++ === 0) {
new Notice(
`Excalidraw Warning: could not find image file: ${imgPath}`,
5000,
);
}
}
}
private fileChanged(): boolean {
if (!this.file) {
return false;
this.file = this.plugin.app.metadataCache.getFirstLinkpathDest(
this.linkParts.path,
this.hostPath,
); // maybe the file has synchronized in the mean time
if(!this.file) {
this.attemptCounter++;
return false;
}
}
return this.mtime != this.file.stat.mtime;
}
@@ -135,7 +147,14 @@ export class EmbeddedFile {
public isLoaded(isDark: boolean): boolean {
if (!this.file) {
return true;
this.file = this.plugin.app.metadataCache.getFirstLinkpathDest(
this.linkParts.path,
this.hostPath,
); // maybe the file has synchronized in the mean time
if(!this.file) {
this.attemptCounter++;
return true;
}
}
if (this.fileChanged()) {
return false;
@@ -162,7 +181,6 @@ export class EmbeddedFile {
export class EmbeddedFilesLoader {
private plugin: ExcalidrawPlugin;
private processedFiles: Map<string, number> = new Map<string, number>();
private isDark: boolean;
public terminate = false;
public uid: string;
@@ -173,7 +191,7 @@ export class EmbeddedFilesLoader {
this.uid = nanoid();
}
public async getObsidianImage(inFile: TFile | EmbeddedFile): Promise<{
public async getObsidianImage(inFile: TFile | EmbeddedFile, depth: number): Promise<{
mimeType: MimeType;
fileId: FileId;
dataURL: DataURL;
@@ -196,15 +214,7 @@ export class EmbeddedFilesLoader {
width: this.plugin.settings.mdSVGwidth,
height: this.plugin.settings.mdSVGmaxHeight,
};
//to block infinite loop of recursive loading of images
const count = this.processedFiles.has(file.path)
? this.processedFiles.get(file.path)
: 0;
if (file.extension === "md" && count > 2) {
new Notice(t("INFINITE_LOOP_WARNING") + file.path, 6000);
return null;
}
this.processedFiles.set(file.path, count + 1);
let hasSVGwithBitmap = false;
const app = this.plugin.app;
const isExcalidrawFile = this.plugin.isExcalidrawFile(file);
@@ -240,6 +250,7 @@ export class EmbeddedFilesLoader {
null,
[],
this.plugin,
depth+1,
getSVGPadding(this.plugin, file),
);
//https://stackoverflow.com/questions/51154171/remove-css-filter-on-child-elements
@@ -315,7 +326,12 @@ export class EmbeddedFilesLoader {
public async loadSceneFiles(
excalidrawData: ExcalidrawData,
addFiles: Function,
depth:number
) {
if(depth > 4) {
new Notice(t("INFINITE_LOOP_WARNING")+depth.toString(), 6000);
return;
}
const entries = excalidrawData.getFileEntries();
//debug({where:"EmbeddedFileLoader.loadSceneFiles",uid:this.uid,isDark:this.isDark,sceneTheme:excalidrawData.scene.appState.theme});
if (this.isDark === undefined) {
@@ -327,7 +343,7 @@ export class EmbeddedFilesLoader {
const embeddedFile: EmbeddedFile = entry.value[1];
if (!embeddedFile.isLoaded(this.isDark)) {
//debug({where:"EmbeddedFileLoader.loadSceneFiles",uid:this.uid,status:"embedded Files are not loaded"});
const data = await this.getObsidianImage(embeddedFile);
const data = await this.getObsidianImage(embeddedFile, depth);
if (data) {
files.push({
mimeType: data.mimeType,
@@ -394,7 +410,8 @@ const convertMarkdownToSVG = async (
): Promise<DataURL> => {
//1.
//get the markdown text
let text = (await getTransclusion(linkParts, plugin.app, file)).contents;
const transclusion = await getTransclusion(linkParts, plugin.app, file);
let text = (transclusion.leadingHashes??"") + transclusion.contents;
if (text === "") {
text =
"# Empty markdown file\nCTRL+Click here to open the file for editing in the current active pane, or CTRL+SHIFT+Click to open it in an adjacent pane.";

File diff suppressed because it is too large Load Diff

View File

@@ -14,6 +14,8 @@ import {
FRONTMATTER_KEY_DEFAULT_MODE,
fileid,
REG_BLOCK_REF_CLEAN,
FRONTMATTER_KEY_LINKBUTTON_OPACITY,
FRONTMATTER_KEY_ONLOAD_SCRIPT,
} from "./Constants";
import { _measureText } from "./ExcalidrawAutomate";
import ExcalidrawPlugin from "./main";
@@ -210,26 +212,28 @@ const wrap = (text: string, lineLen: number) =>
lineLen ? wrapText(text, lineLen, false, 0) : text;
export class ExcalidrawData {
private textElements: Map<
public textElements: Map<
string,
{ raw: string; parsed: string; wrapAt: number | null }
> = null;
private elementLinks: Map<string, string> = null;
public elementLinks: Map<string, string> = null;
public scene: any = null;
public deletedElements: ExcalidrawElement[] = [];
private file: TFile = null;
private app: App;
private showLinkBrackets: boolean;
private linkPrefix: string;
private urlPrefix: string;
private textMode: TextMode = TextMode.raw;
private plugin: ExcalidrawPlugin;
public loaded: boolean = false;
private files: Map<FileId, EmbeddedFile> = null; //fileId, path
private equations: Map<FileId, { latex: string; isLoaded: boolean }> = null; //fileId, path
private compatibilityMode: boolean = false;
selectedElementIds: {[key:string]:boolean} = {}; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/609
constructor(plugin: ExcalidrawPlugin) {
this.plugin = plugin;
constructor(
private plugin: ExcalidrawPlugin,
) {
this.app = plugin.app;
this.files = new Map<FileId, EmbeddedFile>();
this.equations = new Map<FileId, { latex: string; isLoaded: boolean }>();
@@ -372,12 +376,13 @@ export class ExcalidrawData {
public async loadData(
data: string,
file: TFile,
textMode: TextMode,
textMode: TextMode
): Promise<boolean> {
if (!file) {
return false;
}
this.loaded = false;
this.selectedElementIds = {};
this.textElements = new Map<
string,
{ raw: string; parsed: string; wrapAt: number }
@@ -429,17 +434,10 @@ export class ExcalidrawData {
}
return sceneJSONandPOS;
};
//try {
sceneJSONandPOS = loadJSON();
/*} catch (e) {
if(await this.app.vault.adapter.exists(getBakPath(file))) {
data = await this.app.vault.adapter.read(getBakPath(file))
sceneJSONandPOS = loadJSON();
new Notice(t("LOAD_FROM_BACKUP"), 4000);
} else {
throw e;
}*/
this.deletedElements = this.scene.elements.filter((el:ExcalidrawElement)=>el.isDeleted);
this.scene.elements = this.scene.elements.filter((el:ExcalidrawElement)=>!el.isDeleted);
if (!this.scene.files) {
this.scene.files = {}; //loading legacy scenes that do not yet have the files attribute.
@@ -550,6 +548,7 @@ export class ExcalidrawData {
return false;
}
this.loaded = false;
this.selectedElementIds = {};
this.compatibilityMode = true;
this.file = file;
this.textElements = new Map<
@@ -636,23 +635,23 @@ export class ExcalidrawData {
id: string,
wrapResult: boolean = true,
): Promise<string> {
const t = this.textElements.get(id);
if (!t) {
const text = this.textElements.get(id);
if (!text) {
return null;
}
if (this.textMode === TextMode.parsed) {
if (!t.parsed) {
if (!text.parsed) {
this.textElements.set(id, {
raw: t.raw,
parsed: (await this.parse(t.raw)).parsed,
wrapAt: t.wrapAt,
raw: text.raw,
parsed: (await this.parse(text.raw)).parsed,
wrapAt: text.wrapAt,
});
}
//console.log("parsed",this.textElements.get(id).parsed);
return wrapResult ? wrap(t.parsed, t.wrapAt) : t.parsed;
return wrapResult ? wrap(text.parsed, text.wrapAt) : text.parsed;
}
//console.log("raw",this.textElements.get(id).raw);
return t.raw;
return text.raw;
}
private findNewElementLinksInScene(): boolean {
@@ -690,9 +689,10 @@ export class ExcalidrawData {
* check for textElements in Scene missing from textElements Map
* @returns {boolean} - true if there were changes
*/
private findNewTextElementsInScene(): boolean {
private findNewTextElementsInScene(selectedElementIds: {[key: string]: boolean} = {}): boolean {
//console.log("Excalidraw.Data.findNewTextElementsInScene()");
//get scene text elements
this.selectedElementIds = selectedElementIds;
const texts = this.scene.elements?.filter((el: any) => el.type === "text");
let jsonString = JSON.stringify(this.scene);
@@ -707,14 +707,18 @@ export class ExcalidrawData {
if (te.id.length > 8) {
dirty = true;
id = nanoid();
if(this.selectedElementIds[te.id]) { //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/609
delete this.selectedElementIds[te.id];
this.selectedElementIds[id] = true;
}
jsonString = jsonString.replaceAll(te.id, id); //brute force approach to replace all occurances (e.g. links, groups,etc.)
if (this.textElements.has(te.id)) {
//element was created with onBeforeTextSubmit
const t = this.textElements.get(te.id);
const text = this.textElements.get(te.id);
this.textElements.set(id, {
raw: t.raw,
parsed: t.parsed,
wrapAt: t.wrapAt,
raw: text.raw,
parsed: text.parsed,
wrapAt: text.wrapAt,
});
this.textElements.delete(te.id); //delete the old ID from the Map
}
@@ -952,7 +956,7 @@ export class ExcalidrawData {
* @returns markdown string
*/
disableCompression: boolean = false;
generateMD(): string {
generateMD(deletedElements: ExcalidrawElement[] = []): string {
let outString = "# Text Elements\n";
for (const key of this.textElements.keys()) {
outString += `${this.textElements.get(key).raw} ^${key}\n\n`;
@@ -978,7 +982,14 @@ export class ExcalidrawData {
}
outString += this.equations.size > 0 || this.files.size > 0 ? "\n" : "";
const sceneJSONstring = JSON.stringify(this.scene, null, "\t");
const sceneJSONstring = JSON.stringify({
type: this.scene.type,
version: this.scene.version,
source: this.scene.source,
elements: this.scene.elements.concat(deletedElements),
appState: this.scene.appState,
files: this.scene.files
}, null, "\t");
return (
outString +
getMarkdownDrawingSection(
@@ -1021,6 +1032,39 @@ export class ExcalidrawData {
return false;
}
//assing new fileId to duplicate equation and markdown files
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/601
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/593
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/297
const processedIds = new Set<string>();
fileIds.forEach(fileId=>{
if(processedIds.has(fileId)) {
const file = this.getFile(fileId);
//const file = this.files.get(fileId as FileId);
const equation = this.getEquation(fileId);
//const equation = this.equations.get(fileId as FileId);
//images should have a single reference, but equations and markdown embeds should have as many as instances of the file in the scene
if(file && (file.file.extension !== "md" || !this.plugin.isExcalidrawFile(file.file))) {
return;
}
const newId = fileid();
//scene.files[newId] = {...scene.files[fileId]};
(scene.elements.filter((el:ExcalidrawImageElement)=>el.fileId === fileId)[0] as any).fileId = newId;
dirty = true;
processedIds.add(newId);
if(file) {
this.setFile(newId as FileId,new EmbeddedFile(this.plugin,this.file.path,file.linkParts.original));
//this.files.set(newId as FileId,new EmbeddedFile(this.plugin,this.file.path,file.linkParts.original))
}
if(equation) {
this.setEquation(newId as FileId, {latex:equation.latex, isLoaded:false});
//this.equations.set(newId as FileId, equation);
}
}
processedIds.add(fileId);
});
for (const key of Object.keys(scene.files)) {
if (!(this.hasFile(key as FileId) || this.hasEquation(key as FileId))) {
dirty = true;
@@ -1069,7 +1113,7 @@ export class ExcalidrawData {
}
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/297
const equations = new Set<string>();
/*const equations = new Set<string>();
const duplicateEqs = new Set<string>();
for (const key of fileIds) {
if (this.hasEquation(key as FileId)) {
@@ -1095,12 +1139,12 @@ export class ExcalidrawData {
dirty = true;
}
}
}
}*/
return dirty;
}
public async syncElements(newScene: any): Promise<boolean> {
public async syncElements(newScene: any, selectedElementIds?: {[key: string]: boolean}): Promise<boolean> {
this.scene = newScene;
let result = false;
if (!this.compatibilityMode) {
@@ -1115,7 +1159,7 @@ export class ExcalidrawData {
this.setShowLinkBrackets() ||
this.findNewElementLinksInScene();
await this.updateTextElementsFromScene();
return result || this.findNewTextElementsInScene();
return result || this.findNewTextElementsInScene(selectedElementIds);
}
public async updateScene(newScene: any) {
@@ -1225,6 +1269,29 @@ export class ExcalidrawData {
}
}
public getLinkOpacity(): number {
const fileCache = this.app.metadataCache.getFileCache(this.file);
let opacity = this.plugin.settings.linkOpacity;
if (
fileCache?.frontmatter &&
fileCache.frontmatter[FRONTMATTER_KEY_LINKBUTTON_OPACITY] != null
) {
opacity = fileCache.frontmatter[FRONTMATTER_KEY_LINKBUTTON_OPACITY];
}
return opacity;
}
public getOnLoadScript(): string {
const fileCache = this.app.metadataCache.getFileCache(this.file);
if (
fileCache?.frontmatter &&
fileCache.frontmatter[FRONTMATTER_KEY_ONLOAD_SCRIPT] != null
) {
return fileCache.frontmatter[FRONTMATTER_KEY_ONLOAD_SCRIPT];
}
return null;
}
private setLinkPrefix(): boolean {
const linkPrefix = this.linkPrefix;
const fileCache = this.app.metadataCache.getFileCache(this.file);
@@ -1287,14 +1354,35 @@ export class ExcalidrawData {
if (!data.file) {
return;
}
const parts = data.linkParts.original.split("#");
this.plugin.filesMaster.set(fileId, {
path: data.file.path,
path:data.file.path,
blockrefData: parts.length === 1
? null
: parts[1],
hasSVGwithBitmap: data.isSVGwithBitmap,
});
}
public getFiles(): EmbeddedFile[] {
return Object.values(this.files);
}
public getFile(fileId: FileId): EmbeddedFile {
return this.files.get(fileId);
let embeddedFile = this.files.get(fileId);
if(embeddedFile) return embeddedFile;
const masterFile = this.plugin.filesMaster.get(fileId);
if(!masterFile) return embeddedFile;
embeddedFile = new EmbeddedFile(
this.plugin,
this.file.path,
masterFile.blockrefData
? masterFile.path + "#" + masterFile.blockrefData
: masterFile.path
);
this.files.set(fileId,embeddedFile);
return embeddedFile;
}
public getFileEntries() {
@@ -1313,15 +1401,17 @@ export class ExcalidrawData {
return true;
}
if (this.plugin.filesMaster.has(fileId)) {
const fileMaster = this.plugin.filesMaster.get(fileId);
if (!this.app.vault.getAbstractFileByPath(fileMaster.path)) {
const masterFile = this.plugin.filesMaster.get(fileId);
if (!this.app.vault.getAbstractFileByPath(masterFile.path)) {
this.plugin.filesMaster.delete(fileId);
return true;
} // the file no longer exists
const embeddedFile = new EmbeddedFile(
this.plugin,
this.file.path,
fileMaster.path,
masterFile.blockrefData
? masterFile.path + "#" + masterFile.blockrefData
: masterFile.path
);
this.files.set(fileId, embeddedFile);
return true;
@@ -1338,7 +1428,12 @@ export class ExcalidrawData {
}
public getEquation(fileId: FileId): { latex: string; isLoaded: boolean } {
return this.equations.get(fileId);
let result = this.equations.get(fileId);
if(result) return result;
const latex = this.plugin.equationsMaster.get(fileId);
if(!latex) return result;
this.equations.set(fileId, {latex, isLoaded: false});
return {latex, isLoaded: false};
}
public getEquationEntries() {
@@ -1372,7 +1467,7 @@ export const getTransclusion = async (
app: App,
file: TFile,
charCountLimit?: number,
): Promise<{ contents: string; lineNum: number }> => {
): Promise<{ contents: string; lineNum: number; leadingHashes?: string; }> => {
//file-name#^blockref
//1 2 3
@@ -1425,10 +1520,21 @@ export const getTransclusion = async (
let startPos: number = null;
let lineNum: number = 0;
let endPos: number = null;
let depth:number = 1;
for (let i = 0; i < headings.length; i++) {
if (startPos && !endPos) {
let j = i;
while (j<headings.length && headings[j].node.depth>depth) {j++};
if(j === headings.length && headings[j-1].node.depth > depth) {
return {
leadingHashes: "#".repeat(depth)+" ",
contents: contents.substring(startPos).trim(),
lineNum
};
}
endPos = headings[i].node.position.start.offset - 1;
return {
leadingHashes: "#".repeat(depth)+" ",
contents: contents.substring(startPos, endPos).trim(),
lineNum,
};
@@ -1436,6 +1542,7 @@ export const getTransclusion = async (
const c = headings[i].node.children[0];
const dataHeading = headings[i].node.data?.hProperties?.dataHeading;
const cc = c?.children;
//const refNoSpace = linkParts.ref.replaceAll(" ","");
if (
!startPos &&
(c?.value?.replaceAll(REG_BLOCK_REF_CLEAN, "") === linkParts.ref ||
@@ -1446,11 +1553,16 @@ export const getTransclusion = async (
: false))
) {
startPos = headings[i].node.children[0]?.position.start.offset; //
depth = headings[i].node.depth;
lineNum = headings[i].node.children[0]?.position.start.line; //
}
}
if (startPos) {
return { contents: contents.substring(startPos).trim(), lineNum };
return {
leadingHashes: "#".repeat(depth) + " ",
contents: contents.substring(startPos).trim(),
lineNum
};
}
return { contents: linkParts.original.trim(), lineNum: 0 };
};

File diff suppressed because it is too large Load Diff

View File

@@ -3,10 +3,9 @@ import ExcalidrawView from "./ExcalidrawView";
import ExcalidrawPlugin from "./main";
import { FileData, MimeType } from "./EmbeddedFileLoader";
import { FileId } from "@zsviczian/excalidraw/types/element/types";
import { getImageSize, log, sleep, svgToBase64 } from "./utils/Utils";
import { errorlog, getImageSize, log, sleep, svgToBase64 } from "./utils/Utils";
import { fileid } from "./Constants";
import html2canvas from "html2canvas";
import { count } from "console";
import { Notice } from "obsidian";
declare let window: any;
@@ -46,11 +45,14 @@ export async function tex2dataURL(
//if network is slow, or not available, or mathjax has not yet fully loaded
let counter = 0;
while (!plugin.mathjax && !plugin.mathjaxLoaderFinished && counter < 10) {
log({ where: "tex2dataURL", counter });
await sleep(100);
counter++;
}
if(!plugin.mathjaxLoaderFinished) {
errorlog({where: "text2dataURL", fn: tex2dataURL, message:"mathjaxLoader not ready, using fallback. Try reloading Obsidian or restarting the Excalidraw plugin"});
}
//it is not clear why this works, but it seems that after loading the plugin sometimes only the third attempt is successful.
try {
return await mathjaxSVG(tex, plugin);
@@ -79,7 +81,7 @@ export async function tex2dataURL(
}
}
async function mathjaxSVG(
export async function mathjaxSVG(
tex: string,
plugin: ExcalidrawPlugin,
): Promise<{

View File

@@ -36,7 +36,7 @@ let metadataCache: MetadataCache;
const getDefaultWidth = (plugin: ExcalidrawPlugin): string => {
const width = parseInt(plugin.settings.width);
if (isNaN(width) || width === 0) {
if (isNaN(width) || width === 0 || width === null) {
return "400";
}
return plugin.settings.width;
@@ -129,6 +129,7 @@ const getIMG = async (
null,
[],
plugin,
0
));
if (!png) {
return null;
@@ -152,6 +153,7 @@ const getIMG = async (
null,
[],
plugin,
0,
getSVGPadding(plugin, file),
)
).outerHTML;
@@ -196,7 +198,7 @@ const createImageDiv = async (
if (src) {
plugin.openDrawing(
vault.getAbstractFileByPath(src) as TFile,
ev[CTRL_OR_CMD],
ev[CTRL_OR_CMD]?"new-pane":"active-pane",
);
} //.ctrlKey||ev.metaKey);
});
@@ -339,8 +341,8 @@ const tmpObsidianWYSIWYG = async (
const basename = splitFolderAndFilename(attr.fname).basename;
const setAttr = () => {
const hasWidth = internalEmbedDiv.getAttribute("width") !== "";
const hasHeight = internalEmbedDiv.getAttribute("height") !== "";
const hasWidth = internalEmbedDiv.getAttribute("width") && (internalEmbedDiv.getAttribute("width") !== "");
const hasHeight = internalEmbedDiv.getAttribute("height") && (internalEmbedDiv.getAttribute("height") !== "");
if (hasWidth) {
attr.fwidth = internalEmbedDiv.getAttribute("width");
}
@@ -512,7 +514,7 @@ export const observer = new MutationObserver(async (m) => {
if (src) {
plugin.openDrawing(
vault.getAbstractFileByPath(src) as TFile,
ev[CTRL_OR_CMD],
ev[CTRL_OR_CMD]?"new-pane":"active-pane",
);
} //.ctrlKey||ev.metaKey);
});

View File

@@ -163,14 +163,16 @@ export class ScriptEngine {
name: `(Script) ${scriptName}`,
checkCallback: (checking: boolean) => {
if (checking) {
return (
this.plugin.app.workspace.activeLeaf.view.getViewType() ==
VIEW_TYPE_EXCALIDRAW
);
return Boolean(app.workspace.getActiveViewOfType(ExcalidrawView));
}
const view = this.plugin.app.workspace.activeLeaf.view;
if (view instanceof ExcalidrawView) {
this.executeScript(view, f);
const view = app.workspace.getActiveViewOfType(ExcalidrawView);
if (view) {
(async()=>{
const script = await this.plugin.app.vault.read(f);
if(script) {
this.executeScript(view, script, scriptName);
}
})()
return true;
}
return false;
@@ -206,18 +208,13 @@ export class ScriptEngine {
delete app.commands.commands[commandId];
}
async executeScript(view: ExcalidrawView, f: TFile) {
if (!view || !f) {
async executeScript(view: ExcalidrawView, script: string, title: string) {
if (!view || !script || !title) {
return;
}
this.plugin.ea.reset();
this.plugin.ea.setView(view);
const script = await this.plugin.app.vault.read(f);
if (!script) {
return;
}
this.plugin.ea.activeScript = this.getScriptName(f);
this.plugin.ea.activeScript = title;
//https://stackoverflow.com/questions/45381204/get-asyncfunction-constructor-in-typescript changed tsconfig to es2017
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction

View File

@@ -18,7 +18,7 @@ export const SCRIPT_INSTALL_FOLDER = "Downloaded";
export const fileid = customAlphabet("1234567890abcdef", 40);
export const REG_LINKINDEX_INVALIDCHARS = /[<>:"\\|?*#]/g;
export const REG_BLOCK_REF_CLEAN =
/\+|\/|~|=|%|\(|\)|{|}|,|\.|\$|!|\?|;|\[|]|\^|#|\*|<|>|&|@|\||\\|"|:/g;
/\+|\/|~|=|%|\(|\)|{|}|,|&|\.|\$|!|\?|;|\[|]|\^|#|\*|<|>|&|@|\||\\|"|:|\s/g;
export const IMAGE_TYPES = ["jpeg", "jpg", "png", "gif", "svg"];
export const MAX_IMAGE_SIZE = 500;
export const FRONTMATTER_KEY = "excalidraw-plugin";
@@ -30,6 +30,8 @@ export const FRONTMATTER_KEY_EXPORT_PNGSCALE = "excalidraw-export-pngscale";
export const FRONTMATTER_KEY_CUSTOM_PREFIX = "excalidraw-link-prefix";
export const FRONTMATTER_KEY_CUSTOM_URL_PREFIX = "excalidraw-url-prefix";
export const FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS = "excalidraw-link-brackets";
export const FRONTMATTER_KEY_ONLOAD_SCRIPT = "excalidraw-onload-script";
export const FRONTMATTER_KEY_LINKBUTTON_OPACITY = "excalidraw-linkbutton-opacity";
export const FRONTMATTER_KEY_DEFAULT_MODE = "excalidraw-default-mode";
export const FRONTMATTER_KEY_FONT = "excalidraw-font";
export const FRONTMATTER_KEY_FONTCOLOR = "excalidraw-font-color";

View File

@@ -16,6 +16,149 @@ export const RELEASE_NOTES: { [k: string]: string } = {
I develop this plugin as a hobby, spending most of my free time doing this. If you'd like to contribute to the on-going work, I have a simple membership scheme with Bronze, Silver and Gold tiers. Many of you have already bought me a coffee. THANK YOU! It really means a lot to me! If you find this plugin valuable, please consider supporting me.
<div class="ex-coffee-div"><a href="https://ko-fi.com/zsolt"><img src="https://cdn.ko-fi.com/cdn/kofi3.png?v=3" height=45></a></div>
`,
"1.7.6": `
This release is the same as 1.7.5 except for two minor fixes
- a fix for ExcaliBrain, becuase 1.7.5 broke ExcaliBrain.
- I left out the release note from 1.7.5.
# New
- Deployed sidebar for libraries panel from excalidraw.com ([#5274](https://github.com/excalidraw/excalidraw/pull/5274)). You can dock the library to the right side depending on the screen real estate available (i.e. does not work on mobiles).
# Fixed
- When copying 2 identical images from one drawing to another, the second image got corrupted in the process ([#672]https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/672)).
- When making a copy of an equation in a drawing and then without first closing/opening the file, immediately copying the new equation to another drawing, the equation did not get displayed until the file was closed and reopened.
- Copying a markdown embed from one drawing to another, in the destination the markdown embed appeared without the section/block reference and without the width & height (i.e. these settings had to be done again)
- Improved the parsing of section references in embeds. When you had ${String.fromCharCode(96)}&${String.fromCharCode(96)} in the section name in a markdown file, when embedding that markdown document into Excalidraw, the section reference did not work as expected ([#681 ](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/681)).
- Improved the logic for autosave to better detect changes to the document, and to reduce too frequent export of ${String.fromCharCode(96)}.png${String.fromCharCode(96)} and/or ${String.fromCharCode(96)}.svg${String.fromCharCode(96)} files, when auto export is enabled in plugin settings.
`,
"1.7.5": `
# New
- Deployed sidebar for libraries panel from excalidraw.com ([#5274](https://github.com/excalidraw/excalidraw/pull/5274)). You can dock the library to the right side depending on the screen real estate available (i.e. does not work on mobiles).
# Fixed
- When copying 2 identical images from one drawing to another, the second image got corrupted in the process ([#672]https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/672)).
- When making a copy of an equation in a drawing and then without first closing/opening the file, immediately copying the new equation to another drawing, the equation did not get displayed until the file was closed and reopened.
- Copying a markdown embed from one drawing to another, in the destination the markdown embed appeared without the section/block reference and without the width & height (i.e. these settings had to be done again)
- Improved the parsing of section references in embeds. When you had ${String.fromCharCode(96)}&${String.fromCharCode(96)} in the section name in a markdown file, when embedding that markdown document into Excalidraw, the section reference did not work as expected ([#681 ](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/681)).
- Improved the logic for autosave to better detect changes to the document, and to reduce too frequent export of ${String.fromCharCode(96)}.png${String.fromCharCode(96)} and/or ${String.fromCharCode(96)}.svg${String.fromCharCode(96)} files, when auto export is enabled in plugin settings.
`,
"1.7.4": `
- Obsidian 0.15.3 support dragging and dropping work panes between Obsidian windows.
- Addressed Obsidian changes affecting the more-options menu.
- Addressed incompatibility with Obsidian Mobile 1.2.2.
`,
"1.7.3": `
Obsidian 0.15.3 support for dragging and dropping work panes between Obsidian windows.
`,
"1.7.2": `
Due to some of the changes to the code, I highly recommend restarting Obsidian after installing this update to Excalidraw.
# Fixed
- Stability improvements
- Opening links in new panes and creating new drawings from the file explorer works properly again
# New feature
- Two new command palette actions:
- Create a new drawing - IN A POPOUT WINDOW
- Create a new drawing - IN A POPOUT WINDOW - and embed into active document
![image|600](https://user-images.githubusercontent.com/14358394/175137800-88789f5d-f8e8-4371-a356-84f443aa6a50.png)
- Added setting to prefer opening the link in the popout window or in the main workspace.
![image|800](https://user-images.githubusercontent.com/14358394/175076326-1c8eee53-e512-4025-aedb-07881a732c69.png)
`,
"1.7.1": `
Support for Obsidian 0.15.0 popout windows. While there are no new features (apart from the popout window support) under the hood there were some major changes required to make this happen.
`,
"1.7.0": `
This is the first test version of Excalidraw Obsidian supporting Obsidian 0.15.0 popout windows. The current technical solution is not really sustainable, it's more of a working concept. I don't expect any real big issues with this version - on the contrary, this works much better with Obsidian 0.15.0 popout windows, but some of the features aren't working as expected in the Obsidian popouts yet. Also as a consequence of Obsidian 0.15.0 compatibility, multiple hover previews are no longer supported.
`,
"1.6.34": `
With 0.15.1 Obsidian is implementing some exciting, but significant changes to how windows are managed. I need to make some heavy/invasive changes to Excalidraw to adapt. The next version of the Excalidraw Plugin will require Obsidian 0.15.1 or newer. If you are not signed up for Obsidian Insider Builds, you will need to wait few weeks until the new Obsidian version will be made public.
# Fixed
- Error saving when the attachments folder exists but with a different letter case (i.e. ATTACHMENTS instead of attachments) [658](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/658). I added more error tolerance. As a general rule, however, I recommend treating file paths as case-sensitive as some platforms like iOS or LINUX have case-sensitive filenames, and synchronizing your Vault to these platforms will cause you headaches in the future.
- Text detached from the container if you immediately clicked the text-align buttons on the properties pane while still editing the text in the container for the very first time. [#657](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/657).
- Can't add text to the second container if the first container has text and the second container is centered around the first one. [#5300](https://github.com/excalidraw/excalidraw/issues/5300)
`,
"1.6.33": `
# Fixed
- Under some special circumstances when you embedded a drawing (guest) into another drawing (host), the host did not update when you modified the guest, until you closed Excalidraw completely and reopened the host. [#637](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/637)
# New
- ExcalidrawAutomate ${String.fromCharCode(96)}addLabelToLine${String.fromCharCode(96)} adds a text label to a line or arrow. Currently this function only works with simple straight 2-point (start & end) lines.
${String.fromCharCode(96, 96, 96)}typescript
addLabelToLine(lineId: string, label: string): string
${String.fromCharCode(96, 96, 96)}
- ExcalidrawAutomate ${String.fromCharCode(96)}ConnectObjects${String.fromCharCode(96)} now returns the ID of the arrow that was created.`,
"1.6.32": `
## Fixed
- Filenames of embedded images and markdown documents did not get updated if the drawing was open in a work-pane while you changed the filename of the embedded file (image or markdown document) [632](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/632).
- When you created a new text element and immediately dragged it, sometimes autosave interrupted the drag action and Excalidraw dropped the element you were dragging [630](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/630)
- In some edge cases when you had the drawing open on your desktop and you also opened the same image on your tablet, Sync seemed to work in the background but the changes did not appear on the desktop until you closed and opened the drawing again. [629](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/629)
- LaTeX support: Excalidraw must download a javascript library from one of the hosting sites for MathJax tex2svg. It seems that some people do not have access to the URL recommended in the first place by [MathJax](https://docs.mathjax.org/en/latest/web/start.html). If LaTeX formulas do not render correctly in Excalidraw, try changing the source server under Compatibility Settings in Excalidraw Plugin Settings. [628](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/628)`,
"1.6.31": `
Minor update:
## Fixes
- Color picker hotkeys were not working. They are working again [627](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/627)
- I updated MathJax (LaTeX) to the newest (3.2.1) release.`,
"1.6.30": `
## Fixed
- The load stencil library button stopped working after 1.6.29 due to an error in the core Excalidraw package. It is now fixed. [#625](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/625).
- On iPad (probably other Obsidian mobile devices as well) after opening the command palette the positioning of the pointer was off. From now on, the pointer is automatically re-calibrated every 5 seconds.
- I improved shared-vault collaboration sync. If the open file has not been saved for the last 5 minutes (i.e. you are not working on the drawing actively), and a newer remote version of the file is received via sync, then the remote file will simply overwrite the local file (i.e. the behavior of Excalidraw Obsidian prior to implementing Shared (Multiplayer) Vault Synchronization support in 1.6.29). This solution will support active collaboration when parties participating are actively editing the drawing, but also caters to the scenario when you open a drawing on one device (e.g. your desktop) and once you are finished editing you do not close the drawing, but simply put your PC to sleep... then later you edit the same drawing on your tablet. When you turn your desktop PC on the next time, the changes you've made on your tablet will be synchronized by Obsidian sync. In this case the changes from your tablet should be honored. If you have not edited the open drawing for more then 5 minutes (like in this scenario) there is no value in running the file comparison between the local version and the received one. This approach reduces the probability of running into sync conflicts.`,
"1.6.29": `
## New
- I implemented sync support inspired by the new [Obsidian Multiplayer Sync](https://youtu.be/ZyCPhbd51eo) feature (available in insider build v0.14.10).
- To manage expectations, this is not real-time collaboration like on Excalidraw.com. Synchronization is delayed by the frequency of the autosave timer (every 10 secs) and the speed of Obsidian sync. Also if a file has conflicting versions, Obsidian sync may delay the delivery of the changed file.
- Even if you are not using multiplayer Obsidian Vaults, you may benefit from the improved synchronization, for example when using the freedraw tool on your tablet or phone, and in parallel editing the same drawing (e.g. typing text) on your desktop. I frequently do this in a mind-mapping scenario.
- If the same Excalidraw sketch is open on multiple devices then Excalidraw will try to merge changes into the open drawing, thus parallel modifications on different devices are possible. If the same element is edited by multiple parties at the same time, then the foreign (received) version will be honored and the local changes lost.
## Fixed:
- Default embed width setting stopped working. [#622](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/622)
- The link tooltip gets stuck on screen after Excalidraw closes [#621](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/621)
- Layout error when using the Workspaces core plugin. [#28](https://github.com/zsviczian/excalibrain/issues/28)`,
"1.6.28": `
## New
- When dropping a link from a DataView query into Excalidraw the link will honor your "New link format" preferences in Obsidian. It will add the "shortest path when possible", if that is your setting. If the link includes a block or section reference, then the link will automatically include an alias, such that only the filename is displayed (shortest path possible allowing) [#610](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/610)
- If Excalidraw is in a Hover Editor and you open a link in another pane by CTRL+SHIFT+Click then the new page will open in the main workspace, and not in a split pane in the hover editor.
## Fixed
- New text elements get de-selected after auto-save [#609](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/609)
- Update opacity of bound text when the opacity of the container is updated [#5142](https://github.com/excalidraw/excalidraw/pull/5142)
- ExcalidrawAutomate: openFileInNewOrAdjacentLeaf() function. This also caused an error when clicking a link in Excalidraw in a hover window, when there were no leaves in the main workspace view.`,
"1.6.27": `
## New Features
- While these new features are benefitial for all Excalidraw Automation projects, the current changes are mainly in support of the [ExcaliBrain](https://youtu.be/O2s-h5VKCas) integration. See detailed [Release Notes](https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/1.6.27) on GitHub.
`,
"1.6.26": `
## Fixed
- Dragging multiple files onto the canvas will now correctly [#589](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/589)
- add multiple links
- or if you hold the CTRL/(SHIFT on Mac) while dropping the files, then adding multiple images
- Dropped images and links were not selectable with the selection tool until the file was saved. This is now fixed.
- Display the linked block/section on link-hover instead of the full page. [#597](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/597)
- Hover preview without CTRL/CMD works again. Requires configuration in plugin settings. [#595](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/595)
- If you embed the same markdown document into a drawing multiple times, you can now display different sections of the document in each embedded object. [#601](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/601).
- If you make a copy of an equation and edit this copy, the original equation will remain unchanged [#593](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/593)
## New Features
- When you drag files from Dataview-results onto the canvas the obsidian:// urls will be converted into wiki links.[#599](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/599)
- I added one more frontmatter key: ${String.fromCharCode(96)}excalidraw-linkbutton-opacity: ${String.fromCharCode(96)} This sets the opacity of the blue link-button in the top right corner of the element, overriding the respective setting in plugin settings. Valid values are numbers between 0 and 1, where 0 means the button is fully transparent.
## New Excalidraw Automate Features
- As part of building the new [ExcaliBrain](https://youtu.be/O2s-h5VKCas) plugin, I've added a number of integration features. See the GitHub [Release Notes](https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/1.6.26) for details.
`,
"1.6.25": `
## Fixed
- Pinch-zoom in view mode was broken ([#5001](https://github.com/excalidraw/excalidraw/pull/5001))
- The add image button on iPad was not working ([#5038](https://github.com/excalidraw/excalidraw/pull/5038) & [#584](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/584))
## New Features
- If Excalidraw is open in a [hover-editor](https://github.com/nothingislost/obsidian-hover-editor) when opening a link in a new pane Excalidraw will now open the link in the main workspace and not by splitting the view inside the hover-editor.
- Excalidraw ScriptEngine settings
- Script Engine settings now render HTML descriptions
- If the ${String.fromCharCode(96)}height${String.fromCharCode(96)} property of a text setting is set, the corresponding text input field will be rendered as a textArea with the specified height.
`,
"1.6.24": `
## Fixed

View File

@@ -33,7 +33,7 @@ export class OpenFileDialog extends FuzzySuggestModal<TFile> {
if (this.containerEl.innerText.includes(EMPTY_MESSAGE)) {
this.plugin.createAndOpenDrawing(
`${this.plugin.settings.folder}/${this.inputEl.value}.excalidraw.md`,
this.onNewPane,
this.onNewPane?"new-pane":"active-pane",
);
this.close();
}
@@ -55,7 +55,7 @@ export class OpenFileDialog extends FuzzySuggestModal<TFile> {
onChooseItem(item: TFile): void {
switch (this.action) {
case openDialogAction.openFile:
this.plugin.openDrawing(item, this.onNewPane);
this.plugin.openDrawing(item, this.onNewPane?"new-pane":"active-pane");
break;
case openDialogAction.insertLinkToDrawing:
this.plugin.embedDrawing(item);

View File

@@ -219,7 +219,7 @@ export class GenericInputPrompt extends Modal {
}
private removeInputListener() {
this.inputComponent.inputEl.removeEventListener(
this.inputComponent?.inputEl?.removeEventListener(
"keydown",
this.submitEnterCallback,
);

View File

@@ -2,6 +2,8 @@ import { App, MarkdownRenderer, Modal } from "obsidian";
import ExcalidrawPlugin from "../main";
import { FIRST_RUN, RELEASE_NOTES } from "./Messages";
declare const PLUGIN_VERSION:string;
export class ReleaseNotes extends Modal {
private plugin: ExcalidrawPlugin;
private version: string;
@@ -23,9 +25,7 @@ export class ReleaseNotes extends Modal {
async onClose() {
this.contentEl.empty();
await this.plugin.loadSettings();
this.plugin.settings.previousRelease =
//@ts-ignore
this.app.plugins.manifests["obsidian-excalidraw-plugin"].version;
this.plugin.settings.previousRelease = PLUGIN_VERSION
await this.plugin.saveSettings();
}
@@ -36,8 +36,8 @@ export class ReleaseNotes extends Modal {
? Object.keys(RELEASE_NOTES)
.filter((key) => key > prevRelease)
.map((key: string) => `# ${key}\n${RELEASE_NOTES[key]}`)
.slice(0, 6)
.join("\n\n")
.slice(0, 10)
.join("\n\n---\n")
: FIRST_RUN;
await MarkdownRenderer.renderMarkdown(
message,

View File

@@ -236,10 +236,16 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
},
{
field: "connectObjects",
code: "connectObjects(objectA: string, connectionA: ConnectionPoint, objectB: string, connectionB: ConnectionPoint, formatting?: {numberOfPoints?: number; startArrowHead?: string; endArrowHead?: string; padding?: number;},): void;",
code: "connectObjects(objectA: string, connectionA: ConnectionPoint, objectB: string, connectionB: ConnectionPoint, formatting?: {numberOfPoints?: number; startArrowHead?: string; endArrowHead?: string; padding?: number;},): string;",
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: "addLabelToLine",
code: "addLabelToLine(lineId: string, label: string): string;",
desc: 'Adds a text label to a line or arrow. Currently only works with a simple straight 2-point (start & end) line',
after: "",
},
{
field: "clear",
code: "clear(): void;",
@@ -525,6 +531,19 @@ export const FRONTMATTER_KEYS_INFO: SuggesterInfo[] = [
desc: "Specifies how Excalidraw should open by default. Valid values are: view|zen",
after: ": view",
},
{
field: "linkbutton-opacity",
code: null,
desc: "The opacity of the blue link button in the top right of the element overriding the respective setting in plugin settings. "+
"Valid values are between 0 and 1, where 0 means the button is transparent.",
after: ": 0.5",
},
{
field: "onload-script",
code: null,
desc: "The value of this field will be executed as javascript code using the Script Engine environment. Use this to initiate custom actions or logic when loading your drawing.",
after: ': "new Notice(`Hello World!\\n\\nFile: ${ea.targetView.file.basename}`);"',
},
{
field: "font",
code: null,

13
src/index.ts Normal file
View File

@@ -0,0 +1,13 @@
import "obsidian";
//import { ExcalidrawAutomate } from "./ExcalidrawAutomate";
export {ExcalidrawAutomateInterface} from "./types";
export type { ExcalidrawBindableElement, ExcalidrawElement, FileId, FillStyle, StrokeSharpness, StrokeStyle } from "@zsviczian/excalidraw/types/element/types";
export type { Point } from "@zsviczian/excalidraw/types/types";
export const getEA = (view?:any): any => {
try {
return window.ExcalidrawAutomate.getAPI(view);
} catch(e) {
console.log({message: "Excalidraw not available", fn: getEA});
return null;
}
}

View File

@@ -30,10 +30,12 @@ export default {
TRANSCLUDE_MOST_RECENT: "Transclude (embed) the most recently edited drawing",
NEW_IN_NEW_PANE: "Create a new drawing - IN A NEW PANE",
NEW_IN_ACTIVE_PANE: "Create a new drawing - IN THE CURRENT ACTIVE PANE",
NEW_IN_POPOUT_WINDOW: "Create a new drawing - IN A POPOUT WINDOW",
NEW_IN_NEW_PANE_EMBED:
"Create a new drawing - IN A NEW PANE - and embed into active document",
NEW_IN_ACTIVE_PANE_EMBED:
"Create a new drawing - IN THE CURRENT ACTIVE PANE - and embed into active document",
NEW_IN_POPOUT_WINDOW_EMBED: "Create a new drawing - IN A POPOUT WINDOW - and embedd into active document",
EXPORT_SVG: "Save as SVG next to the current file",
EXPORT_PNG: "Save as PNG next to the current file",
TOGGLE_LOCK: "Toggle Text Element edit RAW/PREVIEW",
@@ -189,10 +191,14 @@ export default {
"If you don't want text accidentally changing in your drawings use <code>[[links|with aliases]]</code>.",
ADJACENT_PANE_NAME: "Open in adjacent pane",
ADJACENT_PANE_DESC:
"When CTRL/CMD+SHIFT clicking a link in Excalidraw by default the plugin will open the link in a new pane. " +
"When CTRL/CMD+SHIFT clicking a link in Excalidraw, by default the plugin will open the link in a new pane. " +
"Turning this setting on, Excalidraw will first look for an existing adjacent pane, and try to open the link there. " +
"Excalidraw will first look too the right, then to the left, then down, then up. If no pane is found, Excalidraw will open " +
"a new pane.",
"Excalidraw will look for the adjacent pane based on your focus/navigation history, i.e. the workpane that was active before you " +
"activated Excalidraw.",
MAINWORKSPACE_PANE_NAME: "Open in main workspace",
MAINWORKSPACE_PANE_DESC:
"When CTRL/CMD+SHIFT clicking a link in Excalidraw, by default the plugin will open the link in a new pane in the current active window. " +
"Turning this setting on, Excalidraw will open the link in an existing or new pane in the main workspace. ",
LINK_BRACKETS_NAME: "Show <code>[[brackets]]</code> around links",
LINK_BRACKETS_DESC: `${
"In PREVIEW mode, when parsing Text Elements, place brackets around links. " +
@@ -210,7 +216,9 @@ export default {
}${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 immediately, 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.",
"<b>Toggle On</b>: In Exalidraw <u>view mode</u> the hover preview for [[wiki links]] will be shown immediately, without the need to hold the CTRL/CMD key. " +
"In Excalidraw <u>normal mode</u>, the preview will be shown immediately only when hovering the blue link icon in the top right of the element.<br> " +
"<b>Toggle Off</b>: 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.",
@@ -344,6 +352,10 @@ export default {
"By enabling this feature drawings you create with the ribbon icon, the command palette actions, " +
"and the file explorer are going to be all legacy *.excalidraw files. This setting will also turn off the reminder message " +
"when you open a legacy file for editing.",
MATHJAX_NAME: "MathJax (LaTeX) javascript library host",
MATHJAX_DESC: "If you are using LaTeX equiations in Excalidraw then the plugin needs to load a javascript library for that. " +
"Some users are unable to access certain host servers. If you are experiencing issues try changing the host here. You may need to "+
"restart Obsidian after closing settings, for this change to take effect.",
EXPERIMENTAL_HEAD: "Experimental features",
EXPERIMENTAL_DESC:
"Some of these setting will not take effect immediately, only when the File Explorer is refreshed, or Obsidian restarted.",

View File

@@ -15,7 +15,7 @@ import {
loadMathJax,
request,
MetadataCache,
FrontMatterCache,
FrontMatterCache
} from "obsidian";
import {
BLANK_DRAWING,
@@ -42,10 +42,11 @@ import {
VIRGIL_FONT,
VIRGIL_DATAURL,
} from "./Constants";
import ExcalidrawView, { TextMode } from "./ExcalidrawView";
import ExcalidrawView, { TextMode, getTextMode } from "./ExcalidrawView";
import {
changeThemeOfExcalidrawMD,
getMarkdownDrawingSection,
ExcalidrawData
} from "./ExcalidrawData";
import {
ExcalidrawSettings,
@@ -81,8 +82,9 @@ import {
log,
setLeftHandedMode,
sleep,
debug,
} from "./utils/Utils";
import { getAttachmentsFolderAndFilePath, isObsidianThemeDark } from "./utils/ObsidianUtils";
import { getAttachmentsFolderAndFilePath, getNewOrAdjacentLeaf, getParentOfClass, isObsidianThemeDark } from "./utils/ObsidianUtils";
//import { OneOffs } from "./OneOffs";
import { FileId } from "@zsviczian/excalidraw/types/element/types";
import { ScriptEngine } from "./Scripts";
@@ -94,7 +96,10 @@ import {
} from "./MarkdownPostProcessor";
import { FieldSuggester } from "./dialogs/FieldSuggester";
import { ReleaseNotes } from "./dialogs/ReleaseNotes";
import { debug } from "./utils/Utils";
import { decompressFromBase64 } from "lz-string";
import { Packages } from "./types";
import * as React from "react";
declare module "obsidian" {
interface App {
@@ -115,6 +120,12 @@ declare module "obsidian" {
}
}
declare const EXCALIDRAW_PACKAGES:string;
declare const react:any;
declare const reactDOM:any;
declare const excalidrawLib: any;
declare const PLUGIN_VERSION:string;
export default class ExcalidrawPlugin extends Plugin {
private excalidrawFiles: Set<TFile> = new Set<TFile>();
public excalidrawFileModes: { [file: string]: string } = {};
@@ -139,7 +150,7 @@ export default class ExcalidrawPlugin extends Plugin {
public opencount: number = 0;
public ea: ExcalidrawAutomate;
//A master list of fileIds to facilitate copy / paste
public filesMaster: Map<FileId, { path: string; hasSVGwithBitmap: boolean }> =
public filesMaster: Map<FileId, { path: string; hasSVGwithBitmap: boolean; blockrefData: string }> =
null; //fileId, path
public equationsMaster: Map<FileId, string> = null; //fileId, formula
public mathjax: any = null;
@@ -147,15 +158,37 @@ export default class ExcalidrawPlugin extends Plugin {
public mathjaxLoaderFinished: boolean = false;
public scriptEngine: ScriptEngine;
public fourthFontDef: string = VIRGIL_FONT;
private packageMap: WeakMap<Window,Packages> = new WeakMap<Window,Packages>();
constructor(app: App, manifest: PluginManifest) {
super(app, manifest);
this.filesMaster = new Map<
FileId,
{ path: string; hasSVGwithBitmap: boolean }
{ path: string; hasSVGwithBitmap: boolean; blockrefData: string }
>();
this.equationsMaster = new Map<FileId, string>();
}
public getPackage(win:Window):Packages {
if(win===window) {
return {react, reactDOM, excalidrawLib};
}
if(this.packageMap.has(win)) {
return this.packageMap.get(win);
}
//@ts-ignore
const {react:r, reactDOM:rd, excalidrawLib:e} = win.eval.call(win,
`(function() {
${decompressFromBase64(EXCALIDRAW_PACKAGES)};
return {react:React,reactDOM:ReactDOM,excalidrawLib:ExcalidrawLib};
})()`);
this.packageMap.set(win,{react:r, reactDOM:rd, excalidrawLib:e});
return {react:r, reactDOM:rd, excalidrawLib:e};
}
async onload() {
addIcon(ICON_NAME, EXCALIDRAW_ICON);
addIcon(SCRIPTENGINE_ICON_NAME, SCRIPTENGINE_ICON);
@@ -188,29 +221,16 @@ export default class ExcalidrawPlugin extends Plugin {
//https://github.com/mgmeyers/obsidian-kanban/blob/44118e25661bff9ebfe54f71ae33805dc88ffa53/src/main.ts#L267
this.registerMonkeyPatches();
if (!this.app.isMobile) {
const electron: string = process?.versions?.electron;
if (electron && electron?.startsWith("8.")) {
new Notice(
`You are running an older version of the electron Browser (${electron}). If Excalidraw does not start up, please reinstall Obsidian with the latest installer and try again.`,
10000,
);
}
}
// const patches = new OneOffs(this);
if (this.settings.showReleaseNotes) {
//I am repurposing imageElementNotice, if the value is true, this means the plugin was just newly installed to Obsidian.
const obsidianJustInstalled = this.settings.imageElementNotice;
const version: string =
//@ts-ignore
this.app.plugins.manifests["obsidian-excalidraw-plugin"].version;
if (version > this.settings.previousRelease) {
if (PLUGIN_VERSION > this.settings.previousRelease) {
new ReleaseNotes(
this.app,
this,
obsidianJustInstalled ? null : version,
obsidianJustInstalled ? null : PLUGIN_VERSION,
).open();
}
}
@@ -236,32 +256,44 @@ export default class ExcalidrawPlugin extends Plugin {
const fourthFontDataURL =
font.dataURL === "" ? VIRGIL_DATAURL : font.dataURL;
this.fourthFontDef = font.fontDef;
const newStylesheet = document.createElement("style");
newStylesheet.id = "local-font-stylesheet";
newStylesheet.textContent = `
@font-face {
font-family: 'LocalFont';
src: url("${fourthFontDataURL}");
font-display: swap;
const visitedDocs = new Set<Document>();
app.workspace.iterateAllLeaves((leaf)=>{
const ownerDocument = app.isMobile?document:leaf.view.containerEl.ownerDocument;
if(!ownerDocument) return;
if(visitedDocs.has(ownerDocument)) return;
visitedDocs.add(ownerDocument);
// replace the old local font <style> element with the one we just created
const newStylesheet = ownerDocument.createElement("style");
newStylesheet.id = "local-font-stylesheet";
newStylesheet.textContent = `
@font-face {
font-family: 'LocalFont';
src: url("${fourthFontDataURL}");
font-display: swap;
}
`;
const oldStylesheet = ownerDocument.getElementById(newStylesheet.id);
ownerDocument.head.appendChild(newStylesheet);
if (oldStylesheet) {
ownerDocument.head.removeChild(oldStylesheet);
}
`;
// replace the old local font <style> element with the one we just created
const oldStylesheet = document.getElementById(newStylesheet.id);
document.head.appendChild(newStylesheet);
if (oldStylesheet) {
document.head.removeChild(oldStylesheet);
}
await (document as any).fonts.load(`20px LocalFont`);
ownerDocument.fonts.load('20px LocalFont');
})
});
}
private loadMathJax() {
public loadMathJax() {
const self = this;
this.app.workspace.onLayoutReady(async () => {
//loading Obsidian MathJax as fallback
await loadMathJax();
try {
if(self.mathjaxDiv) {
document.body.removeChild(self.mathjaxDiv);
self.mathjax = null;
self.mathjaxLoaderFinished = false;
}
self.mathjaxDiv = document.body.createDiv();
self.mathjaxDiv.title = "Excalidraw MathJax Support";
self.mathjaxDiv.style.display = "none";
@@ -299,7 +331,7 @@ export default class ExcalidrawPlugin extends Plugin {
self.mathjaxLoaderFinished = true;
});
};
script.src = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js";
script.src = self.settings.mathjaxSourceURL; // "https://cdn.jsdelivr.net/npm/mathjax@3.2.1/es5/tex-svg.js";
//script.src = MATHJAX_DATAURL;
doc.head.appendChild(script);
} catch {
@@ -633,7 +665,7 @@ export default class ExcalidrawPlugin extends Plugin {
this.addRibbonIcon(ICON_NAME, t("CREATE_NEW"), async (e) => {
this.createAndOpenDrawing(
getDrawingFilename(this.settings),
e[CTRL_OR_CMD],
e[CTRL_OR_CMD]?"new-pane":"active-pane",
); //.ctrlKey||e.metaKey);
});
@@ -651,7 +683,7 @@ export default class ExcalidrawPlugin extends Plugin {
}
this.createAndOpenDrawing(
getDrawingFilename(this.settings),
false,
"active-pane",
folderpath,
);
});
@@ -723,7 +755,7 @@ export default class ExcalidrawPlugin extends Plugin {
name: t("TRANSCLUDE"),
checkCallback: (checking: boolean) => {
if (checking) {
return this.app.workspace.activeLeaf.view.getViewType() == "markdown";
return Boolean(this.app.workspace.getActiveViewOfType(MarkdownView))
}
this.openDialog.start(openDialogAction.insertLinkToDrawing, false);
return true;
@@ -736,7 +768,7 @@ export default class ExcalidrawPlugin extends Plugin {
checkCallback: (checking: boolean) => {
if (checking) {
return (
this.app.workspace.activeLeaf.view.getViewType() == "markdown" &&
Boolean(this.app.workspace.getActiveViewOfType(MarkdownView)) &&
this.lastActiveExcalidrawFilePath != null
);
}
@@ -755,7 +787,7 @@ export default class ExcalidrawPlugin extends Plugin {
id: "excalidraw-autocreate",
name: t("NEW_IN_NEW_PANE"),
callback: () => {
this.createAndOpenDrawing(getDrawingFilename(this.settings), true);
this.createAndOpenDrawing(getDrawingFilename(this.settings), "new-pane");
},
});
@@ -763,11 +795,21 @@ export default class ExcalidrawPlugin extends Plugin {
id: "excalidraw-autocreate-on-current",
name: t("NEW_IN_ACTIVE_PANE"),
callback: () => {
this.createAndOpenDrawing(getDrawingFilename(this.settings), false);
this.createAndOpenDrawing(getDrawingFilename(this.settings), "active-pane");
},
});
const insertDrawingToDoc = async (inNewPane: boolean) => {
this.addCommand({
id: "excalidraw-autocreate-popout",
name: t("NEW_IN_POPOUT_WINDOW"),
callback: () => {
this.createAndOpenDrawing(getDrawingFilename(this.settings), "popout-window");
},
});
const insertDrawingToDoc = async (
location: "active-pane"|"new-pane"|"popout-window"
) => {
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
if (!activeView) {
return;
@@ -787,7 +829,7 @@ export default class ExcalidrawPlugin extends Plugin {
).folder;
const file = await this.createDrawing(filename, folder);
await this.embedDrawing(file);
this.openDrawing(file, inNewPane);
this.openDrawing(file, location);
};
this.addCommand({
@@ -795,9 +837,9 @@ export default class ExcalidrawPlugin extends Plugin {
name: t("NEW_IN_NEW_PANE_EMBED"),
checkCallback: (checking: boolean) => {
if (checking) {
return this.app.workspace.activeLeaf.view.getViewType() == "markdown";
return Boolean(this.app.workspace.getActiveViewOfType(MarkdownView));
}
insertDrawingToDoc(true);
insertDrawingToDoc("new-pane");
return true;
},
});
@@ -807,25 +849,36 @@ export default class ExcalidrawPlugin extends Plugin {
name: t("NEW_IN_ACTIVE_PANE_EMBED"),
checkCallback: (checking: boolean) => {
if (checking) {
return this.app.workspace.activeLeaf.view.getViewType() == "markdown";
return Boolean(this.app.workspace.getActiveViewOfType(MarkdownView));
}
insertDrawingToDoc(false);
insertDrawingToDoc("active-pane");
return true;
},
});
this.addCommand({
id: "excalidraw-autocreate-and-embed-popout",
name: t("NEW_IN_POPOUT_WINDOW_EMBED"),
checkCallback: (checking: boolean) => {
if (checking) {
return Boolean(this.app.workspace.getActiveViewOfType(MarkdownView));
}
insertDrawingToDoc("popout-window");
return true;
},
});
this.addCommand({
id: "export-svg",
name: t("EXPORT_SVG"),
checkCallback: (checking: boolean) => {
if (checking) {
return (
this.app.workspace.activeLeaf.view.getViewType() ==
VIEW_TYPE_EXCALIDRAW
Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView))
);
}
const view = this.app.workspace.activeLeaf.view;
if (view instanceof ExcalidrawView) {
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
if (view) {
view.saveSVG();
return true;
}
@@ -839,12 +892,11 @@ export default class ExcalidrawPlugin extends Plugin {
checkCallback: (checking: boolean) => {
if (checking) {
return (
this.app.workspace.activeLeaf.view.getViewType() ===
VIEW_TYPE_EXCALIDRAW
Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView))
);
}
const view = this.app.workspace.activeLeaf.view;
if (view instanceof ExcalidrawView) {
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
if (view) {
search(view);
return true;
}
@@ -858,12 +910,11 @@ export default class ExcalidrawPlugin extends Plugin {
checkCallback: (checking: boolean) => {
if (checking) {
return (
this.app.workspace.activeLeaf.view.getViewType() ===
VIEW_TYPE_EXCALIDRAW
Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView))
);
}
const view = this.app.workspace.activeLeaf.view;
if (view instanceof ExcalidrawView) {
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
if (view) {
if (view.isFullscreen()) {
view.exitFullscreen();
} else {
@@ -875,67 +926,17 @@ export default class ExcalidrawPlugin extends Plugin {
},
});
/* this.addCommand({
id: "ocr",
name: "Test OCR",//t("EXPORT_PNG"),
checkCallback: (checking: boolean) => {
if (checking) {
return (
this.app.workspace.activeLeaf.view.getViewType() ===
//@ts-ignore
VIEW_TYPE_EXCALIDRAW && typeof Tesseract !== "undefined"
);
}
const view = this.app.workspace.activeLeaf.view;
if (view instanceof ExcalidrawView) {
//@ts-ignore
const worker = Tesseract.createWorker({logger: m => console.log(m)});
//@ts-ignore
Tesseract.setLogging(true);
const exportSettings: ExportSettings = {
withBackground: true,
withTheme: false,
};
const blobToBase64 = async (blob:any) => {
return new Promise((resolve, _) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.readAsDataURL(blob);
});
}
(async () => {
const png = await getPNG(
view.getScene(),
exportSettings,
3,
);
await worker.load();
await worker.loadLanguage('https://https://raw.githubusercontent.com/thecodingone/trained-tesseract-handwriting-fonts/blob/master/eng.traineddata');
await worker.initialize('https://raw.githubusercontent.com/thecodingone/trained-tesseract-handwriting-fonts/blob/master/eng.traineddata');
const { data: { text } } = await worker.recognize(await blobToBase64(png));
console.log(text);
await worker.terminate();
})();
return true;
}
return false;
},
});*/
this.addCommand({
id: "export-png",
name: t("EXPORT_PNG"),
checkCallback: (checking: boolean) => {
if (checking) {
return (
this.app.workspace.activeLeaf.view.getViewType() ==
VIEW_TYPE_EXCALIDRAW
Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView))
);
}
const view = this.app.workspace.activeLeaf.view;
if (view instanceof ExcalidrawView) {
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
if (view) {
view.savePNG();
return true;
}
@@ -950,16 +951,15 @@ export default class ExcalidrawPlugin extends Plugin {
checkCallback: (checking: boolean) => {
if (checking) {
if (
this.app.workspace.activeLeaf.view.getViewType() ===
VIEW_TYPE_EXCALIDRAW
Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView))
) {
return !(this.app.workspace.activeLeaf.view as ExcalidrawView)
return !(this.app.workspace.getActiveViewOfType(ExcalidrawView))
.compatibilityMode;
}
return false;
}
const view = this.app.workspace.activeLeaf.view;
if (view instanceof ExcalidrawView) {
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
if (view) {
view.changeTextMode(
view.textMode === TextMode.parsed ? TextMode.raw : TextMode.parsed,
);
@@ -974,11 +974,10 @@ export default class ExcalidrawPlugin extends Plugin {
name: t("DELETE_FILE"),
checkCallback: (checking: boolean) => {
if (checking) {
const view = this.app.workspace.activeLeaf.view;
return view instanceof ExcalidrawView;
return Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView))
}
const view = this.app.workspace.activeLeaf.view;
if (view instanceof ExcalidrawView) {
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
if (view) {
this.ea.reset();
this.ea.setView(view);
const el = this.ea.getViewSelectedElement();
@@ -1011,11 +1010,10 @@ export default class ExcalidrawPlugin extends Plugin {
name: t("INSERT_LINK"),
checkCallback: (checking: boolean) => {
if (checking) {
const view = this.app.workspace.activeLeaf.view;
return view instanceof ExcalidrawView;
return Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView))
}
const view = this.app.workspace.activeLeaf.view;
if (view instanceof ExcalidrawView) {
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
if (view) {
this.insertLinkDialog.start(view.file.path, view.addText);
return true;
}
@@ -1029,11 +1027,10 @@ export default class ExcalidrawPlugin extends Plugin {
name: t("INSERT_LINK_TO_ELEMENT"),
checkCallback: (checking: boolean) => {
if (checking) {
const view = this.app.workspace.activeLeaf.view;
return view instanceof ExcalidrawView;
return Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView))
}
const view = this.app.workspace.activeLeaf.view;
if (view instanceof ExcalidrawView) {
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
if (view) {
view.copyLinkToSelectedElementToClipboard();
return true;
}
@@ -1046,11 +1043,10 @@ export default class ExcalidrawPlugin extends Plugin {
name: t("INSERT_IMAGE"),
checkCallback: (checking: boolean) => {
if (checking) {
const view = this.app.workspace.activeLeaf.view;
return view instanceof ExcalidrawView;
return Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView))
}
const view = this.app.workspace.activeLeaf.view;
if (view instanceof ExcalidrawView) {
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
if (view) {
this.insertImageDialog.start(view);
return true;
}
@@ -1063,13 +1059,9 @@ export default class ExcalidrawPlugin extends Plugin {
name: t("READ_RELEASE_NOTES"),
checkCallback: (checking: boolean) => {
if (checking) {
const view = this.app.workspace.activeLeaf.view;
return view instanceof ExcalidrawView;
return Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView))
}
const version: string =
//@ts-ignore
this.app.plugins.manifests["obsidian-excalidraw-plugin"].version;
new ReleaseNotes(this.app, this, version).open();
new ReleaseNotes(this.app, this, PLUGIN_VERSION).open();
return true;
},
});
@@ -1079,8 +1071,8 @@ export default class ExcalidrawPlugin extends Plugin {
name: t("TRAY_MODE"),
checkCallback: (checking: boolean) => {
if (checking) {
const view = this.app.workspace.activeLeaf.view;
if (!(view instanceof ExcalidrawView) || !view.excalidrawRef) {
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
if (!view || !view.excalidrawRef) {
return false;
}
const st = view.excalidrawAPI.getAppState();
@@ -1089,8 +1081,8 @@ export default class ExcalidrawPlugin extends Plugin {
}
return true;
}
const view = this.app.workspace.activeLeaf.view;
if (view instanceof ExcalidrawView && view.excalidrawAPI) {
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
if (view && view.excalidrawAPI) {
view.toggleTrayMode();
return true;
}
@@ -1103,11 +1095,10 @@ export default class ExcalidrawPlugin extends Plugin {
name: t("INSERT_MD"),
checkCallback: (checking: boolean) => {
if (checking) {
const view = this.app.workspace.activeLeaf.view;
return view instanceof ExcalidrawView;
return Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView))
}
const view = this.app.workspace.activeLeaf.view;
if (view instanceof ExcalidrawView) {
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
if (view) {
this.insertMDDialog.start(view);
return true;
}
@@ -1120,13 +1111,10 @@ export default class ExcalidrawPlugin extends Plugin {
name: t("INSERT_LATEX"),
checkCallback: (checking: boolean) => {
if (checking) {
return (
this.app.workspace.activeLeaf.view.getViewType() ==
VIEW_TYPE_EXCALIDRAW
);
return Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView));
}
const view = this.app.workspace.activeLeaf.view;
if (view instanceof ExcalidrawView) {
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
if (view) {
insertLaTeXToView(view);
return true;
}
@@ -1146,25 +1134,30 @@ export default class ExcalidrawPlugin extends Plugin {
if (checking) {
if (
this.app.workspace.activeLeaf.view.getViewType() ==
VIEW_TYPE_EXCALIDRAW
Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView))
) {
return !(this.app.workspace.activeLeaf.view as ExcalidrawView)
return !(this.app.workspace.getActiveViewOfType(ExcalidrawView))
.compatibilityMode;
}
return fileIsExcalidraw;
}
const activeLeaf = this.app.workspace.activeLeaf;
if (activeLeaf?.view && activeLeaf.view instanceof ExcalidrawView) {
const excalidrawView = this.app.workspace.getActiveViewOfType(ExcalidrawView)
if (excalidrawView) {
const activeLeaf = excalidrawView.leaf;
this.excalidrawFileModes[(activeLeaf as any).id || activeFile.path] =
"markdown";
this.setMarkdownView(activeLeaf);
} else if (fileIsExcalidraw) {
return;
}
const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView)
if (markdownView && fileIsExcalidraw) {
const activeLeaf = markdownView.leaf;
this.excalidrawFileModes[(activeLeaf as any).id || activeFile.path] =
VIEW_TYPE_EXCALIDRAW;
this.setExcalidrawView(activeLeaf);
return;
}
},
});
@@ -1174,9 +1167,9 @@ export default class ExcalidrawPlugin extends Plugin {
name: t("CONVERT_NOTE_TO_EXCALIDRAW"),
checkCallback: (checking) => {
const activeFile = this.app.workspace.getActiveFile();
const activeLeaf = this.app.workspace.activeLeaf;
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
if (!activeFile || !activeLeaf) {
if (!activeFile || !activeView) {
return false;
}
@@ -1185,13 +1178,14 @@ export default class ExcalidrawPlugin extends Plugin {
if (checking) {
return isFileEmpty;
}
if (isFileEmpty) {
(async () => {
await this.app.vault.modify(
activeFile,
await this.getBlankDrawing(),
);
this.setExcalidrawView(activeLeaf);
this.setExcalidrawView(activeView.leaf);
})();
}
},
@@ -1325,7 +1319,7 @@ export default class ExcalidrawPlugin extends Plugin {
// Add a menu item to go back to Excalidraw view
this.register(
around(MarkdownView.prototype, {
onMoreOptionsMenu(next) {
onPaneMenu(next) {
return function (menu: Menu) {
const file = this.file;
const cache = file
@@ -1345,6 +1339,7 @@ export default class ExcalidrawPlugin extends Plugin {
item
.setTitle(t("OPEN_AS_EXCALIDRAW"))
.setIcon(ICON_NAME)
.setSection("pane")
.onClick(() => {
self.excalidrawFileModes[this.leaf.id || file.path] =
VIEW_TYPE_EXCALIDRAW;
@@ -1352,7 +1347,6 @@ export default class ExcalidrawPlugin extends Plugin {
});
})
.addSeparator();
next.call(this, menu);
};
},
@@ -1360,30 +1354,10 @@ export default class ExcalidrawPlugin extends Plugin {
);
}
public ctrlKeyDown: boolean;
public shiftKeyDown: boolean;
public altKeyDown: boolean;
public onKeyUp: any;
public onKeyDown: any;
private popScope: Function = null;
private registerEventListeners() {
const self = this;
this.app.workspace.onLayoutReady(async () => {
self.onKeyUp = (e: KeyboardEvent) => {
self.ctrlKeyDown = e[CTRL_OR_CMD];
self.shiftKeyDown = e.shiftKey;
self.altKeyDown = e.altKey;
};
self.onKeyDown = (e: KeyboardEvent) => {
this.ctrlKeyDown = e[CTRL_OR_CMD];
this.shiftKeyDown = e.shiftKey;
this.altKeyDown = e.altKey;
};
window.addEventListener("keydown", self.onKeyDown, false);
window.addEventListener("keyup", self.onKeyUp, false);
//watch filename change to rename .svg, .png; to sync to .md; to update links
const renameEventHandler = async (
@@ -1414,7 +1388,7 @@ export default class ExcalidrawPlugin extends Plugin {
const modifyEventHandler = async (file: TFile) => {
const leaves = self.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
leaves.forEach((leaf: WorkspaceLeaf) => {
leaves.forEach(async (leaf: WorkspaceLeaf) => {
const excalidrawView = leaf.view as ExcalidrawView;
if (
excalidrawView.file &&
@@ -1425,8 +1399,24 @@ export default class ExcalidrawPlugin extends Plugin {
file.path.lastIndexOf(".excalidraw"),
)}.md` === excalidrawView.file.path))
) {
//debug({where:"ExcalidrawPlugin.modifyEventHandler",file:file.name,reloadfile:excalidrawView.file,before:"reload(true)"});
excalidrawView.reload(true, excalidrawView.file);
if(excalidrawView.semaphores.preventReload) {
excalidrawView.semaphores.preventReload = false;
return;
}
//if the user hasn't touched the file for 5 minutes, don't synchronize, reload.
//this is to avoid complex sync scenarios of multiple remote changes outside an active collaboration session
if(excalidrawView.lastSaveTimestamp + 300000 < Date.now()) {
excalidrawView.reload(true, excalidrawView.file);
return;
}
if(file.extension==="md") {
const inData = new ExcalidrawData(self);
const data = await app.vault.read(file);
await inData.loadData(data,file,getTextMode(data));
excalidrawView.synchronizeWithData(inData);
} else {
excalidrawView.reload(true, excalidrawView.file);
}
}
});
};
@@ -1497,7 +1487,9 @@ export default class ExcalidrawPlugin extends Plugin {
if (previouslyActiveEV.leaf != leaf) {
//if loading new view to same leaf then don't save. Excalidarw view will take care of saving anyway.
//avoid double saving
await previouslyActiveEV.save(true); //this will update transclusions in the drawing
if(previouslyActiveEV.semaphores.dirty) {
await previouslyActiveEV.save(true); //this will update transclusions in the drawing
}
}
if (previouslyActiveEV.file) {
self.triggerEmbedUpdates(previouslyActiveEV.file.path);
@@ -1571,6 +1563,7 @@ export default class ExcalidrawPlugin extends Plugin {
});
}
//Save the drawing if the user clicks outside the canvas
addFileSaveTriggerEventHandlers() {
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/551
const onClickEventSaveActiveDrawing = (e: PointerEvent) => {
@@ -1578,7 +1571,9 @@ export default class ExcalidrawPlugin extends Plugin {
!this.activeExcalidrawView ||
!this.activeExcalidrawView.semaphores.dirty ||
//@ts-ignore
e.target?.className === "excalidraw__canvas"
e.target && (e.target.className === "excalidraw__canvas" ||
//@ts-ignore
getParentOfClass(e.target,"excalidraw-wrapper"))
) {
return;
}
@@ -1675,9 +1670,6 @@ export default class ExcalidrawPlugin extends Plugin {
}
onunload() {
window.removeEventListener("keydown", this.onKeyDown, false);
window.removeEventListener("keyup", this.onKeyUp, false);
destroyExcalidrawAutomate();
if (this.popScope) {
this.popScope();
@@ -1703,9 +1695,12 @@ export default class ExcalidrawPlugin extends Plugin {
if (this.mathjaxDiv) {
document.body.removeChild(this.mathjaxDiv);
}
//this.settings.drawingOpenCount += this.opencount;
//this.settings.loadCount++;
//this.saveSettings();
Object.values(this.packageMap).forEach((p:Packages)=>{
delete p.excalidrawLib;
delete p.reactDOM;
delete p.react;
})
}
public async embedDrawing(file: TFile) {
@@ -1779,30 +1774,38 @@ export default class ExcalidrawPlugin extends Plugin {
}
public triggerEmbedUpdates(filepath?: string) {
const e = document.createEvent("Event");
e.initEvent(RERENDER_EVENT, true, false);
document
.querySelectorAll(
`div[class^='excalidraw-svg']${
filepath ? `[src='${filepath.replaceAll("'", "\\'")}']` : ""
}`,
)
.forEach((el) => el.dispatchEvent(e));
const visitedDocs = new Set<Document>();
app.workspace.iterateAllLeaves((leaf)=>{
const ownerDocument = app.isMobile?document:leaf.view.containerEl.ownerDocument;
if(!ownerDocument) return;
if(visitedDocs.has(ownerDocument)) return;
visitedDocs.add(ownerDocument);
const e = ownerDocument.createEvent("Event");
e.initEvent(RERENDER_EVENT, true, false);
ownerDocument
.querySelectorAll(
`div[class^='excalidraw-svg']${
filepath ? `[src='${filepath.replaceAll("'", "\\'")}']` : ""
}`,
)
.forEach((el) => el.dispatchEvent(e));
})
}
public openDrawing(drawingFile: TFile, onNewPane: boolean) {
let leaf: WorkspaceLeaf = null;
if (!leaf) {
leaf = this.app.workspace.activeLeaf;
public openDrawing(
drawingFile: TFile,
location: "active-pane"|"new-pane"|"popout-window"
) {
let leaf: WorkspaceLeaf;
if(location === "popout-window") {
//@ts-ignore
leaf = app.workspace.openPopoutLeaf();
}
if (!leaf) {
leaf = this.app.workspace.getLeaf();
}
if (onNewPane) {
leaf = this.app.workspace.createLeafBySplit(leaf);
else {
leaf = this.app.workspace.getLeaf(false);
if ((leaf.view.getViewType() !== 'empty') && (location === "new-pane")) {
leaf = getNewOrAdjacentLeaf(this, leaf)
}
}
leaf.setViewState({
@@ -1889,20 +1892,32 @@ export default class ExcalidrawPlugin extends Plugin {
);
await checkAndCreateFolder(this.app.vault, folderpath); //create folder if it does not exist
const fname = getNewUniqueFilepath(this.app.vault, filename, folderpath);
return await this.app.vault.create(
const file = await this.app.vault.create(
fname,
initData ?? (await this.getBlankDrawing()),
);
//wait for metadata cache
let counter = 0;
while(file instanceof TFile && !this.isExcalidrawFile(file) && counter++<10) {
await sleep(50);
}
if(counter > 10) {
errorlog({file, error: "new drawing not recognized as an excalidraw file", fn: this.createDrawing});
}
return file;
}
public async createAndOpenDrawing(
filename: string,
onNewPane: boolean,
location: "active-pane"|"new-pane"|"popout-window",
foldername?: string,
initData?: string,
): Promise<string> {
const file = await this.createDrawing(filename, foldername, initData);
this.openDrawing(file, onNewPane);
this.openDrawing(file, location);
return file.path;
}

View File

@@ -11,6 +11,7 @@ import { ReleaseNotes } from "../dialogs/ReleaseNotes";
import { ScriptIconMap } from "../Scripts";
import { getIMGFilename } from "../utils/FileUtils";
declare const PLUGIN_VERSION:string;
const dark = '<svg style="stroke:#ced4da;#212529;color:#ced4da;fill:#ced4da" ';
const light = '<svg style="stroke:#212529;color:#212529;fill:#212529" ';
@@ -45,10 +46,12 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
previousHeight: number = 0;
onRightEdge: boolean = false;
onBottomEdge: boolean = false;
private containerRef = React.createRef<HTMLDivElement>();
private containerRef: React.RefObject<HTMLDivElement>;
constructor(props: PanelProps) {
super(props);
const react = props.view.plugin.getPackage(props.view.ownerWindow).react;
this.containerRef = react.createRef();
this.state = {
visible: props.visible,
top: 50,
@@ -191,7 +194,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
: "none",
height: "fit-content",
maxHeight: "400px",
zIndex: 3,
zIndex: 5,
}}
>
<div
@@ -225,15 +228,15 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
};
const onPointerUp = () => {
document.removeEventListener("pointerup", onPointerUp);
document.removeEventListener("pointermove", onDrag);
this.props.view.ownerDocument?.removeEventListener("pointerup", onPointerUp);
this.props.view.ownerDocument?.removeEventListener("pointermove", onDrag);
};
event.preventDefault();
this.penDownX = this.pos3 = event.clientX;
this.penDownY = this.pos4 = event.clientY;
document.addEventListener("pointerup", onPointerUp);
document.addEventListener("pointermove", onDrag);
this.props.view.ownerDocument.addEventListener("pointerup", onPointerUp);
this.props.view.ownerDocument.addEventListener("pointermove", onDrag);
}}
>
<svg
@@ -276,15 +279,10 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
key={"release-notes"}
title={t("READ_RELEASE_NOTES")}
action={() => {
const version: string =
//@ts-ignore
this.props.view.app.plugins.manifests[
"obsidian-excalidraw-plugin"
].version;
new ReleaseNotes(
this.props.view.app,
this.props.view.plugin,
version,
PLUGIN_VERSION,
).open();
}}
icon={ICONS.releaseNotes}
@@ -520,13 +518,14 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
)
: this.state.scriptIconMap[key].name
}
action={() => {
action={async () => {
const f =
this.props.view.app.vault.getAbstractFileByPath(key);
if (f && f instanceof TFile) {
this.props.view.plugin.scriptEngine.executeScript(
this.props.view,
f,
await this.props.view.plugin.app.vault.read(f),
this.props.view.plugin.scriptEngine.getScriptName(f)
);
}
}}

View File

@@ -44,6 +44,7 @@ export interface ExcalidrawSettings {
zoomToFitOnResize: boolean;
zoomToFitMaxLevel: number;
openInAdjacentPane: boolean;
openInMainWorkspace: boolean;
showLinkBrackets: boolean;
linkPrefix: string;
urlPrefix: string;
@@ -86,10 +87,21 @@ export interface ExcalidrawSettings {
mdFontColor: string;
mdBorderColor: string;
mdCSS: string;
scriptEngineSettings: {};
scriptEngineSettings: {
[key:string]: {
[key:string]: {
value?:string,
hidden?: boolean,
description?: string,
valueset?: string[],
height?: number,
}
}
};
defaultTrayMode: boolean;
previousRelease: string;
showReleaseNotes: boolean;
mathjaxSourceURL: string;
}
export const DEFAULT_SETTINGS: ExcalidrawSettings = {
@@ -122,6 +134,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
hoverPreviewWithoutCTRL: false,
linkOpacity: 1,
openInAdjacentPane: false,
openInMainWorkspace: true,
showLinkBrackets: true,
allowCtrlClick: true,
forceWrap: false,
@@ -169,6 +182,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
defaultTrayMode: false,
previousRelease: "1.6.13",
showReleaseNotes: true,
mathjaxSourceURL: "https://cdn.jsdelivr.net/npm/mathjax@3.2.1/es5/tex-svg.js"
};
const fragWithHTML = (html: string) =>
@@ -178,6 +192,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
plugin: ExcalidrawPlugin;
private requestEmbedUpdate: boolean = false;
private requestReloadDrawings: boolean = false;
private reloadMathJax: boolean = false;
//private applyDebounceTimer: number = 0;
constructor(app: App, plugin: ExcalidrawPlugin) {
@@ -218,6 +233,9 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
this.plugin.triggerEmbedUpdates();
}
this.plugin.scriptEngine.updateScriptPath();
if(this.reloadMathJax) {
this.plugin.loadMathJax();
}
}
async display() {
@@ -529,7 +547,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
el.style.textAlign = "right";
el.innerText = ` ${this.plugin.settings.zoomToFitMaxLevel.toString()}`;
});
this.containerEl.createEl("h1", { text: t("LINKS_HEAD") });
this.containerEl.createEl(
"span",
@@ -549,6 +567,19 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
}),
);
new Setting(containerEl)
.setName(t("MAINWORKSPACE_PANE_NAME"))
.setDesc(fragWithHTML(t("MAINWORKSPACE_PANE_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.openInMainWorkspace)
.onChange(async (value) => {
this.plugin.settings.openInMainWorkspace = value;
this.applySettingsUpdate(true);
}),
);
new Setting(containerEl)
.setName(t("LINK_BRACKETS_NAME"))
.setDesc(fragWithHTML(t("LINK_BRACKETS_DESC")))
@@ -1098,6 +1129,24 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
}),
);
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/628
new Setting(containerEl)
.setName(t("MATHJAX_NAME"))
.setDesc(t("MATHJAX_DESC"))
.addDropdown((dropdown) => {
dropdown
.addOption("https://cdn.jsdelivr.net/npm/mathjax@3.2.1/es5/tex-svg.js", "jsdelivr")
.addOption("https://unpkg.com/mathjax@3.2.1/es5/tex-svg.js", "unpkg")
.addOption("https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.2.1/es5/tex-svg-full.min.js","cdnjs")
.setValue(this.plugin.settings.mathjaxSourceURL)
.onChange((value)=> {
this.plugin.settings.mathjaxSourceURL = value;
this.reloadMathJax = true;
this.applySettingsUpdate();
})
})
this.containerEl.createEl("h1", { text: t("EXPERIMENTAL_HEAD") });
this.containerEl.createEl("p", { text: t("EXPERIMENTAL_DESC") });
@@ -1185,6 +1234,9 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
);
});
//-------------------------------------
//Script settings
//-------------------------------------
const scripts = this.plugin.scriptEngine
.getListofScripts()
?.map((f) => this.plugin.scriptEngine.getScriptName(f));
@@ -1192,6 +1244,18 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
Object.keys(this.plugin.settings.scriptEngineSettings).length > 0 &&
scripts
) {
const textAreaHeight = (scriptName: string, variableName: string): any => {
const variable =
//@ts-ignore
this.plugin.settings.scriptEngineSettings[scriptName][variableName];
switch (typeof variable) {
case "object":
return variable.height;
default:
return null;
}
};
const getValue = (scriptName: string, variableName: string): any => {
const variable =
//@ts-ignore
@@ -1236,7 +1300,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
) => {
new Setting(containerEl)
.setName(variableName)
.setDesc(description ?? "")
.setDesc(fragWithHTML(description ?? ""))
.addToggle((toggle) =>
toggle
.setValue(getValue(scriptName, variableName))
@@ -1260,7 +1324,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
) {
new Setting(containerEl)
.setName(variableName)
.setDesc(description ?? "")
.setDesc(fragWithHTML(description ?? ""))
.addDropdown((dropdown) => {
valueset.forEach((val: any) =>
dropdown.addOption(val.toString(), val.toString()),
@@ -1273,17 +1337,33 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
});
});
} else {
new Setting(containerEl)
.setName(variableName)
.setDesc(description ?? "")
.addText((text) =>
text
.setValue(getValue(scriptName, variableName))
.onChange(async (value) => {
setValue(scriptName, variableName, value);
this.applySettingsUpdate();
}),
);
if(textAreaHeight(scriptName, variableName)) {
new Setting(containerEl)
.setName(variableName)
.setDesc(fragWithHTML(description ?? ""))
.addTextArea((text) => {
text.inputEl.style.minHeight = textAreaHeight(scriptName, variableName);
text.inputEl.style.minWidth = "400px";
text
.setValue(getValue(scriptName, variableName))
.onChange(async (value) => {
setValue(scriptName, variableName, value);
this.applySettingsUpdate();
});
});
} else {
new Setting(containerEl)
.setName(variableName)
.setDesc(fragWithHTML(description ?? ""))
.addText((text) =>
text
.setValue(getValue(scriptName, variableName))
.onChange(async (value) => {
setValue(scriptName, variableName, value);
this.applySettingsUpdate();
}),
);
}
}
};
@@ -1294,7 +1374,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
) => {
new Setting(containerEl)
.setName(variableName)
.setDesc(description ?? "")
.setDesc(fragWithHTML(description ?? ""))
.addText((text) =>
text
.setPlaceholder("Enter a number")

237
src/types.d.ts vendored Normal file
View File

@@ -0,0 +1,237 @@
import { ExcalidrawBindableElement, ExcalidrawElement, FileId, FillStyle, NonDeletedExcalidrawElement, StrokeSharpness, StrokeStyle } from "@zsviczian/excalidraw/types/element/types";
import { Point } from "@zsviczian/excalidraw/types/types";
import { TFile, WorkspaceLeaf } from "obsidian";
import { EmbeddedFilesLoader } from "./EmbeddedFileLoader";
import { ExcalidrawAutomate } from "./ExcalidrawAutomate";
import ExcalidrawView, { ExportSettings } from "./ExcalidrawView";
import ExcalidrawPlugin from "./main";
export type ConnectionPoint = "top" | "bottom" | "left" | "right" | null;
export type Packages = {
react: any,
reactDOM: any,
excalidrawLib: any,
}
export interface ExcalidrawAutomateInterface {
plugin: ExcalidrawPlugin;
elementsDict: {[key:string]:any}; //contains the ExcalidrawElements currently edited in Automate indexed by el.id
imagesDict: {[key: FileId]: any}; //the images files including DataURL, indexed by fileId
style: {
strokeColor: string; //https://www.w3schools.com/colors/default.asp
backgroundColor: string;
angle: number; //radian
fillStyle: FillStyle; //type FillStyle = "hachure" | "cross-hatch" | "solid"
strokeWidth: number;
strokeStyle: StrokeStyle; //type StrokeStyle = "solid" | "dashed" | "dotted"
roughness: number;
opacity: number;
strokeSharpness: StrokeSharpness; //type StrokeSharpness = "round" | "sharp"
fontFamily: number; //1: Virgil, 2:Helvetica, 3:Cascadia, 4:LocalFont
fontSize: number;
textAlign: string; //"left"|"right"|"center"
verticalAlign: string; //"top"|"bottom"|"middle" :for future use, has no effect currently
startArrowHead: string; //"triangle"|"dot"|"arrow"|"bar"|null
endArrowHead: string;
};
canvas: {
theme: string; //"dark"|"light"
viewBackgroundColor: string;
gridSize: number;
};
getAPI(view?:ExcalidrawView):ExcalidrawAutomate;
setFillStyle(val: number): void; //0:"hachure", 1:"cross-hatch" 2:"solid"
setStrokeStyle(val: number): void; //0:"solid", 1:"dashed", 2:"dotted"
setStrokeSharpness(val: number): void; //0:"round", 1:"sharp"
setFontFamily(val: number): void; //1: Virgil, 2:Helvetica, 3:Cascadia
setTheme(val: number): void; //0:"light", 1:"dark"
addToGroup(objectIds: []): string;
toClipboard(templatePath?: string): void;
getElements(): ExcalidrawElement[]; //get all elements from ExcalidrawAutomate elementsDict
getElement(id: string): ExcalidrawElement; //get single element from ExcalidrawAutomate elementsDict
create(params?: {
//create a drawing and save it to filename
filename?: string; //if null: default filename as defined in Excalidraw settings
foldername?: string; //if null: default folder as defined in Excalidraw settings
templatePath?: string;
onNewPane?: boolean;
frontmatterKeys?: {
"excalidraw-plugin"?: "raw" | "parsed";
"excalidraw-link-prefix"?: string;
"excalidraw-link-brackets"?: boolean;
"excalidraw-url-prefix"?: string;
};
}): Promise<string>;
createSVG(
templatePath?: string,
embedFont?: boolean,
exportSettings?: ExportSettings, //use ExcalidrawAutomate.getExportSettings(boolean,boolean)
loader?: EmbeddedFilesLoader, //use ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?)
theme?: string,
padding?: number
): Promise<SVGSVGElement>;
createPNG(
templatePath?: string,
scale?: number,
exportSettings?: ExportSettings, //use ExcalidrawAutomate.getExportSettings(boolean,boolean)
loader?: EmbeddedFilesLoader, //use ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?)
theme?: string,
): Promise<any>;
wrapText(text: string, lineLen: number): string;
addRect(topX: number, topY: number, width: number, height: number): string;
addDiamond(topX: number, topY: number, width: number, height: number): string;
addEllipse(topX: number, topY: number, width: number, height: number): string;
addBlob(topX: number, topY: number, width: number, height: number): string;
addText(
topX: number,
topY: number,
text: string,
formatting?: {
wrapAt?: number;
width?: number;
height?: number;
textAlign?: string;
box?: boolean | "box" | "blob" | "ellipse" | "diamond"; //if !null, text will be boxed
boxPadding?: number;
},
id?: string,
): string;
addLine(points: [[x: number, y: number]]): string;
addArrow(
points: [[x: number, y: number]],
formatting?: {
startArrowHead?: string;
endArrowHead?: string;
startObjectId?: string;
endObjectId?: string;
},
): string;
addImage(topX: number, topY: number, imageFile: TFile): Promise<string>;
addLaTex(topX: number, topY: number, tex: string): Promise<string>;
connectObjects(
objectA: string,
connectionA: ConnectionPoint, //type ConnectionPoint = "top" | "bottom" | "left" | "right" | null
objectB: string,
connectionB: ConnectionPoint, //when passed null, Excalidraw will automatically decide
formatting?: {
numberOfPoints?: number; //points on the line. Default is 0 ie. line will only have a start and end point
startArrowHead?: string; //"triangle"|"dot"|"arrow"|"bar"|null
endArrowHead?: string; //"triangle"|"dot"|"arrow"|"bar"|null
padding?: number;
},
): string;
addLabelToLine(lineId: string, label:string): string;
clear(): void; //clear elementsDict and imagesDict only
reset(): void; //clear() + reset all style values to default
isExcalidrawFile(f: TFile): boolean; //returns true if MD file is an Excalidraw file
//view manipulation
targetView: ExcalidrawView; //the view currently edited
setView(view: ExcalidrawView | "first" | "active"): ExcalidrawView;
getExcalidrawAPI(): any; //https://github.com/excalidraw/excalidraw/tree/master/src/packages/excalidraw#ref
getViewElements(): ExcalidrawElement[]; //get elements in View
deleteViewElements(el: ExcalidrawElement[]): boolean;
getViewSelectedElement(): ExcalidrawElement; //get the selected element in the view, if more are selected, get the first
getViewSelectedElements(): ExcalidrawElement[];
getViewFileForImageElement(el: ExcalidrawElement): TFile | null; //Returns the TFile file handle for the image element
copyViewElementsToEAforEditing(elements: ExcalidrawElement[]): void; //copies elements from view to elementsDict for editing
viewToggleFullScreen(forceViewMode?: boolean): void;
connectObjectWithViewSelectedElement( //connect an object to the selected element in the view
objectA: string, //see connectObjects
connectionA: ConnectionPoint,
connectionB: ConnectionPoint,
formatting?: {
numberOfPoints?: number;
startArrowHead?: string;
endArrowHead?: string;
padding?: number;
},
): boolean;
addElementsToView( //Adds elements from elementsDict to the current view
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
//Note that elements copied to the view with copyViewElementsToEAforEditing retain their
//position in the stack of elements in the view even if modified using EA
newElementsOnTop?: boolean, //default is false, i.e. the new elements get to the bottom of the stack
): Promise<boolean>;
registerThisAsViewEA():boolean;
deregisterThisAsViewEA():boolean;
onViewUnloadHook(view: ExcalidrawView): void;
onViewModeChangeHook(isViewModeEnabled:boolean, view: ExcalidrawView, ea: ExcalidrawAutomate): void;
onLinkHoverHook(
element: NonDeletedExcalidrawElement,
linkText: string,
view: ExcalidrawView,
ea: ExcalidrawAutomate
):boolean;
onLinkClickHook(
element: ExcalidrawElement,
linkText: string,
event: MouseEvent,
view: ExcalidrawView,
ea: ExcalidrawAutomate
): boolean;
onDropHook(data: {
//if set Excalidraw will call this function onDrop events
ea: ExcalidrawAutomate;
event: React.DragEvent<HTMLDivElement>;
draggable: any; //Obsidian draggable object
type: "file" | "text" | "unknown";
payload: {
files: TFile[]; //TFile[] array of dropped files
text: string; //string
};
excalidrawFile: TFile; //the file receiving the drop event
view: ExcalidrawView; //the excalidraw view receiving the drop
pointerPosition: { x: number; y: number }; //the pointer position on canvas at the time of drop
}): boolean; //a return of true will stop the default onDrop processing in Excalidraw
mostRecentMarkdownSVG: SVGSVGElement; //Markdown renderer will drop a copy of the most recent SVG here for debugging purposes
getEmbeddedFilesLoader(isDark?: boolean): EmbeddedFilesLoader; //utility function to generate EmbeddedFilesLoader object
getExportSettings( //utility function to generate ExportSettings object
withBackground: boolean,
withTheme: boolean,
): ExportSettings;
getBoundingBox(elements: ExcalidrawElement[]): {
//get bounding box of elements
topX: number; //bounding box is the box encapsulating all of the elements completely
topY: number;
width: number;
height: number;
};
//elements grouped by the highest level groups
getMaximumGroups(elements: ExcalidrawElement[]): ExcalidrawElement[][];
//gets the largest element from a group. useful when a text element is grouped with a box, and you want to connect an arrow to the box
getLargestElement(elements: ExcalidrawElement[]): ExcalidrawElement;
// Returns 2 or 0 intersection points between line going through `a` and `b`
// and the `element`, in ascending order of distance from `a`.
intersectElementWithLine(
element: ExcalidrawBindableElement,
a: readonly [number, number],
b: readonly [number, number],
gap?: number, //if given, element is inflated by this value
): Point[];
//See OCR plugin for example on how to use scriptSettings
activeScript: string; //Set automatically by the ScriptEngine
getScriptSettings(): {}; //Returns script settings. Saves settings in plugin settings, under the activeScript key
setScriptSettings(settings: any): Promise<void>; //sets script settings.
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;
isExcalidrawView(view: any): boolean;
selectElementsInView(elements: ExcalidrawElement[]): void; //sets selection in view
generateElementId(): string; //returns an 8 character long random id
cloneElement(element: ExcalidrawElement): ExcalidrawElement; //Returns a clone of the element with a new id
moveViewElementToZIndex(elementId: number, newZIndex: number): void; //Moves the element to a specific position in the z-index
hexStringToRgb(color: string): number[];
rgbToHexString(color: number[]): string;
hslToRgb(color: number[]): number[];
rgbToHsl(color: number[]): number[];
colorNameToHex(color: string): string;
}

View File

@@ -1,4 +1,4 @@
import { normalizePath, TAbstractFile, TFolder, Vault } from "obsidian";
import { normalizePath, Notice, TAbstractFile, TFile, TFolder, Vault } from "obsidian";
import { ExcalidrawSettings } from "src/Settings";
/**
@@ -122,10 +122,15 @@ export function getEmbedFilename(
*/
export async function checkAndCreateFolder(vault: Vault, folderpath: string) {
folderpath = normalizePath(folderpath);
const folder = vault.getAbstractFileByPath(folderpath);
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/658
//@ts-ignore
const folder = vault.getAbstractFileByPathInsensitive(folderpath);
if (folder && folder instanceof TFolder) {
return;
}
if (folder && folder instanceof TFile) {
new Notice(`The folder cannot be created because it already exists as a file: ${folderpath}.`)
}
await vault.createFolder(folderpath);
}

View File

@@ -14,52 +14,45 @@ export const getParentOfClass = (element: HTMLElement, cssClass: string):HTMLEle
) {
parent = parent.parentElement;
}
return parent.classList.contains(cssClass) ? parent : null;
return parent?.classList?.contains(cssClass) ? parent : null;
};
export const getNewOrAdjacentLeaf = (
plugin: ExcalidrawPlugin,
leaf: WorkspaceLeaf
): WorkspaceLeaf => {
const inHoverEditorLeaf = leaf.view?.containerEl
? getParentOfClass(leaf.view.containerEl, "popover") !== null
: false;
if (inHoverEditorLeaf) {
const mainLeaves = app.workspace.getLayout().main.children.filter((c:any) => c.type === "leaf");
if(mainLeaves.length === 0) {
//@ts-ignore
return leafToUse = app.workspace.createLeafInParent(app.workspace.rootSplit);
if(plugin.settings.openInMainWorkspace) {
leaf.view.navigation = false;
const mainLeaf = app.workspace.getLeaf(false)
leaf.view.navigation = true;
if(plugin.settings.openInAdjacentPane || mainLeaf.view.getViewType() === 'empty') {
return mainLeaf;
}
const targetLeaf = app.workspace.getLeafById(mainLeaves[0].id);
if (plugin.settings.openInAdjacentPane) {
return targetLeaf;
}
return plugin.app.workspace.createLeafBySplit(targetLeaf);
return app.workspace.createLeafBySplit(mainLeaf);
}
if (plugin.settings.openInAdjacentPane) {
let leafToUse = plugin.app.workspace.getAdjacentLeafInDirection(
leaf,
"right"
);
if (!leafToUse) {
leafToUse = plugin.app.workspace.getAdjacentLeafInDirection(leaf, "left");
//if in popout window
if(leaf.view.containerEl.ownerDocument !== document) {
const popoutLeaves = new Set<WorkspaceLeaf>();
app.workspace.iterateAllLeaves(l=>{
if(l !== leaf && l.view.navigation && l.view.containerEl.ownerDocument === leaf.view.containerEl.ownerDocument) {
popoutLeaves.add(l);
}
});
if(popoutLeaves.size === 0) {
return app.workspace.getLeaf(true);
}
return Array.from(popoutLeaves)[0];
}
if (!leafToUse) {
leafToUse = plugin.app.workspace.getAdjacentLeafInDirection(
leaf,
"bottom"
);
}
if (!leafToUse) {
leafToUse = plugin.app.workspace.getAdjacentLeafInDirection(leaf, "top");
}
if (!leafToUse) {
leafToUse = plugin.app.workspace.createLeafBySplit(leaf);
}
return leafToUse;
leaf.view.navigation = false;
const leafToUse = app.workspace.getLeaf(false)
leaf.view.navigation = true;
return leafToUse
}
return plugin.app.workspace.createLeafBySplit(leaf);
};
@@ -78,7 +71,7 @@ export const getAttachmentsFolderAndFilePath = async (
const activeFileFolder = `${splitFolderAndFilename(activeViewFilePath).folderpath}/`;
folder = normalizePath(activeFileFolder + folder.substring(2));
}
if (!folder) {
if (!folder || folder === "/") {
folder = "";
}
await checkAndCreateFolder(app.vault, folder);

View File

@@ -1,4 +1,4 @@
import { exportToSvg, exportToBlob } from "@zsviczian/excalidraw";
//import Excalidraw from "@zsviczian/excalidraw";
import {
App,
Notice,
@@ -23,6 +23,14 @@ import { ExportSettings } from "../ExcalidrawView";
import { compressToBase64, decompressFromBase64 } from "lz-string";
import { getIMGFilename } from "./FileUtils";
declare const PLUGIN_VERSION:string;
const {
exportToSvg,
exportToBlob,
//@ts-ignore
} = excalidrawLib;
declare module "obsidian" {
interface Workspace {
getAdjacentLeafInDirection(
@@ -41,8 +49,6 @@ export const checkExcalidrawVersion = async (app: App) => {
return;
}
versionUpdateChecked = true;
//@ts-ignore
const manifest = app.plugins.manifests[PLUGIN_ID];
try {
const gitAPIrequest = async () => {
@@ -63,9 +69,9 @@ export const checkExcalidrawVersion = async (app: App) => {
.filter((el: any) => el.version.match(/^\d+\.\d+\.\d+$/))
.sort((el1: any, el2: any) => el2.published - el1.published)[0].version;
if (latestVersion > manifest.version) {
if (latestVersion > PLUGIN_VERSION) {
new Notice(
`A newer version of Excalidraw is available in Community Plugins.\n\nYou are using ${manifest.version}.\nThe latest is ${latestVersion}`,
`A newer version of Excalidraw is available in Community Plugins.\n\nYou are using ${PLUGIN_VERSION}.\nThe latest is ${latestVersion}`,
);
}
} catch (e) {
@@ -390,16 +396,23 @@ export const scaleLoadedImage = (
};
export const setLeftHandedMode = (isLeftHanded: boolean) => {
const newStylesheet = document.createElement("style");
newStylesheet.id = "excalidraw-letf-handed";
newStylesheet.textContent = `.excalidraw .App-bottom-bar{justify-content:flex-end;}`;
const oldStylesheet = document.getElementById(newStylesheet.id);
if (oldStylesheet) {
document.head.removeChild(oldStylesheet);
}
if (isLeftHanded) {
document.head.appendChild(newStylesheet);
}
const visitedDocs = new Set<Document>();
app.workspace.iterateAllLeaves((leaf) => {
const ownerDocument = app.isMobile?document:leaf.view.containerEl.ownerDocument;
if(!ownerDocument) return;
if(visitedDocs.has(ownerDocument)) return;
visitedDocs.add(ownerDocument);
const newStylesheet = ownerDocument.createElement("style");
newStylesheet.id = "excalidraw-letf-handed";
newStylesheet.textContent = `.excalidraw .App-bottom-bar{justify-content:flex-end;}`;
const oldStylesheet = ownerDocument.getElementById(newStylesheet.id);
if (oldStylesheet) {
ownerDocument.head.removeChild(oldStylesheet);
}
if (isLeftHanded) {
ownerDocument.head.appendChild(newStylesheet);
}
})
};
export type LinkParts = {
@@ -542,9 +555,7 @@ export const errorlog = (data: {}) => {
console.error({ plugin: "Excalidraw", ...data });
};
export const sleep = async (ms: number) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
export const sleep = async (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
export const log = console.log.bind(window.console);
export const debug = console.log.bind(window.console);

3
testloader.js Normal file

File diff suppressed because one or more lines are too long

10
tsconfig-lib.json Normal file
View File

@@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"declaration": true,
"outDir": "lib",
"plugins": [{ "transform": "@zerollup/ts-transform-paths" }],
},
"include": ["src/**/*.ts"],
"exclude": ["src/test/**/*", "lib/**/*"]
}

View File

@@ -1,8 +1,7 @@
{
"compilerOptions": {
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"sourceMap": true,
"module": "es2015",
"target": "es2017",
"allowJs": true,

24
tsconfig.prod.json Normal file
View File

@@ -0,0 +1,24 @@
{
"compilerOptions": {
"baseUrl": ".",
"sourceMap": false,
"module": "es2015",
"target": "es2017",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"lib": [
"dom",
"scripthost",
"es2017",
"esnext",
"DOM.Iterable"
],
"jsx": "react"
},
"include": [
"**/*.ts",
"**/*.tsx", "src/Dialogs/OpenDrawing.ts"
]
}

View File

@@ -1,4 +1,6 @@
{
"1.6.24": "0.12.16",
"1.7.6": "0.15.3",
"1.7.2": "0.15.2",
"1.6.34": "0.12.16",
"1.4.2": "0.11.13"
}

1461
yarn.lock

File diff suppressed because it is too large Load Diff