Compare commits

..

39 Commits

Author SHA1 Message Date
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
37 changed files with 1889 additions and 1083 deletions

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

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

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.27",
"minAppVersion": "0.12.16",
"version": "1.7.5",
"minAppVersion": "0.15.3",
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
"author": "Zsolt Viczian",
"authorUrl": "https://zsolt.blog",

View File

@@ -1,15 +1,15 @@
{
"name": "obsidian-excalidraw-plugin",
"version": "1.6.27",
"version": "1.6.33",
"description": "This is an Obsidian.md plugin that lets you view and edit Excalidraw drawings",
"main": "lib/index.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"
},
@@ -17,48 +17,49 @@
"author": "",
"license": "MIT",
"dependencies": {
"@zsviczian/excalidraw": "0.11.0-obsidian-16",
"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"
"@zsviczian/excalidraw": "0.11.0-obsidian-24",
"clsx": "^1.1.1",
"lz-string": "^1.4.4",
"monkey-around": "^2.3.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"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",
"@babel/preset-react": "^7.18.6",
"@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",
"@rollup/plugin-replace": "^3.0.1",
"@rollup/plugin-typescript": "^8.3.0",
"rollup-plugin-typescript2": "^0.31.2",
"rollup-plugin-web-worker-loader": "^1.6.1",
"@types/js-beautify": "^1.13.3",
"@types/node": "^15.12.4",
"@types/react-dom": "^17.0.11",
"@types/react-dom": "^18.0.5",
"@zerollup/ts-transform-paths": "^1.7.18",
"@popperjs/core": "^2.11.2",
"cross-env": "^7.0.3",
"html2canvas": "^1.4.0",
"nanoid": "^3.1.31",
"obsidian": "^0.14.6",
"rollup": "^2.70.1",
"rollup-plugin-visualizer": "^5.6.0",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-copy": "^3.4.0",
"tslib": "^2.3.1",
"typescript": "^4.5.5",
"ttypescript": "^1.5.13",
"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

@@ -9,13 +9,38 @@ 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);
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'],
external: ['obsidian', '@zsviczian/excalidraw', 'react', 'react-dom'],
}
const getRollupPlugins = (tsconfig, ...plugins) =>
@@ -30,7 +55,7 @@ const BUILD_CONFIG = {
...BASE_CONFIG,
output: {
dir: '.',
sourcemap: 'inline',
sourcemap: isProd?false:'inline',
format: 'cjs',
exports: 'default',
},
@@ -43,11 +68,23 @@ const BUILD_CONFIG = {
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],
])
],
],
}

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;

View File

@@ -34,22 +34,29 @@ import { getNewOrAdjacentLeaf, isObsidianThemeDark } from "./utils/ObsidianUtils
import { AppState, Point } from "@zsviczian/excalidraw/types/types";
import { EmbeddedFilesLoader, FileData } from "./EmbeddedFileLoader";
import { tex2dataURL } from "./LaTeX";
import {
determineFocusDistance,
getCommonBoundingBox,
getMaximumGroups,
intersectElementWithLine,
measureText,
} from "@zsviczian/excalidraw";
//import Excalidraw from "@zsviczian/excalidraw";
import { Prompt } from "./dialogs/Prompt";
import { t } from "./lang/helpers";
import { ScriptEngine } from "./Scripts";
import { ConnectionPoint, ExcalidrawAutomateInterface } from "./types";
declare const PLUGIN_VERSION:string;
const {
determineFocusDistance,
intersectElementWithLine,
getCommonBoundingBox,
getMaximumGroups,
measureText,
//@ts-ignore
} = excalidrawLib;
const GAP = 4;
declare global {
interface Window { ExcalidrawAutomate: ExcalidrawAutomateInterface; }
interface Window {
ExcalidrawAutomate: ExcalidrawAutomateInterface;
}
}
export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
@@ -352,7 +359,7 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
params?.filename
? params.filename + (params.filename.endsWith(".md") ? "": ".excalidraw.md")
: getDrawingFilename(this.plugin.settings),
params?.onNewPane ? params.onNewPane : false,
(params?.onNewPane ? params.onNewPane : false)?"new-pane":"active-pane",
params?.foldername ? params.foldername : this.plugin.settings.folder,
this.plugin.settings.compatibilityMode
? JSON.stringify(scene, null, "\t")
@@ -485,7 +492,6 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
w: number,
h: number,
) {
const ea = window.ExcalidrawAutomate;
return {
id,
type: eltype,
@@ -948,7 +954,7 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
endArrowHead?: "triangle"|"dot"|"arrow"|"bar"|null;
padding?: number;
},
): void {
): string {
if (!(this.elementsDict[objectA] && this.elementsDict[objectB])) {
return;
}
@@ -1032,7 +1038,7 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
aY + (i * (bY - aY)) / (numAP - 1),
]);
}
this.addArrow(points, {
return this.addArrow(points, {
startArrowHead: formatting?.startArrowHead,
endArrowHead: formatting?.endArrowHead,
startObjectId: objectA,
@@ -1040,6 +1046,45 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
});
};
/**
* Adds a text label to a line or arrow. Currently only works with a straight (2 point - start & end - line)
* @param lineId id of the line or arrow object in elementsDict
* @param label the label text
* @returns undefined (if unsuccessful) or the id of the new text element
*/
addLabelToLine(lineId: string, label: string): string {
const line = this.elementsDict[lineId];
if(!line || !["arrow","line"].includes(line.type) || line.points.length !== 2) {
return;
}
let angle = Math.atan2(line.points[1][1],line.points[1][0]);
const size = this.measureText(label);
//let delta = size.height/6;
if(angle < 0) {
if(angle < -Math.PI/2) {
angle+= Math.PI;
} /*else {
delta = -delta;
} */
} else {
if(angle > Math.PI/2) {
angle-= Math.PI;
//delta = -delta;
}
}
this.style.angle = angle;
const id = this.addText(
line.x+line.points[1][0]/2-size.width/2,//+delta,
line.y+line.points[1][1]/2-size.height,//-5*size.height/6,
label
);
this.style.angle = 0;
return id;
}
/**
* clear elementsDict and imagesDict only
*/
@@ -1094,7 +1139,7 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
*/
setView(view: ExcalidrawView | "first" | "active"): ExcalidrawView {
if (view == "active") {
const v = this.plugin.app.workspace.activeLeaf.view;
const v = this.plugin.app.workspace.getActiveViewOfType(ExcalidrawView);
if (!(v instanceof ExcalidrawView)) {
return;
}
@@ -1380,7 +1425,7 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
) => boolean = null;
/**
* If set, this callback is triggered, when the user click a link in the scene.
* If set, this callback is triggered, when the user clicks a link in the scene.
* You can use this callback in case you want to do something additional when the onLinkClick event occurs.
* This callback must return a boolean value.
* In case you want to prevent the excalidraw onLinkClick action you must return false, it will stop the native excalidraw onLinkClick management flow.
@@ -1575,9 +1620,7 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
* @returns
*/
verifyMinimumPluginVersion(requiredVersion: string): boolean {
//@ts-ignore
const manifest = this.plugin.app.plugins.manifests[PLUGIN_ID];
return manifest.version >= requiredVersion;
return PLUGIN_VERSION >= requiredVersion;
};
/**

View File

@@ -212,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 }>();
@@ -374,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 }
@@ -431,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.
@@ -552,6 +548,7 @@ export class ExcalidrawData {
return false;
}
this.loaded = false;
this.selectedElementIds = {};
this.compatibilityMode = true;
this.file = file;
this.textElements = new Map<
@@ -638,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 {
@@ -692,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);
@@ -709,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
}
@@ -954,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`;
@@ -980,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(
@@ -1030,10 +1039,12 @@ export class ExcalidrawData {
const processedIds = new Set<string>();
fileIds.forEach(fileId=>{
if(processedIds.has(fileId)) {
const file = this.files.get(fileId as FileId);
const equation = this.equations.get(fileId as 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") {
if(file && (file.file.extension !== "md" || !this.plugin.isExcalidrawFile(file.file))) {
return;
}
const newId = fileid();
@@ -1042,10 +1053,12 @@ export class ExcalidrawData {
dirty = true;
processedIds.add(newId);
if(file) {
this.files.set(newId as FileId,new EmbeddedFile(this.plugin,this.file.path,file.linkParts.original))
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.equations.set(newId as FileId, equation);
this.setEquation(newId as FileId, {latex:equation.latex, isLoaded:false});
//this.equations.set(newId as FileId, equation);
}
}
processedIds.add(fileId);
@@ -1131,7 +1144,7 @@ export class ExcalidrawData {
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) {
@@ -1146,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) {
@@ -1341,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() {
@@ -1367,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;
@@ -1392,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() {
@@ -1501,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 ||

View File

@@ -9,9 +9,9 @@ import {
MarkdownView,
request,
} from "obsidian";
import * as React from "react";
import * as ReactDOM from "react-dom";
import Excalidraw, { getSceneVersion } from "@zsviczian/excalidraw";
//import * as React from "react";
//import * as ReactDOM from "react-dom";
//import Excalidraw from "@zsviczian/excalidraw";
import {
ExcalidrawElement,
ExcalidrawTextElement,
@@ -56,7 +56,7 @@ import {
} from "./utils/FileUtils";
import {
checkExcalidrawVersion,
//debug,
debug,
embedFontsInSVG,
errorlog,
getExportTheme,
@@ -87,6 +87,7 @@ import { ToolsPanel } from "./menu/ToolsPanel";
import { ScriptEngine } from "./Scripts";
import { getTextElementAtPointer, getImageElementAtPointer, getElementWithLinkAtPointer } from "./utils/GetElementAtPointer";
export enum TextMode {
parsed,
raw,
@@ -181,8 +182,20 @@ export default class ExcalidrawView extends TextFileView {
private parentMoveObserver: MutationObserver;
public linksAlwaysOpenInANewPane: boolean = false; //override the need for SHIFT+CTRL+click
private hookServer: ExcalidrawAutomate;
public lastSaveTimestamp: number = 0; //used to validate if incoming file should sync with open file
private onKeyUp: (e: KeyboardEvent) => void;
private onKeyDown:(e: KeyboardEvent) => void;
private ctrlKeyDown: boolean = false;
private shiftKeyDown: boolean = false;
private altKeyDown: boolean = false;
public ownerWindow: Window;
public ownerDocument: Document;
public semaphores: {
viewunload: boolean;
//first time initialization of the view
scriptsReady: boolean;
//The role of justLoaded is to capture the Excalidraw.onChange event that fires right after the canvas was loaded for the first time to
//- prevent the first onChange event to mark the file as dirty and to consequently cause a save right after load, causing sync issues in turn
//- trigger autozoom (in conjunction with preventAutozoomOnLoad)
@@ -211,6 +224,8 @@ export default class ExcalidrawView extends TextFileView {
saving: boolean;
hoverSleep: boolean; //flag with timer to prevent hover preview from being triggered dozens of times
} = {
viewunload: false,
scriptsReady: false,
justLoaded: false,
preventAutozoom: false,
autosaving: false,
@@ -406,6 +421,7 @@ export default class ExcalidrawView extends TextFileView {
}
}
private preventReloadResetTimer: NodeJS.Timeout = null;
async save(preventReload: boolean = true, forcesave: boolean = false) {
//debug({where:"save", preventReload, forcesave, semaphores:this.semaphores});
if (this.semaphores.saving) {
@@ -425,31 +441,49 @@ export default class ExcalidrawView extends TextFileView {
}
try {
const allowSave =
const allowSave = Boolean (
(this.semaphores.dirty !== null && this.semaphores.dirty) ||
this.semaphores.autosaving ||
forcesave; //dirty == false when view.file == null;
forcesave
); //dirty == false when view.file == null;
const scene = this.getScene();
if (this.compatibilityMode) {
await this.excalidrawData.syncElements(scene);
} else if (
await this.excalidrawData.syncElements(scene)
await this.excalidrawData.syncElements(scene, this.excalidrawAPI.getAppState().selectedElementIds)
//&& !this.semaphores.autosaving
) {
await this.loadDrawing(false);
await this.loadDrawing(
false,
this.excalidrawAPI.getSceneElementsIncludingDeleted().filter((el:ExcalidrawElement)=>el.isDeleted)
);
}
if (allowSave) {
//reload() is triggered indirectly when saving by the modifyEventHandler in main.ts
//prevent reload is set here to override reload when not wanted: typically when the user is editing
//and we do not want to interrupt the flow by reloading the drawing into the canvas.
if(this.preventReloadResetTimer) {
clearTimeout(this.preventReloadResetTimer);
this.preventReloadResetTimer = null;
}
this.semaphores.preventReload = preventReload;
await super.save();
this.lastSaveTimestamp = this.file.stat.mtime;
this.clearDirty();
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/629
//there were odd cases when preventReload semaphore did not get cleared and consequently a synchronized image
//did not update the open drawing
if(preventReload) {
const self = this;
this.preventReloadResetTimer = setTimeout(()=>self.semaphores.preventReload = false,2000);
}
}
if (!this.semaphores.autosaving) {
if (!this.semaphores.autosaving && !this.semaphores.viewunload) {
if (this.plugin.settings.autoexportSVG) {
await this.saveSVG();
}
@@ -485,6 +519,9 @@ export default class ExcalidrawView extends TextFileView {
if (!this.excalidrawData.loaded) {
return this.data;
}
//include deleted elements in save in case saving in markdown mode
//deleted elements are only used if sync modifies files while Excalidraw is open
//otherwise deleted elements are discarded when loading the scene
const scene = this.getScene();
if (!this.compatibilityMode) {
let trimLocation = this.data.search(/(^%%\n)?# Text Elements\n/m);
@@ -500,7 +537,7 @@ export default class ExcalidrawView extends TextFileView {
.replace(
/excalidraw-plugin:\s.*\n/,
`${FRONTMATTER_KEY}: ${
this.textMode == TextMode.raw ? "raw\n" : "parsed\n"
this.textMode === TextMode.raw ? "raw\n" : "parsed\n"
}`,
);
@@ -514,7 +551,9 @@ export default class ExcalidrawView extends TextFileView {
this.excalidrawData.disableCompression =
this.isEditedAsMarkdownInOtherView();
}
const reuslt = header + this.excalidrawData.generateMD();
const reuslt = header + this.excalidrawData.generateMD(
this.excalidrawAPI.getSceneElementsIncludingDeleted().filter((el:ExcalidrawElement)=>el.isDeleted) //will be concatenated to scene.elements
);
this.excalidrawData.disableCompression = false;
return reuslt;
}
@@ -592,7 +631,7 @@ export default class ExcalidrawView extends TextFileView {
element.querySelector("input").focus();
});
this.fullscreenModalObserver.observe(document.body, {
this.fullscreenModalObserver.observe(this.ownerDocument.body, {
childList: true,
subtree: false,
});
@@ -607,8 +646,8 @@ export default class ExcalidrawView extends TextFileView {
isFullscreen(): boolean {
return (
document.fullscreenEnabled &&
document.fullscreenElement === this.contentEl // excalidrawWrapperRef?.current
this.ownerDocument.fullscreenEnabled &&
this.ownerDocument.fullscreenElement === this.contentEl // excalidrawWrapperRef?.current
); //this.contentEl;
}
@@ -623,10 +662,17 @@ export default class ExcalidrawView extends TextFileView {
}
return;
}
document.exitFullscreen();
this.ownerDocument.exitFullscreen();
}
async handleLinkClick(view: ExcalidrawView, ev: MouseEvent) {
const tooltip = this.ownerDocument.body.querySelector(
"body>div.excalidraw-tooltip,div.excalidraw-tooltip--visible",
);
if (tooltip) {
this.ownerDocument.body.removeChild(tooltip);
}
const selectedText = this.getSelectedTextElement();
const selectedImage = selectedText?.id
? null
@@ -735,7 +781,7 @@ export default class ExcalidrawView extends TextFileView {
latex: formula,
isLoaded: false,
});
await this.save(true);
await this.save(false);
await updateEquation(
formula,
selectedImage.fileId,
@@ -743,11 +789,11 @@ export default class ExcalidrawView extends TextFileView {
addFiles,
this.plugin,
);
this.setDirty();
this.setDirty(1);
});
return;
}
await this.save(true); //in case pasted images haven't been saved yet
await this.save(false); //in case pasted images haven't been saved yet
if (this.excalidrawData.hasFile(selectedImage.fileId)) {
if (ev.altKey) {
const ef = this.excalidrawData.getFile(selectedImage.fileId);
@@ -767,9 +813,9 @@ export default class ExcalidrawView extends TextFileView {
return;
}
ef.resetImage(this.file.path, link);
await this.save(true);
await this.save(false);
await this.loadSceneFiles();
this.setDirty();
this.setDirty(2);
});
return;
}
@@ -841,7 +887,25 @@ export default class ExcalidrawView extends TextFileView {
}
diskIcon: HTMLElement;
excalidrawGetSceneVersion: (elements: ExcalidrawElement[]) => number;
getSceneVersion (elements: ExcalidrawElement[]):number {
if(!this.excalidrawGetSceneVersion) {
this.excalidrawGetSceneVersion = this.plugin.getPackage(this.ownerWindow).excalidrawLib.getSceneVersion;
}
return this.excalidrawGetSceneVersion(elements.filter(el=>!el.isDeleted));
}
onload() {
const apiMissing = Boolean(typeof this.containerEl.onWindowMigrated === "undefined")
//@ts-ignore
if(!app.isMobile && !apiMissing) this.containerEl.onWindowMigrated(()=>this.leaf.rebuildView());
const doc = app.isMobile?document:this.containerEl.ownerDocument;
this.ownerDocument = doc;
this.ownerWindow = this.ownerDocument.defaultView;
this.plugin.getPackage(this.ownerWindow);
this.semaphores.scriptsReady = true;
this.addAction(SCRIPTENGINE_ICON_NAME, t("INSTALL_SCRIPT_BUTTON"), () => {
new ScriptInstallPrompt(this.plugin).open();
});
@@ -886,35 +950,54 @@ export default class ExcalidrawView extends TextFileView {
}
const self = this;
this.app.workspace.onLayoutReady(() => {
self.addSlidingPanesListner();
this.app.workspace.onLayoutReady(async () => {
self.contentEl.addClass("excalidraw-view");
//https://github.com/zsviczian/excalibrain/issues/28
await self.addSlidingPanesListner(); //awaiting this because when using workspaces, onLayoutReady comes too early
self.addParentMoveObserver();
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;
};
self.ownerWindow.addEventListener("keydown", self.onKeyDown, false);
self.ownerWindow.addEventListener("keyup", self.onKeyUp, false);
});
this.setupAutosaveTimer();
this.contentEl.addClass("excalidraw-view");
}
//this is to solve sliding panes bug
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/9
private slidingPanesListner: any;
private addSlidingPanesListner() {
private async addSlidingPanesListner() {
const self = this;
this.slidingPanesListner = () => {
if (self.refresh) {
self.refresh();
}
};
(
this.app.workspace.rootSplit as WorkspaceItem as WorkspaceItemExt
).containerEl.addEventListener("scroll", this.slidingPanesListner);
let rootSplit = this.app.workspace.rootSplit as WorkspaceItem as WorkspaceItemExt;
while(!rootSplit) {
await sleep(50);
rootSplit = this.app.workspace.rootSplit as WorkspaceItem as WorkspaceItemExt;
}
rootSplit.containerEl.addEventListener("scroll", this.slidingPanesListner);
}
private removeSlidingPanesListner() {
if (this.slidingPanesListner) {
(
this.app.workspace.rootSplit as WorkspaceItem as WorkspaceItemExt
).containerEl.removeEventListener("scroll", this.slidingPanesListner);
).containerEl?.removeEventListener("scroll", this.slidingPanesListner);
}
}
@@ -1019,14 +1102,19 @@ export default class ExcalidrawView extends TextFileView {
warningUnknowSeriousError();
return;
}
const editing = api.getAppState().editingElement !== null;
const st = api.getAppState();
const editing = st.editingElement !== null;
//this will reset positioning of the cursor in case due to the popup keyboard,
//or the command palette, or some other unexpected reason the onResize would not fire...
this.refresh();
if (
this.isLoaded &&
this.semaphores.dirty &&
this.semaphores.dirty == this.file?.path &&
this.plugin.settings.autosave &&
!this.semaphores.forceSaving &&
!editing
!editing &&
st.draggingElement === null //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/630
) {
this.autosaveTimer = null;
this.semaphores.autosaving = true;
@@ -1062,7 +1150,11 @@ export default class ExcalidrawView extends TextFileView {
}
//save current drawing when user closes workspace leaf
async onunload() {
onunload() {
this.semaphores.viewunload = true;
this.ownerWindow?.removeEventListener("keydown", this.onKeyDown, false);
this.ownerWindow?.removeEventListener("keyup", this.onKeyUp, false);
if(this.getHookServer().onViewUnloadHook) {
try {
this.getHookServer().onViewUnloadHook(this);
@@ -1070,14 +1162,14 @@ export default class ExcalidrawView extends TextFileView {
errorlog({where: "ExcalidrawView.onunload", fn: this.getHookServer().onViewUnloadHook, error: e});
}
}
this.removeParentMoveObserver();
this.removeSlidingPanesListner();
const tooltip = document.body.querySelector(
const tooltip = this.containerEl?.ownerDocument?.body.querySelector(
"body>div.excalidraw-tooltip,div.excalidraw-tooltip--visible",
);
if (tooltip) {
document.body.removeChild(tooltip);
this.containerEl?.ownerDocument?.body.removeChild(tooltip);
}
this.removeParentMoveObserver();
this.removeSlidingPanesListner();
if (this.autosaveTimer) {
clearInterval(this.autosaveTimer);
this.autosaveTimer = null;
@@ -1208,6 +1300,7 @@ export default class ExcalidrawView extends TextFileView {
}
if (this.activeLoader) {
this.activeLoader.terminate = true;
this.activeLoader = null;
}
this.nextLoader = null;
api.resetScene();
@@ -1222,6 +1315,7 @@ export default class ExcalidrawView extends TextFileView {
if (clear) {
this.clear();
}
this.lastSaveTimestamp = this.file.stat.mtime;
data = this.data = data.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
this.app.workspace.onLayoutReady(async () => {
this.compatibilityMode = this.file.extension === "excalidraw";
@@ -1301,6 +1395,21 @@ export default class ExcalidrawView extends TextFileView {
this.activeLoader = null;
if (this.nextLoader) {
runLoader(this.nextLoader);
} else {
//in case one or more files have not loaded retry later hoping that sync has delivered the file in the mean time.
this.excalidrawData.getFiles().some(ef=>{
if(ef && !ef.file && ef.attemptCounter<30) {
const self = this;
const currentFile = this.file.path;
setTimeout(async ()=>{
if(self && self.excalidrawAPI && currentFile === self.file.path) {
self.loadSceneFiles();
}
},2000)
return true;
}
return false;
})
}
},0
);
@@ -1312,12 +1421,149 @@ export default class ExcalidrawView extends TextFileView {
}
}
public async synchronizeWithData(inData: ExcalidrawData) {
//check if saving, wait until not
let counter = 0;
while(this.semaphores.saving && counter++<30) {
await sleep(100);
}
if(counter>=30) {
errorlog({
where:"ExcalidrawView.synchronizeWithData",
message:`Aborting sync with received file (${this.file.path}) because semaphores.saving remained true for ower 3 seconds`,
"fn": this.synchronizeWithData
});
return;
}
this.semaphores.saving = true;
let reloadFiles = false;
try {
const deletedIds = inData.deletedElements.map(el=>el.id);
const sceneElements = this.excalidrawAPI.getSceneElements()
//remove deleted elements
.filter((el: ExcalidrawElement)=>!deletedIds.contains(el.id));
const sceneElementIds = sceneElements.map((el:ExcalidrawElement)=>el.id);
const manageMapChanges = (incomingElement: ExcalidrawElement ) => {
switch(incomingElement.type) {
case "text":
this.excalidrawData.textElements.set(
incomingElement.id,
inData.textElements.get(incomingElement.id)
);
break;
case "image":
if(inData.getFile(incomingElement.fileId)) {
this.excalidrawData.setFile(
incomingElement.fileId,
inData.getFile(incomingElement.fileId)
);
reloadFiles = true;
} else if (inData.getEquation(incomingElement.fileId)) {
this.excalidrawData.setEquation(
incomingElement.fileId,
inData.getEquation(incomingElement.fileId)
)
reloadFiles = true;
}
break;
}
if(inData.elementLinks.has(incomingElement.id)) {
this.excalidrawData.elementLinks.set(
incomingElement.id,
inData.elementLinks.get(incomingElement.id)
)
}
}
//update items with higher version number then in scene
inData.scene.elements.forEach((
incomingElement:ExcalidrawElement,
idx: number,
inElements: ExcalidrawElement[]
)=>{
const sceneElement:ExcalidrawElement = sceneElements.filter(
(element:ExcalidrawElement)=>element.id === incomingElement.id
)[0];
if(
sceneElement &&
(sceneElement.version < incomingElement.version ||
//in case of competing versions of the truth, the incoming version will be honored
(sceneElement.version === incomingElement.version &&
JSON.stringify(sceneElement) !== JSON.stringify(incomingElement))
)
) {
manageMapChanges(incomingElement);
//place into correct element layer sequence
const currentLayer = sceneElementIds.indexOf(incomingElement.id);
//remove current element from scene
const elToMove = sceneElements.splice(currentLayer,1);
if(idx === 0) {
sceneElements.splice(0,0,incomingElement);
if(currentLayer!== 0) {
sceneElementIds.splice(currentLayer,1);
sceneElementIds.splice(0,0,incomingElement.id);
}
} else {
const prevId = inElements[idx-1].id;
const parentLayer = sceneElementIds.indexOf(prevId);
sceneElements.splice(parentLayer+1,0,incomingElement);
if(parentLayer!==currentLayer-1) {
sceneElementIds.splice(currentLayer,1)
sceneElementIds.splice(parentLayer+1,0,incomingElement.id);
}
}
return;
} else if(!sceneElement) {
manageMapChanges(incomingElement);
if(idx === 0) {
sceneElements.splice(0,0,incomingElement);
sceneElementIds.splice(0,0,incomingElement.id);
} else {
const prevId = inElements[idx-1].id;
const parentLayer = sceneElementIds.indexOf(prevId);
sceneElements.splice(parentLayer+1,0,incomingElement);
sceneElementIds.splice(parentLayer+1,0,incomingElement.id);
}
} else if(sceneElement && incomingElement.type === "image") { //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/632
if(inData.getFile(incomingElement.fileId)) {
this.excalidrawData.setFile(
incomingElement.fileId,
inData.getFile(incomingElement.fileId)
);
reloadFiles = true;
}
}
})
this.previousSceneVersion = this.getSceneVersion(sceneElements);
//changing files could result in a race condition for sync. If at the end of sync there are differences
//set dirty will trigger an autosave
if(this.getSceneVersion(inData.scene.elements) !== this.previousSceneVersion) {
this.setDirty(3);
}
this.excalidrawAPI.updateScene({elements: sceneElements});
if(reloadFiles) this.loadSceneFiles();
} catch(e) {
errorlog({
where:"ExcalidrawView.synchronizeWithData",
message:`Error during sync with received file (${this.file.path})`,
"fn": this.synchronizeWithData,
error: e
});
}
this.semaphores.saving = false;
}
initialContainerSizeUpdate = false;
/**
*
* @param justloaded - a flag to trigger zoom to fit after the drawing has been loaded
*/
private async loadDrawing(justloaded: boolean) {
private async loadDrawing(justloaded: boolean, deletedElements?: ExcalidrawElement[]) {
const excalidrawData = this.excalidrawData.scene;
this.semaphores.justLoaded = justloaded;
this.initialContainerSizeUpdate = justloaded;
@@ -1341,9 +1587,12 @@ export default class ExcalidrawView extends TextFileView {
this.updateScene(
{
elements: excalidrawData.elements,
elements: excalidrawData.elements.concat(deletedElements??[]), //need to preserve deleted elements during autosave if images, links, etc. are updated
appState: {
...excalidrawData.appState,
...this.excalidrawData.selectedElementIds !== {} //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/609
? this.excalidrawData.selectedElementIds
: {},
zenModeEnabled,
viewModeEnabled,
linkOpacity: this.excalidrawData.getLinkOpacity(),
@@ -1357,7 +1606,7 @@ export default class ExcalidrawView extends TextFileView {
justloaded,
);
if (
this.app.workspace.activeLeaf === this.leaf &&
this.app.workspace.getActiveViewOfType(ExcalidrawView) === this.leaf.view &&
this.excalidrawWrapperRef
) {
//.firstElmentChild solves this issue: https://github.com/zsviczian/obsidian-excalidraw-plugin/pull/346
@@ -1391,7 +1640,7 @@ export default class ExcalidrawView extends TextFileView {
this.plugin.settings.compress !== isCompressed &&
!this.isEditedAsMarkdownInOtherView()
) {
this.setDirty();
this.setDirty(4);
}
}
@@ -1404,7 +1653,8 @@ export default class ExcalidrawView extends TextFileView {
);
}
public setDirty() {
public setDirty(debug?:number) {
//console.log(debug);
this.semaphores.dirty = this.file?.path;
this.diskIcon.querySelector("svg").addClass("excalidraw-dirty");
}
@@ -1417,7 +1667,7 @@ export default class ExcalidrawView extends TextFileView {
this.semaphores.dirty = null;
const el = api.getSceneElements();
if (el) {
this.previousSceneVersion = getSceneVersion(el);
this.previousSceneVersion = this.getSceneVersion(el);
}
this.diskIcon.querySelector("svg").removeClass("excalidraw-dirty");
}
@@ -1480,11 +1730,11 @@ export default class ExcalidrawView extends TextFileView {
await this.save();
this.plugin.openDrawing(
await this.plugin.convertSingleExcalidrawToMD(this.file),
false,
"active-pane",
);
}
onMoreOptionsMenu(menu: Menu) {
onPaneMenu(menu: Menu, source: string): void {
// Add a menu item to force the board to markdown view
if (!this.compatibilityMode) {
menu
@@ -1494,7 +1744,8 @@ export default class ExcalidrawView extends TextFileView {
.setIcon("document")
.onClick(() => {
this.openAsMarkdown();
});
})
.setSection("pane");
})
.addItem((item) => {
item
@@ -1502,13 +1753,15 @@ export default class ExcalidrawView extends TextFileView {
.setIcon(ICON_NAME)
.onClick(async () => {
this.exportExcalidraw();
});
})
.setSection("pane");
});
} else {
menu.addItem((item) => {
item
.setTitle(t("CONVERT_FILE"))
.onClick(() => this.convertExcalidrawToMD());
.onClick(() => this.convertExcalidrawToMD())
.setSection("pane");
});
}
menu
@@ -1516,6 +1769,7 @@ export default class ExcalidrawView extends TextFileView {
item
.setTitle(t("SAVE_AS_PNG"))
.setIcon(PNG_ICON_NAME)
.setSection("pane")
.onClick(async (ev) => {
if (!this.getScene || !this.file) {
return;
@@ -1535,12 +1789,14 @@ export default class ExcalidrawView extends TextFileView {
return;
}
this.savePNG();
});
})
.setSection("pane");
})
.addItem((item) => {
item
.setTitle(t("SAVE_AS_SVG"))
.setIcon(SVG_ICON_NAME)
.setSection("pane")
.onClick(async (ev) => {
if (!this.getScene || !this.file) {
return;
@@ -1562,7 +1818,7 @@ export default class ExcalidrawView extends TextFileView {
});
})
.addSeparator();
super.onMoreOptionsMenu(menu);
super.onPaneMenu(menu, source);
}
async getLibrary() {
@@ -1572,7 +1828,12 @@ export default class ExcalidrawView extends TextFileView {
private previousSceneVersion = 0;
private previousBackgroundColor = "";
private instantiateExcalidraw(initdata: any) {
private async instantiateExcalidraw(initdata: any) {
while(!this.semaphores.scriptsReady) {
await sleep(50);
}
const React = this.plugin.getPackage(this.ownerWindow).react;
const ReactDOM = this.plugin.getPackage(this.ownerWindow).reactDOM;
//console.log("ExcalidrawView.instantiateExcalidraw()");
this.clearDirty();
const reactElement = React.createElement(() => {
@@ -1668,6 +1929,9 @@ export default class ExcalidrawView extends TextFileView {
if (this.toolsPanelRef && this.toolsPanelRef.current) {
this.toolsPanelRef.current.updatePosition();
}
if(this.ownerDocument !== document) {
this.refresh(); //because resizeobserver in Excalidraw does not seem to work when in Obsidian Window
}
} catch (err) {
errorlog({
where: "Excalidraw React-Wrapper, onResize",
@@ -1675,8 +1939,8 @@ export default class ExcalidrawView extends TextFileView {
});
}
};
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
this.ownerWindow.addEventListener("resize", onResize);
return () => this.ownerWindow?.removeEventListener("resize", onResize);
}, [excalidrawWrapperRef]);
this.getSelectedTextElement = (): { id: string; text: string } => {
@@ -1952,7 +2216,7 @@ export default class ExcalidrawView extends TextFileView {
if (save) {
await this.save(false); //preventReload=false will ensure that markdown links are paresed and displayed correctly
} else {
this.setDirty();
this.setDirty(5);
}
return true;
};
@@ -2019,7 +2283,7 @@ export default class ExcalidrawView extends TextFileView {
const clearHoverPreview = () => {
if (hoverPreviewTarget) {
const event = new MouseEvent("click", {
view: window,
view: this.ownerWindow,
bubbles: true,
cancelable: true,
});
@@ -2062,8 +2326,8 @@ export default class ExcalidrawView extends TextFileView {
const event = new MouseEvent("click", {
ctrlKey: true,
metaKey: true,
shiftKey: this.plugin.shiftKeyDown,
altKey: this.plugin.altKeyDown,
shiftKey: this.shiftKeyDown,
altKey: this.altKeyDown,
});
this.handleLinkClick(this, event);
selectedTextElement = null;
@@ -2074,8 +2338,8 @@ export default class ExcalidrawView extends TextFileView {
const event = new MouseEvent("click", {
ctrlKey: true,
metaKey: true,
shiftKey: this.plugin.shiftKeyDown,
altKey: this.plugin.altKeyDown,
shiftKey: this.shiftKeyDown,
altKey: this.altKeyDown,
});
this.handleLinkClick(this, event);
selectedImageElement = null;
@@ -2087,8 +2351,8 @@ export default class ExcalidrawView extends TextFileView {
const event = new MouseEvent("click", {
ctrlKey: true,
metaKey: true,
shiftKey: this.plugin.shiftKeyDown,
altKey: this.plugin.altKeyDown,
shiftKey: this.shiftKeyDown,
altKey: this.altKeyDown,
});
this.handleLinkClick(this, event);
selectedElementWithLink = null;
@@ -2173,7 +2437,7 @@ export default class ExcalidrawView extends TextFileView {
}
if (
document.querySelector(`div.popover-title[data-path="${f.path}"]`)
this.ownerDocument.querySelector(`div.popover-title[data-path="${f.path}"]`)
) {
return;
}
@@ -2188,7 +2452,7 @@ export default class ExcalidrawView extends TextFileView {
event: mouseEvent,
source: VIEW_TYPE_EXCALIDRAW,
hoverParent: hoverPreviewTarget,
targetEl: null, //hoverPreviewTarget,
targetEl: hoverPreviewTarget, //null //0.15.0 hover editor!!
linktext: this.plugin.hover.linkText,
sourcePath: this.plugin.hover.sourcePath,
});
@@ -2197,9 +2461,9 @@ export default class ExcalidrawView extends TextFileView {
const self = this;
setTimeout(() => {
const popover =
document.querySelector(`div.popover-title[data-path="${f.path}"]`)
this.ownerDocument.querySelector(`div.popover-title[data-path="${f.path}"]`)
?.parentElement?.parentElement?.parentElement ??
document.body.querySelector("div.popover");
this.ownerDocument.body.querySelector("div.popover");
if (popover) {
self.contentEl.append(popover);
}
@@ -2207,6 +2471,10 @@ export default class ExcalidrawView extends TextFileView {
}
};
const {
Excalidraw,
} = this.plugin.getPackage(this.ownerWindow).excalidrawLib;
const excalidrawDiv = React.createElement(
"div",
{
@@ -2262,7 +2530,7 @@ export default class ExcalidrawView extends TextFileView {
},
onDragLeave: () => {},
},
React.createElement(Excalidraw.default, {
React.createElement(Excalidraw, {
ref: excalidrawRef,
width: dimensions.width,
height: dimensions.height,
@@ -2296,7 +2564,7 @@ export default class ExcalidrawView extends TextFileView {
blockOnMouseButtonDown = true;
//ctrl click
if (this.plugin.ctrlKeyDown) {
if (this.ctrlKeyDown) {
handleLinkClick();
return;
}
@@ -2312,13 +2580,14 @@ export default class ExcalidrawView extends TextFileView {
if (p.button === "up") {
blockOnMouseButtonDown = false;
}
if (this.plugin.ctrlKeyDown ||
if (this.ctrlKeyDown ||
(this.excalidrawAPI.getAppState().isViewModeEnabled &&
this.plugin.settings.hoverPreviewWithoutCTRL)) {
showHoverPreview();
}
},
libraryReturnUrl: "app://obsidian.md",
autoFocus: true,
onChange: (et: ExcalidrawElement[], st: AppState) => {
viewModeEnabled = st.viewModeEnabled;
@@ -2327,7 +2596,7 @@ export default class ExcalidrawView extends TextFileView {
if (!this.semaphores.preventAutozoom) {
this.zoomToFit(false);
}
this.previousSceneVersion = getSceneVersion(et);
this.previousSceneVersion = this.getSceneVersion(et);
this.previousBackgroundColor = st.viewBackgroundColor;
return;
}
@@ -2343,15 +2612,16 @@ export default class ExcalidrawView extends TextFileView {
st.editingGroupId === null &&*/
st.editingLinearElement === null
) {
const sceneVersion = getSceneVersion(et);
const sceneVersion = this.getSceneVersion(et);
if (
(sceneVersion > 0 &&
((sceneVersion > 0 ||
(sceneVersion === 0 && et.length > 0)) && //Addressing the rare case when the last element is deleted from the scene
sceneVersion !== this.previousSceneVersion) ||
st.viewBackgroundColor !== this.previousBackgroundColor
) {
this.previousSceneVersion = sceneVersion;
this.previousBackgroundColor = st.viewBackgroundColor;
this.setDirty();
this.setDirty(6);
}
}
},
@@ -2382,6 +2652,8 @@ export default class ExcalidrawView extends TextFileView {
this.loadSceneFiles();
toolsPanelRef?.current?.setTheme(newTheme);
},
ownerDocument: this.ownerDocument,
ownerWindow: this.ownerWindow,
onDrop: (event: React.DragEvent<HTMLDivElement>): boolean => {
const api = this.excalidrawAPI;
if (!api) {
@@ -2540,6 +2812,16 @@ export default class ExcalidrawView extends TextFileView {
if(html) {
const path = html.match(/href="app:\/\/obsidian\.md\/(.*?)"/);
if(path.length === 2) {
const link = decodeURIComponent(path[1]).split("#");
const f = app.vault.getAbstractFileByPath(link[0]);
if(f && f instanceof TFile) {
const path = app.metadataCache.fileToLinktext(f,this.file.path);
this.addText(`[[${
path +
(link.length>1 ? "#" + link[1] + "|" + path : "")
}]]`);
return;
}
this.addText(`[[${decodeURIComponent(path[1])}]]`);
return false;
}
@@ -2587,7 +2869,7 @@ export default class ExcalidrawView extends TextFileView {
if (isDeleted) {
this.excalidrawData.deleteTextElement(textElement.id);
this.setDirty();
this.setDirty(7);
return [null, null, null];
}
@@ -2602,7 +2884,7 @@ export default class ExcalidrawView extends TextFileView {
) {
//the user made changes to the text or the text is missing from Excalidraw Data (recently copy/pasted)
//setTextElement will attempt a quick parse (without processing transclusions)
this.setDirty();
this.setDirty(8);
const [parseResultWrapped, parseResultOriginal, link] =
this.excalidrawData.setTextElement(
textElement.id,
@@ -2658,6 +2940,12 @@ export default class ExcalidrawView extends TextFileView {
if (!link || link === "") {
return;
}
const tooltip = this.ownerDocument.body.querySelector(
"body>div.excalidraw-tooltip,div.excalidraw-tooltip--visible",
);
if (tooltip) {
this.ownerDocument.body.removeChild(tooltip);
}
const event = e?.detail?.nativeEvent;
if(this.getHookServer().onLinkClickHook) {
try {

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;
@@ -198,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);
});
@@ -341,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");
}
@@ -514,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,13 +163,10 @@ 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) {
const view = app.workspace.getActiveViewOfType(ExcalidrawView);
if (view) {
(async()=>{
const script = await this.plugin.app.vault.read(f);
if(script) {

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";

View File

@@ -17,6 +17,101 @@ I develop this plugin as a hobby, spending most of my free time doing this. If y
<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.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.

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,7 +36,7 @@ export class ReleaseNotes extends Modal {
? Object.keys(RELEASE_NOTES)
.filter((key) => key > prevRelease)
.map((key: string) => `# ${key}\n${RELEASE_NOTES[key]}`)
.slice(0, 6)
.slice(0, 10)
.join("\n\n---\n")
: FIRST_RUN;
await MarkdownRenderer.renderMarkdown(

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;",

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. " +
@@ -346,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}

View File

@@ -44,6 +44,7 @@ export interface ExcalidrawSettings {
zoomToFitOnResize: boolean;
zoomToFitMaxLevel: number;
openInAdjacentPane: boolean;
openInMainWorkspace: boolean;
showLinkBrackets: boolean;
linkPrefix: string;
urlPrefix: string;
@@ -100,6 +101,7 @@ export interface ExcalidrawSettings {
defaultTrayMode: boolean;
previousRelease: string;
showReleaseNotes: boolean;
mathjaxSourceURL: string;
}
export const DEFAULT_SETTINGS: ExcalidrawSettings = {
@@ -132,6 +134,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
hoverPreviewWithoutCTRL: false,
linkOpacity: 1,
openInAdjacentPane: false,
openInMainWorkspace: true,
showLinkBrackets: true,
allowCtrlClick: true,
forceWrap: false,
@@ -179,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) =>
@@ -188,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) {
@@ -228,6 +233,9 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
this.plugin.triggerEmbedUpdates();
}
this.plugin.scriptEngine.updateScriptPath();
if(this.reloadMathJax) {
this.plugin.loadMathJax();
}
}
async display() {
@@ -559,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")))
@@ -1108,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") });

10
src/types.d.ts vendored
View File

@@ -6,8 +6,15 @@ 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
@@ -114,7 +121,8 @@ export interface ExcalidrawAutomateInterface {
endArrowHead?: string; //"triangle"|"dot"|"arrow"|"bar"|null
padding?: number;
},
): void;
): 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 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 = {

3
testloader.js Normal file

File diff suppressed because one or more lines are too long

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.27": "0.12.16",
"1.7.5": "0.15.3",
"1.7.2": "0.15.2",
"1.6.34": "0.12.16",
"1.4.2": "0.11.13"
}

1224
yarn.lock

File diff suppressed because it is too large Load Diff