Compare commits
86 Commits
shape-arit
...
1.8.19
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c1030880a | ||
|
|
1b62983016 | ||
|
|
52fb7ab546 | ||
|
|
604bfbf23f | ||
|
|
3c1a3c18c2 | ||
|
|
f531c361de | ||
|
|
4609ea33bb | ||
|
|
41b1a170f7 | ||
|
|
e6d39eca75 | ||
|
|
8ca6a9fe96 | ||
|
|
48e47f333e | ||
|
|
3091ed629a | ||
|
|
a9193dd695 | ||
|
|
3122e86e22 | ||
|
|
a6efe27146 | ||
|
|
adbec35e30 | ||
|
|
205a94d3a3 | ||
|
|
6e88c8f0eb | ||
|
|
32a05322d0 | ||
|
|
8738b74236 | ||
|
|
aa0ddd85fd | ||
|
|
bcd47ddb8e | ||
|
|
50f24b42cb | ||
|
|
0a5d511c96 | ||
|
|
70d93602f7 | ||
|
|
68c7c9f55e | ||
|
|
42f1fa88b9 | ||
|
|
3a2d064024 | ||
|
|
38cfdd4e0e | ||
|
|
fb93c0c352 | ||
|
|
f4fb1e3cc8 | ||
|
|
5c11e5733e | ||
|
|
8d8f7a7866 | ||
|
|
da89e32213 | ||
|
|
b6d36e5076 | ||
|
|
90c377f125 | ||
|
|
df85138890 | ||
|
|
654b656a2f | ||
|
|
1d22dcb488 | ||
|
|
a42e907d0c | ||
|
|
353cad21d6 | ||
|
|
2b02f186a6 | ||
|
|
3e12d1e815 | ||
|
|
539bbdf07f | ||
|
|
b993b358fe | ||
|
|
0639ea5969 | ||
|
|
05b7bc6029 | ||
|
|
c9755be0e9 | ||
|
|
9526fb5726 | ||
|
|
c04587ab5c | ||
|
|
ab94fe304b | ||
|
|
b080fec85e | ||
|
|
c9972116b3 | ||
|
|
c254c8cc5d | ||
|
|
14c2ce3766 | ||
|
|
437a01c194 | ||
|
|
da98aca107 | ||
|
|
e81f4d3688 | ||
|
|
f280704744 | ||
|
|
9547ced85c | ||
|
|
356a0b7a63 | ||
|
|
171e37b4e5 | ||
|
|
cfb070cfe5 | ||
|
|
76e2a32998 | ||
|
|
23a4f42c27 | ||
|
|
8202cf0dde | ||
|
|
40f88bb900 | ||
|
|
e6f5f9469a | ||
|
|
0502ac3bb0 | ||
|
|
36125c9b83 | ||
|
|
cced2ca2e4 | ||
|
|
0de2c78cac | ||
|
|
7848c8f705 | ||
|
|
f6c135227b | ||
|
|
95ca41fcaa | ||
|
|
d7648d702a | ||
|
|
5033a7cccf | ||
|
|
1a83abb256 | ||
|
|
08f616b5d9 | ||
|
|
ca44699e8d | ||
|
|
e0111e264c | ||
|
|
c6196a86a9 | ||
|
|
3926e5c30b | ||
|
|
a1256422fa | ||
|
|
eeb47d4912 | ||
|
|
8ceac4ab31 |
62
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,32 +1,30 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Environment (please complete the following information):**
|
||||
- OS including version: [e.g. iOS 15.1, Android 9, Windows 11, etc]
|
||||
- Plugin version:
|
||||
- Obsidian version:
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help me improve Excalidraw
|
||||
title: 'BUG: '
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Your environment**
|
||||
Please run `Command Palette/Show Debug info` in Obsidian and paste the result here.
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
40
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,20 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: 'FR: '
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
|
||||
5
.gitignore
vendored
@@ -15,5 +15,6 @@ data.json
|
||||
lib
|
||||
|
||||
#VSCode
|
||||
.vscode
|
||||
yarn.lock
|
||||
.vscode
|
||||
yarn.lock
|
||||
.DS_Store
|
||||
|
||||
32
README-BUILD.md
Normal file
@@ -0,0 +1,32 @@
|
||||
The project runs with `node 16.10.0`. Some packages will throw dependency errors if you try to compile with a higher node version.
|
||||
|
||||
After running `npm -i` you'll need to make two manual changes:
|
||||
|
||||
## postprocess
|
||||
postprocess is used in rollup.config.js.
|
||||
However, the version available on npmjs does not work, after installing packages you need this update:
|
||||
`npm install brettz9/rollup-plugin-postprocess#update --save-dev``
|
||||
|
||||
More info here: https://github.com/developit/rollup-plugin-postprocess/issues/10
|
||||
|
||||
## colormaster
|
||||
1.2.1 misses 3 plugin references after installing the package you need to update
|
||||
`node_modules/colormaster/package.json` adding the following to the `exports:` section:
|
||||
```typescript
|
||||
,
|
||||
"./plugins/luv": {
|
||||
"import": "./plugins/luv.mjs",
|
||||
"require": "./plugins/luv.js",
|
||||
"default": "./plugins/luv.mjs"
|
||||
},
|
||||
"./plugins/uvw": {
|
||||
"import": "./plugins/uvw.mjs",
|
||||
"require": "./plugins/uvw.js",
|
||||
"default": "./plugins/uvw.mjs"
|
||||
},
|
||||
"./plugins/ryb": {
|
||||
"import": "./plugins/ryb.mjs",
|
||||
"require": "./plugins/ryb.js",
|
||||
"default": "./plugins/ryb.mjs"
|
||||
}
|
||||
```
|
||||
349
README.md
@@ -1,120 +1,287 @@
|
||||
# Excalidraw
|
||||
|
||||
The Obsidian-Excalidraw plugin integrates [Excalidraw](https://excalidraw.com/), a feature rich sketching tool, into Obsidian. You can store and edit Excalidraw files in your vault, you can embed drawings into your documents, and you can link to documents and other drawings to/and from Excalidraw. For a showcase of Excalidraw features, please read my blog post [here](https://www.zsolt.blog/2021/03/showcasing-excalidraw.html) and/or watch the videos below.
|
||||
|
||||
Please upgrade to Obsidian v0.12.19 or higher to get the latest release.
|
||||
## Video Walkthrough
|
||||
|
||||

|
||||
<a href="https://youtu.be/o0exK-xFP3k" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/156931370-aa4d88de-c4a8-46cc-aeb2-dc09aa0bea39.jpg" width="300"/></a>
|
||||
|
||||
# Video walkthrough
|
||||
| | | |
|
||||
|----|----|----|
|
||||
|[](https://youtu.be/o0exK-xFP3k)| | |
|
||||
|[](https://youtu.be/UxJLLYtgDKE)|[](https://youtu.be/sY4FoflGaiM)|[](https://youtu.be/Iy_oVTq12Gw)|
|
||||
|[](https://youtu.be/QOL1KF7-kdc)|[](https://youtu.be/aSgcbfspvfo)|[](https://youtu.be/MaJ5jJwBRWs)|
|
||||
|[](https://youtu.be/MXzeCOEExNo)|[](https://youtu.be/R0IAg0s-wQE)|[](https://youtu.be/ibdS7ykwpW4)|
|
||||
|[](https://youtu.be/VRZVujfVab0)|[](https://youtu.be/D1iBYo1_jjc)|[](https://www.youtube.com/watch?v=_c_0zpBJ4Xc&)|
|
||||
|[](https://youtu.be/r08wk-58DPk)|[](https://youtu.be/tsecSfnTMow)|[](https://youtu.be/K6qZkTz8GHs)|
|
||||
|[](https://youtu.be/hePJcObHIso)|[](https://youtu.be/NOuddK6xrr8)|[](https://youtu.be/lzYdOQ6z8F0)|
|
||||
|[](https://youtu.be/eKFmrSQhFA4)|[](https://youtu.be/qbPIAZguJeo)|[](https://youtu.be/2Y8OhkGiTHg)|
|
||||
|[](https://youtu.be/2v9TZmQNO8c)|[](https://youtu.be/xHPGWR3m0c8)|[](https://youtu.be/gMIKXyhS-dM)|
|
||||
|[](https://youtu.be/Etskjw7a5zo)|[](https://youtu.be/4N6efq1DtH0)|[](https://youtu.be/U2LkBRBk4LY)|
|
||||
| [](https://youtu.be/qiKuqMcNWgU)|[](https://youtu.be/yZQoJg2RCKI)|[](https://youtu.be/6PLGHBH9VZ4) |
|
||||
|[](https://youtu.be/epYNx2FSf2w) | [](https://youtu.be/Amhlv6r9WvM) | [](https://youtu.be/r9oB1SlK1GU) |
|
||||
|[](https://youtu.be/7gJDwNgQ6NU) | | |
|
||||
<details><summary>10 Part (slightly outdated) Video Walkthrough</summary>
|
||||
<a href="https://youtu.be/sY4FoflGaiM" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/125160304-7f211180-e17c-11eb-8363-c52723de1ffd.jpg" width="100" style="vertical-align: middle;"/> 1 Getting Started</a><br>
|
||||
<a href="https://youtu.be/Iy_oVTq12Gw" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/125160312-8a743d00-e17c-11eb-9fa2-490ef4cbd59e.jpg" width="100" style="vertical-align: middle;"/> 2 Basic shapes and features</a><br>
|
||||
<a href="https://youtu.be/QOL1KF7-kdc" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/125160323-96f89580-e17c-11eb-9bce-8eb1067a51bb.jpg" width="100" style="vertical-align: middle;"/> 3 Grouping elements</a><br>
|
||||
<a href="https://youtu.be/aSgcbfspvfo" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/125160332-9f50d080-e17c-11eb-98e9-fec60fe147d9.jpg" width="100" style="vertical-align: middle;"/> 4 The stencil-library</a><br>
|
||||
<a href="https://youtu.be/MaJ5jJwBRWs" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/125160341-a546b180-e17c-11eb-9de8-d87fdc844c9c.jpg" width="100" style="vertical-align: middle;"/> 5 Embedding</a><br>
|
||||
<a href="https://youtu.be/MXzeCOEExNo" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/125160346-aa0b6580-e17c-11eb-930b-4024807040d1.jpg" width="100" style="vertical-align: middle;"/> 6 Links</a><br>
|
||||
<a href="https://youtu.be/R0IAg0s-wQE" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/125160354-b2fc3700-e17c-11eb-81af-9e71e461f6dd.jpg" width="100" style="vertical-align: middle;"/> 7 Markdown</a><br>
|
||||
<a href="https://youtu.be/ibdS7ykwpW4" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/125160360-b8f21800-e17c-11eb-8bd8-79d4e3f6e92d.jpg" width="100" style="vertical-align: middle;"/> 8 Templates</a><br>
|
||||
<a href="https://youtu.be/VRZVujfVab0" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/125160367-bdb6cc00-e17c-11eb-92f1-6f59faea85fd.jpg" width="100" style="vertical-align: middle;"/> 9 Excalidraw Automate</a><br>
|
||||
<a href="https://youtu.be/D1iBYo1_jjc" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/125160374-c3141680-e17c-11eb-8cc2-dfaffd903d15.jpg" width="100" style="vertical-align: middle;"/> 10 Miscellaneous</a><br>
|
||||
</details>
|
||||
<details><summary>Embedding stuff into Excalidraw</summary>
|
||||
<a href="https://www.youtube.com/watch?v=_c_0zpBJ4Xc&" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/138607067-ccb62f92-48a4-4880-ac6e-68c1bf86ac2c.png" width="100" style="vertical-align: middle;"/> Image Elements</a><br>
|
||||
<a href="https://youtu.be/r08wk-58DPk" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/143732412-1c65227e-4381-406d-847a-b001ab3506ca.jpg" width="100" style="vertical-align: middle;"/> LaTex Demo</a><br>
|
||||
<a href="https://youtu.be/tsecSfnTMow" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/143732440-90bfa029-8615-462e-ada3-c903d71a82c9.jpg" width="100" style="vertical-align: middle;"/> Markdown embeds</a><br>
|
||||
<a href="https://youtu.be/K6qZkTz8GHs" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/143783906-15cee494-c6d5-4495-a2ca-74634e4e7355.jpg" width="100" style="vertical-align: middle;"/> Markdown embeds advanced features</a><br>
|
||||
<a href="https://youtu.be/Etskjw7a5zo" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/156931461-0979b821-315a-41dd-86f1-31d169b7c127.jpg" width="100" style="vertical-align: middle;"/> Link to Elements, Vertical text alignment, Markdown Styling</a><br>
|
||||
</details>
|
||||
<details><summary>The Script Engine Store - Excalidraw Automation</summary>
|
||||
<a href="https://youtu.be/hePJcObHIso" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/145684531-8d9c2992-59ac-4ebc-804a-4cce1777ded2.jpg" width="100" style="vertical-align: middle;"/> Introducing the Script Engine</a><br>
|
||||
<a href="https://youtu.be/lzYdOQ6z8F0" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/147889174-6c306d0d-2d29-46cc-a53f-3f0013cf14de.jpg" width="100" style="vertical-align: middle;"/> Script Enginge Store</a><br>
|
||||
</details>
|
||||
<details><summary>Working with colors</summary>
|
||||
<a href="https://youtu.be/6PLGHBH9VZ4" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/194773147-5418a0ab-6be5-4eb0-a8e4-d6af21b1b483.png" width="100" style="vertical-align: middle;"/> Colors - Excalidraw Basics (Custom)</a><br>
|
||||
<a href="https://youtu.be/epYNx2FSf2w" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/194773211-9e871be7-0795-4dc7-947e-c6c275e690d0.png" width="100" style="vertical-align: middle;"/> Excalidraw color palettes (Custom)</a><br>
|
||||
<a href="https://youtu.be/Amhlv6r9WvM" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/194773268-400cfb1b-6bde-45e0-9e4b-91bbaa461cf0.png" width="100" style="vertical-align: middle;"/> "Artistic" Color Gradients</a><br>
|
||||
<a href="https://youtu.be/r9oB1SlK1GU" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/194773527-ef35c8b9-1a6d-4415-9c7e-b667fb17535d.png" width="100" style="vertical-align: middle;"/> Simple rules for beautiful sketches</a><br>
|
||||
<a href="https://youtu.be/7gJDwNgQ6NU" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/195988535-a133a9b9-d094-45ba-ba64-c994b9a1e0ef.png" width="100" style="vertical-align: middle;"/> ColorMaster Scripting</a><br>
|
||||
</details>
|
||||
<details><summary>Links and block references</summary>
|
||||
<a href="https://youtu.be/qiKuqMcNWgU" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/171635214-30533c45-94fa-436e-83a9-b2ec99f190e2.jpg" width="100" style="vertical-align: middle;"/> 6 strategies for linking your visual thoughts v4</a><br>
|
||||
<a href="https://youtu.be/yZQoJg2RCKI" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/185791706-3d9983ab-7cb1-4b27-a016-30c039d84e34.jpg" width="100" style="vertical-align: middle;"/> Block reference parts of images</a><br>
|
||||
<a href="https://youtu.be/Etskjw7a5zo" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/156931461-0979b821-315a-41dd-86f1-31d169b7c127.jpg" width="100" style="vertical-align: middle;"/> Link to Elements, Vertical text alignment, Markdown Styling</a><br>
|
||||
<a href="https://youtu.be/2Y8OhkGiTHg" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/152585752-7eb0371f-0bab-40f6-a194-3b48e5811735.jpg" width="100" style="vertical-align: middle;"/> How to guide for the Excalidraw-native hyperlinks</a><br>
|
||||
</details>
|
||||
<details><summary>Powertools</summary>
|
||||
<a href="https://youtu.be/NOuddK6xrr8" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/147283367-e5689385-ea51-4983-81a3-04d810d39f62.jpg" width="100" style="vertical-align: middle;"/> Sticky Notes (word wrapping)</a><br>
|
||||
<a href="https://youtu.be/eKFmrSQhFA4" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/149659524-2a4e0a24-40c9-4e66-a6b1-c92f3b88ecd5.jpg" width="100" style="vertical-align: middle;"/> Fourt Font</a><br>
|
||||
<a href="https://youtu.be/vlC1-iBvIfo" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/199207784-8bbe14e0-7d10-47d7-971d-20dce8dbd659.png" width="100" style="vertical-align: middle;"/> SVG import</a><br>
|
||||
<a href="https://youtu.be/7gu4ETx7zro" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/202916770-28f2fa64-1ba2-4b40-a7fe-d721b42634f7.png" width="100" style="vertical-align: middle;"/> OCR</a><br>
|
||||
<a href="https://youtu.be/U2LkBRBk4LY" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/159369910-6371f08d-b5fa-454d-9c6c-948f7e7a7d26.jpg" width="100" style="vertical-align: middle;"/> Bind/unbind text from container, Frontmatter tags to customize export</a><br>
|
||||
<a href="https://youtu.be/uZz5MgzWXiM" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/211054371-8872e01a-77d6-4afc-a0c2-86a55410a8d3.png" width="100" style="vertical-align: middle;"/> Custom pen support</a><br>
|
||||
</details>
|
||||
<details><summary>Quality of life improvements</summary>
|
||||
<a href="https://youtu.be/qbPIAZguJeo" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/151705333-54e9ffd2-0bd7-4d02-b99e-0bd4e4708d4d.jpg" width="100" style="vertical-align: middle;"/> Mobile Support</a><br>
|
||||
<a href="https://youtu.be/2v9TZmQNO8c" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/153676009-6f86b2d7-c248-49a2-b802-be21c6999e4f.jpg" width="100" style="vertical-align: middle;"/> Tray-mode and Customizable Color Palette</a><br>
|
||||
<a href="https://youtu.be/xHPGWR3m0c8" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/154821232-a404b6cf-72fb-4ce4-9d53-619132dce491.jpg" width="100" style="vertical-align: middle;"/> Compressed JSON and improved save/sync support</a><br>
|
||||
<a href="https://youtu.be/gMIKXyhS-dM" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/156931428-b2269fd9-87bd-43ab-8558-5572f40dff93.jpg" width="100" style="vertical-align: middle;"/> The Obsidian Tools Panel</a><br>
|
||||
<a href="https://youtu.be/4N6efq1DtH0" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/158008902-12c6a851-237e-4edd-a631-d48e81c904b2.jpg" width="100" style="vertical-align: middle;"/> Eraser, left-handed mode, improved filename configuration</a><br>
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
# Key features
|
||||
- The plugin integrates Excalidraw seamlessly into Obsidian including Command Palette actions, File Explorer features, Option Menu commands, and the Ribbon Button.
|
||||
- <kbd>CTRL/CMD+Click</kbd> on the ribbon button, or in the file explorer to create / open drawings in a new pane.
|
||||
- Settings will allow you to customize Excalidraw to your needs:
|
||||
- Default folder for new drawings and define custom filename pattern for new drawings.
|
||||
- Template for new drawings. The template will restore stroke properties. This means you can set up defaults in your template for stroke color, stroke width, opacity, font family, font size, fill style, stroke style, etc. This also applies to ExcalidrawAutomate.
|
||||
- Via the template you can customize the color palette used by Excalidraw. Switch to Markdown view. Scroll down to the bottom of the file and find `"AppState": {`. Find `"customColorPalette": {` at the end of the AppState section. You may specify the 3 palettes used in Excalidraw by adding any or all of the following 3 variables: `"canvasBackground":[], "elementBackground":[], "elementStroke": []`. Add a comma separated list of valid HTML colors (e.g. `#FF0000` for red) in the array for each of the variables. See my videos above for further help.
|
||||
- If portability is important to you: Auto-export SVG and/or PNG files including keep-in-sync feature so you can embed SVG/PNG into your documents instead of embedding excalidraw files. You can override export settings for an individual file by adding the `excalidraw-autoexport` frontmatter key. Valid values for this key are `none`, `both`, `png` and `svg`.
|
||||
- Specify the default width of embedded drawings.
|
||||
- Compatibility features to auto-export and keep in sync markdown excalidraw files and legacy .excalidraw files.
|
||||
- Experimental feature to add custom TAG to file explorer to mark drawing files.
|
||||
- Enable / disable autosave.
|
||||
- You can customize the size and position of the embedded images using the `![[image.excalidraw|100]]`, `![[image.excalidraw|100x100]]`, `![[image.excalidraw|100|left]]`, `![[image.excalidraw|right-wrap]]`, formatting options. `![[<filename.excalidraw>|<width>x<height>|<alignment>]]`. You can add your custom [alignment via CSS](https://www.scaler.com/topics/align-image-in-html/). Any text that appears in `<alignment>` will be added to the rendered SVG element style and to the wrapper DIV element. See [styles.css](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/styles.css) for more insight.
|
||||
- Supports hyperlinks e.g. `https://zsolt.blog`, `[Obsidian](https://obsidian.md)`, and internal links e.g. `[[My file in vault|Alias]]` in drawing text.
|
||||
- Links will update when files are moved or renamed, if you have the Obsidian setting Files & Links/Automatically Update Internal Links enabled.
|
||||
- Links in drawings will show up in backlinks of documents
|
||||
- Transclusions are supported
|
||||
- The plugin integrates Excalidraw seamlessly into Obsidian including Command Palette actions, File
|
||||
Explorer features, Option Menu commands, and the Ribbon Button.
|
||||
- <kbd>CTRL/CMD+Click</kbd> on the ribbon button, or in the file explorer to create / open drawings
|
||||
in a new pane.
|
||||
|
||||
### Settings
|
||||
|
||||
Settings will allow you to customize Excalidraw to your needs. The plugin comes with tons of settings. I tried adding meaningful explanations to these settings, so please be patient and look for the setting, for most requests a setting already exists.
|
||||
|
||||
Plugin settings are grouped into the following sections:
|
||||
- **Basic settings**: such as default folders to use
|
||||
- **Saving**: compression and autosave timer
|
||||
- **Filename**: configure the automatically created Excalidraw filename
|
||||
- **Display**: settings that effect the handling of Excalidraw (e.g.: left-handed mode, theme settings, mouse wheel and pinch zoom settings, zoom to fit settings)
|
||||
- **Links and transclusions**: Settings that effect how links and embedded items behave on the Excalidraw canvas
|
||||
- **Markdown-embed settings**: These settings control how markdown documents from your Vault embedded into Excalidraw drawings will behave
|
||||
- **Embed & Export**: Settings that control how Excalidraw images are displayed when embedding them into markdown documents
|
||||
- **Auto-export Settings**: You can configure Excalidraw to create a PNG or SVG copy of your drawing each time it gets saved
|
||||
- **Compatibility features**: Check these settings if you edit the Excalidraw drawings outside Obsidian (e.g. in LogSeq, Visual Studio, on the web, etc.)
|
||||
- **Experimental features**: There are advanced features that are implemented as "clever" hacks. Features include defining a fourth font, adding a custom icon to distinguish Exalidraw files in the Obsidian file explorer, OCR settings, and more.
|
||||
- **Settings for installed Scripts**: Some of the scripts you install from the Script Library come with settings. Script settings are installed the first time you run the script. So to access settings for a script, install the script, run it for the first time, then look for the settings in plugin settings.
|
||||
|
||||
#### Templates
|
||||
|
||||
- Template for new drawings. The template will restore stroke properties.
|
||||
This means you can set up defaults in your template for stroke color, stroke width,
|
||||
opacity, font family, font size, fill style, stroke style, etc.
|
||||
This also applies to ExcalidrawAutomate.
|
||||
- Via the template you can customize the color palette used by Excalidraw.
|
||||
- Switch to Markdown view.
|
||||
- Scroll down to the bottom of the file and find `"AppState": {`.
|
||||
- Find `"customColorPalette": {` at the end of the AppState section.
|
||||
- You may specify the 3 palettes used in Excalidraw by adding any or all of the following 3 variables:
|
||||
- `"canvasBackground":[], "elementBackground":[], "elementStroke": []`.
|
||||
- Add a comma separated list of valid HTML colors (e.g. `#FF0000` for red)
|
||||
in the array for each of the variables.
|
||||
- See my videos above for further help.
|
||||
|
||||
#### Export
|
||||
|
||||
- If portability is important to you:
|
||||
- Auto-export SVG and/or PNG files including keep-in-sync feature so you can
|
||||
embed SVG/PNG into your documents instead of embedding excalidraw files.
|
||||
- You can override export settings for an individual file by adding the `excalidraw-autoexport`
|
||||
frontmatter key. Valid values for this key are `none`, `both`, `png` and `svg`.
|
||||
|
||||
- Specify the default width of embedded drawings.
|
||||
- Compatibility features to auto-export and keep in sync markdown excalidraw files and legacy `.excalidraw` files.
|
||||
- Experimental feature to add custom TAG to file explorer to mark drawing files.
|
||||
- Enable / disable autosave.
|
||||
|
||||
### Embedding your drawings into markdown documents
|
||||
- You can customize the size and position of the embedded images using the
|
||||
- `![[image.excalidraw|100]]`,
|
||||
- `![[image.excalidraw|100x100]]`,
|
||||
- `![[image.excalidraw|100|left]]`,
|
||||
- `![[image.excalidraw|right-wrap]]`, formatting options.
|
||||
- `![[<filename.excalidraw>|<width>x<height>|<alignment>]]`.
|
||||
- You can add your custom [alignment via CSS](https://www.scaler.com/topics/align-image-in-html/).
|
||||
- Any text that appears in `<alignment>` will be added to the rendered SVG element style and to the wrapper DIV element.
|
||||
- See [styles.css](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/styles.css) for more insight.
|
||||
- Excalidraw drawings do not display in Obsidian Publish. If you want to use Excalidraw in your published documents, you can configure in plugin settings, under `Embed & Export`, to automatically insert a PNG or SVG version of the drawing in your document when creating a new file. See `type of file to insert into document`
|
||||
- Under `Export settings` you can also configure to automatically export a dark and light version of the image, in case your published site supports dark and light mode.
|
||||
|
||||
### Hyperlinks and Drag & Drop support
|
||||

|
||||
|
||||
#### Hyperlinks
|
||||
- Supports hyperlinks e.g.
|
||||
- `https://zsolt.blog`,
|
||||
- `[Obsidian](https://obsidian.md)`, and
|
||||
- internal links e.g. `[[My file in vault|Alias]]` in drawing text.
|
||||
- Links will update when files are moved or renamed, if you have the Obsidian
|
||||
setting Files & Links/Automatically Update Internal Links enabled.
|
||||
- Links in drawings will show up in backlinks of documents
|
||||
- Transclusions are supported
|
||||
- `![[myfile#^blockref]]` will convert in the drawing into the transcluded text of the block
|
||||
- `![[myfile#section]]` also works, this will transclude the section
|
||||
- you can also specify word wrapping for transcluded text by adding the max character count in curly brackets right after the transclusion e.g. `![[myfile#^blockref]]{40}` will wrap text at 40 characters.
|
||||
- For convenience you can also use the command palette to insert links into drawings
|
||||
- <kbd>CTRL/CMD + hover</kbd> to bring up the Obsidian quick preview for the link. (On Mac it is <kbd>CTRL+CMD+hover</kbd>).
|
||||
- <kbd>CTRL/CMD + CLICK</kbd> a text element to open it as a link.
|
||||
- <kbd>CTRL/CMD + ALT + CLICK</kbd> to create the file (if it does not yet exist) and open it
|
||||
- <kbd>CTRL/CMD + SHIFT + CLICK</kbd> to open the file in a new pane
|
||||
- <kbd>CTRL/CMD + ALT + SHIFT + CLICK</kbd> to create the file (if it does not yet exist) and open it in a new pane
|
||||
- you can also specify word wrapping for transcluded text by adding the max character count
|
||||
in curly brackets right after the transclusion e.g. `![[myfile#^blockref]]{40}` will wrap text at 40 characters.
|
||||
- For convenience you can also use the command palette to insert links into drawings
|
||||
- <kbd>CTRL/CMD + hover</kbd> to bring up the Obsidian quick preview for the link. (On Mac it is <kbd>CTRL+CMD+hover</kbd>).
|
||||
- Using the block reference you can also reference & transclude text that appears on drawings, in other documents
|
||||
- Insert LaTeX formulas using the Command Palette action "Insert LaTeX formula". You can edit formulas either in Markdown view, or by <kbd>CTRL/CMD + Click</kbd> on the formula.
|
||||
- Drag & Drop support
|
||||
- You can drag files from the Obsidian file explorer and they will become links to those files in Excalidraw.
|
||||
- Dragging image files (PNG, SVG, JPG, ICO, GIF, WEBP, Excalidraw) from Obsidian's file explorer while pressing the <kbd>CTRL</kbd> (<kbd>SHIFT</kbd> on Mac) button will embed the image into your drawing.
|
||||
- If in addition to <kbd>CTRL</kbd> or <kbd>SHIFT</kbd> you also hold down <kbd>ALT<kbd>, the image will be inserted at 100% of its size. ⚠ Note: this is a very niche feature with a very particular behavior that I built primarily for myself (even more so than other features in Excalidraw Obsidian - also built primarily for myself 😉)... This will reset your embedded image to 100% size every time you open the Excalidraw drawing, or in case you have embedded an Excalidraw drawing on your canvas inserted using this function, every time you update the embedded drawing, it will be scaled back to 100% size. This means that even if you resize the image on the drawing, it will reset to 100% the next time you open the file or you modify the original embedded object. This feature is useful when you decompose a drawing into separate Excalidraw files, but when combined onto a single canvas you want the individual pieces to maintain their actual sizes. I use this feature to construct Book-on-a-Page summaries from atomic drawings.
|
||||
- You can drag and drop text from Markdown views onto Excalidraw.
|
||||
- You can drag and drop web addresses from your browser and they will become links.
|
||||
- Image support
|
||||
- On iOS and Android you can add images from your camera by pressing the add image button in Excalidraw.
|
||||
- You can copy/paste images into your drawing. Images will be saved in your vault.
|
||||
- You can drag and drop images as explained above.
|
||||
- Block referencing parts of images
|
||||
- When referencing an element on the canvas in a link pointing to an Excalidraw file using the elementId or the section header (i.e. a Text Element containing the `# <Section title>`) - e.g. `[[file#^elementID]]`, you can add the `group=` prefix, e.g. `[[file#^group=elementID]]` or the `area=` prefix, e.g. `[[file#area=Section heading]]`.
|
||||
- If the `group=` prefix is found Excalidraw will select the group of elements in the same group as the element referenced by the elementID (block reference) or the section heading.
|
||||
|
||||
#### Drag & Drop support
|
||||
- You can drag files from the Obsidian file explorer and they will become links to those files in Excalidraw. See table above for the varios modifier key combinations.
|
||||
- Note: anchoring an image to 100% of its size is a very niche feature with a very particular behavior that I built primarily for myself
|
||||
- (even more so than other features in Excalidraw Obsidian - also built primarily for myself 😉).
|
||||
- This will reset your embedded image to 100% size every time you open the Excalidraw drawing,
|
||||
or in case you have embedded an Excalidraw drawing on your canvas inserted using this function,
|
||||
every time you update the embedded drawing, it will be scaled back to 100% size.
|
||||
- This means that even if you resize the image on the drawing, it will reset to 100% the next time you open
|
||||
the file or you modify the original embedded object. This feature is useful when you
|
||||
decompose a drawing into separate Excalidraw files, but when combined onto a single canvas
|
||||
you want the individual pieces to maintain their actual sizes. I use this feature to
|
||||
construct Book-on-a-Page summaries from atomic drawings.
|
||||
- You can drag and drop text from Markdown views onto Excalidraw.
|
||||
- You can drag and drop web addresses from your browser and they will become links.
|
||||
- You can drag and drop YouTube links and thumbnails and they will be YouTube links with thumbnails in Excalidraw
|
||||
|
||||
### LaTeX
|
||||
Insert LaTeX formulas using the Command Palette action "Insert LaTeX formula".
|
||||
You can edit formulas either in Markdown view, or by <kbd>CTRL/CMD + Click</kbd> on the formula.
|
||||
|
||||
### Image support
|
||||
- On iOS and Android you can add images from your camera by pressing the add image button in Excalidraw.
|
||||
- You can copy/paste images into your drawing. Images will be saved in your vault.
|
||||
- You can drag and drop images as explained above.
|
||||
- URL link to images on the web: You can drag images from a webpage to Excalidraw. If you hold down the CTRL button while dropping the image to Excalidraw, the image will not be saved to your vault. Excalidraw will load the image from the URL. Note, that if you do not have internet access, or these images are deleted from the internet, they will also disappear from your drawing.
|
||||
- If you page an image URL to excalidraw (simply click copy on the url, then click paste on the excalidraw canvas), the image will be inserted with a link to the image on the web. Again, the image won't be save to your vault, only the link.
|
||||
- If you drop a YouTube video link it will be convereted into a thumbnail photo with an element link pointing to the video.
|
||||
|
||||
### Block referencing parts of images
|
||||
For more details see this [video](https://youtu.be/yZQoJg2RCKI)
|
||||
- When referencing an element on the canvas in a link pointing to an Excalidraw file using
|
||||
- the elementId or the section header (i.e. a Text Element containing the `# <Section title>`)
|
||||
- e.g. `[[file#^elementID]]`,
|
||||
- you can add the `group=` prefix,
|
||||
- e.g. `[[file#^group=elementID]]` or
|
||||
- the `area=` prefix,
|
||||
- e.g. `[[file#area=Section heading]]`.
|
||||
- If the `group=` prefix is found Excalidraw will select the group of elements in the
|
||||
same group as the element referenced by the elementID (block reference) or the section heading.
|
||||
- If the `area=` prefix is found Excalidraw will insert a cutout of the image around the referenced element.
|
||||
- Note that the `area=` selector is not supported when embedding Excalidraw as PNG into your markdown documents.
|
||||
- Referencing the elementID of a text element without the `group=` or `area=` prefix will transclude the element as plain text. Referencing a non-Text Element (e.g. rectangle, ellipse, etc.) without the `group=` or `area=` prefix will result in an Obsidian error since these elementIds are not present in the Excalidraw markdown file as block references.
|
||||
- Referencing the elementID of a text element without the `group=` or `area=` prefix will
|
||||
transclude the element as plain text. Referencing a non-Text Element (e.g. rectangle,
|
||||
ellipse, etc.) without the `group=` or `area=` prefix will result in an Obsidian error
|
||||
since these elementIds are not present in the Excalidraw markdown file as block
|
||||
references.
|
||||
|
||||
### Markdown
|
||||
- Since 1.2.0 Drawing files are stored in Markdown files
|
||||
- You can add tags to drawings
|
||||
- You can add metadata to the YAML front matter of drawings
|
||||
- Anything you add between the frontmatter and the `# Text Elements` heading will be ignored by Excalidraw, i.e. you can add whatever you like here, it will be preserved as part of the document.
|
||||
- Excalidraw documents now show in graph view.
|
||||
- The following front matter keys will customize how the drawing is displayed - overriding general settings:
|
||||
- `excalidraw-link-prefix: "📍"` preview prefix for internal links
|
||||
- `excalidraw-url-prefix: "🌐"` preview prefix for external links
|
||||
- `excalidraw-link-brackets: true|false` whether or not to display brackets around links in preview
|
||||
- `excalidraw-default-mode: view|zen` Open this document in view mode or zen mode by defult. Default view mode is excellent for presentation slides.
|
||||
- Frontmatter tags to customize image export at a file level [519](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/519). If these keys are present they will override the default excalidraw embed and export settings.
|
||||
- `excalidraw-export-transparent: true`: true == Transparent / false == with background.
|
||||
- `excalidraw-export-dark`: true == Dark mode / false == light mode.
|
||||
- `excalidraw-export-padding`: Specify the export padding for the image
|
||||
- `excalidraw-export-pngscale`: This only affects export to PNG. Specify the export scale for the image. The typical range is between 0.5 and 5, but you can experiment with other values as well.
|
||||
- Embed complete markdown files into your drawings
|
||||
- Drag from the desired file from the Obsidian file explorer and hold down <kbd>CTRL/CMD</kbd> while dropping the file onto the canvas.
|
||||
- Use the command palette action: `Insert markdown file from vault`
|
||||
- Use custom woff, woff2 or TTF font to display the document, you can set the default font to use under Excalidraw Settings.
|
||||
- You can set a custom css for rendering the snapshot image of your markdown document. Only operating system standard fonts are supported as the font-family ([Win10](https://docs.microsoft.com/en-us/typography/fonts/windows_10_font_list), [Mac & iOS](https://developer.apple.com/fonts/system-fonts/)), plus you can set one additional custom font using the setting explained above. (for a demonstration watch this [video](https://youtu.be/K6qZkTz8GHs) and check out this [sample css](https://github.com/zsviczian/obsidian-excalidraw-plugin/discussions/281)).
|
||||
- To help with styling you can observe the SVG snapshot of the markdown document created by Excalidraw. Open Obsidian Developer Console (<kbd>CTRL+Shift+i</kbd>) and execute the following command: `ExcalidrawAutomate.mostRecentMarkdownSVG`
|
||||
- You can control appearance of the embedded markdown file on a file by file bases by adding the following front matter keys to your markdown document:
|
||||
- You can add tags to drawings
|
||||
- You can add metadata to the YAML front matter of drawings
|
||||
- Anything you add between the frontmatter and the `# Text Elements` heading will be ignored by Excalidraw, i.e. you can add whatever you like here, it will be preserved as part of the document.
|
||||
- Excalidraw documents now show in graph view.
|
||||
- The following front matter keys will customize how the drawing is displayed - overriding general settings:
|
||||
- `excalidraw-link-prefix: "📍"` preview prefix for internal links
|
||||
- `excalidraw-url-prefix: "🌐"` preview prefix for external links
|
||||
- `excalidraw-link-brackets: true|false` whether or not to display brackets around links in preview
|
||||
- `excalidraw-default-mode: view|zen` Open this document in view mode or zen mode by defult. Default view mode is excellent for presentation slides.
|
||||
- Frontmatter tags to customize image export at a file level [519](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/519). If these keys are present they will override the default excalidraw embed and export settings.
|
||||
- `excalidraw-export-transparent: true`: true == Transparent / false == with background.
|
||||
- `excalidraw-export-dark`: true == Dark mode / false == light mode.
|
||||
- `excalidraw-export-padding`: Specify the export padding for the image
|
||||
- `excalidraw-export-pngscale`: This only affects export to PNG. Specify the export scale for the image. The typical range is between 0.5 and 5, but you can experiment with other values as well.
|
||||
|
||||
### Embed complete markdown files into your drawings
|
||||
- Drag from the desired file from the Obsidian file explorer and hold down <kbd>SHIFT</kbd> while dropping the file onto the canvas.
|
||||
- Use the command palette action: `Insert markdown file from vault`
|
||||
- Use custom woff, woff2 or TTF font to display the document, you can set the default font to use under Excalidraw Settings.
|
||||
- You can set a custom css for rendering the snapshot image of your markdown document.
|
||||
Only operating system standard fonts are supported as the font-family (
|
||||
[Win10](https://docs.microsoft.com/en-us/typography/fonts/windows_10_font_list),
|
||||
[Mac & iOS](https://developer.apple.com/fonts/system-fonts/)
|
||||
), plus you can set one additional custom font using the setting explained above.
|
||||
- (for a demonstration watch this [video](https://youtu.be/K6qZkTz8GHs) and check out this
|
||||
- [sample css](https://github.com/zsviczian/obsidian-excalidraw-plugin/discussions/281)).
|
||||
- To help with styling you can observe the SVG snapshot of the markdown document created by Excalidraw.
|
||||
- Open Obsidian Developer Console (<kbd>CTRL+Shift+i</kbd>/<kbd>CMD+OPT+i</kbd>) and
|
||||
- execute the following command: `ExcalidrawAutomate.mostRecentMarkdownSVG`
|
||||
- You can control appearance of the embedded markdown file on a file by file
|
||||
bases by adding the following front matter keys to your markdown document:
|
||||
- `excalidraw-font: Virgil|Cascadia|font_file_name.extension`
|
||||
- `excalidraw-font-color: css-color-name|#HEXcolor|any-other-html-standard-format`, you can find css color names [here](https://www.w3schools.com/colors/colors_names.asp).
|
||||
- `excalidraw-font-color: css-color-name|#HEXcolor|any-other-html-standard-format`,
|
||||
- you can find css color names [here](https://www.w3schools.com/colors/colors_names.asp).
|
||||
- `excalidraw-border-color: css-color-name|#HEXcolor|any-other-html-standard-format`
|
||||
- `excalidraw-css: "css-filename|css snippet"`
|
||||
- Switch to markdown view or use <kbd>CTRL/CMD+ALT/OPT</kbd> click on the image to edit properties of the embed: `[[filename#^blockref|WIDTHxMAXHEIGHT]]`
|
||||
- Includes full [QuickAdd](https://github.com/chhoumann/quickadd), [Templater](https://silentvoid13.github.io/Templater/) and [Dataview](https://blacksmithgu.github.io/obsidian-dataview/docs/api/intro/) support through ExcalidrawAutomate. Check out the [detailed help + examples](https://zsviczian.github.io/obsidian-excalidraw-plugin/). I also have a [YouTube ExcalidrawAutomate Playlist](https://www.youtube.com/playlist?list=PL6mqgtMZ4NP1IR4nXxSlMA4PA5E-qpyHZ) with lots of examples.
|
||||
- Since 1.5.0 you can easily execute ExcalidrawAutomate macros and assign command palette shortcuts to them, using the ScriptEngine. You will find an intro video and a growing library of ready to install scripts [here](https://github.com/zsviczian/obsidian-excalidraw-plugin/tree/master/ea-scripts).
|
||||
- Switch to markdown view or use <kbd>WIN+CTRL</kbd>/<kbd>CMD+CTRL</kbd> click on the image to edit properties of the embed:
|
||||
- `[[filename#^blockref|WIDTHxMAXHEIGHT]]`
|
||||
|
||||
### Custom Font, Custom Pen, OCR support, SVG import
|
||||
- In plugin settings you can add a custom 4th font. For more details see this [video](https://youtu.be/eKFmrSQhFA4)
|
||||
- The plugin includes OCR support using Taskbone OCR. For more details see this [video](https://youtu.be/7gu4ETx7zro)
|
||||
- You can convert SVG files into Excalidraw drawings (with some limitation). For more details see this [video](https://youtu.be/vlC1-iBvIfo)
|
||||
- You can define custom freedraw pens. See documentation [here](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Alternative%20Pens.md), [video](https://youtu.be/uZz5MgzWXiM)
|
||||
|
||||
### Script Engine
|
||||
- Since 1.5.0 you can easily execute ExcalidrawAutomate macros and assign command palette shortcuts to them, using the ScriptEngine. You will find an intro video and a growing library of ready to install scripts [here](https://github.com/zsviczian/obsidian-excalidraw-plugin/tree/master/ea-scripts).
|
||||
- You can organize scripts into groups on the Obsidian Tools Panel in Excalidraw by moving scripts and accompanying SVG icon files to folders. See demo [video](https://youtu.be/wTtaXmRJ7wg?t=16).
|
||||
|
||||
### Other
|
||||
- Left-handed mode
|
||||
- Includes full
|
||||
- [QuickAdd](https://github.com/chhoumann/quickadd),
|
||||
- [Templater](https://silentvoid13.github.io/Templater/) and
|
||||
- [Dataview](https://blacksmithgu.github.io/obsidian-dataview/docs/api/intro/) support through ExcalidrawAutomate.
|
||||
- Check out the [detailed help + examples](https://zsviczian.github.io/obsidian-excalidraw-plugin/).
|
||||
- I also have a [YouTube ExcalidrawAutomate Playlist](https://www.youtube.com/playlist?list=PL6mqgtMZ4NP1IR4nXxSlMA4PA5E-qpyHZ) with lots of examples.
|
||||
- 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.
|
||||
|
||||
# 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)
|
||||
---
|
||||
|
||||
Please head over to [GitHub](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues) to report a bug or request an enhancement.
|
||||
## Feedback, questions, ideas, problems
|
||||
|
||||
# Say Thank You
|
||||
If you are enjoying Excalidraw then please support my work and enthusiasm by buying me a coffee on [https://ko-fi/zsolt](https://ko-fi.com/zsolt).
|
||||
Join the conversation about the Excalidraw plugin on
|
||||
[forum.obsidian.md](https://forum.obsidian.md/t/excalidraw-full-featured-sketching-plugin-in-obsidian)
|
||||
|
||||
Please also help spread the word by sharing about the Obsidian Excalidraw Plugin on Twitter, Reddit, or any other social media platform you regularly use.
|
||||
Please head over to [GitHub](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues) to
|
||||
report a bug or request an enhancement.
|
||||
|
||||
---
|
||||
|
||||
## Say Thank You
|
||||
|
||||
If you are enjoying Excalidraw then please support my work and enthusiasm by buying me a coffee on
|
||||
[https://ko-fi/zsolt](https://ko-fi.com/zsolt).
|
||||
|
||||
Please also help spread the word by sharing about the Obsidian Excalidraw Plugin on Twitter, Reddit,
|
||||
or any other social media platform you regularly use.
|
||||
|
||||
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
|
||||
---
|
||||
|
||||
## Friends of Excalidraw
|
||||
If you enjoy Excalidraw, consider giving [ExcaliBrain](https://github.com/zsviczian/excalibrain) a try (also available via Obsidian Community Plugins).
|
||||
|
||||
[](https://youtu.be/gOkniMkDPyM)
|
||||
|
||||
<a href="https://youtu.be/gOkniMkDPyM" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/169708346-9e41289d-9536-43ec-8f70-2d2ad2d369d6.png" width="300"/></a>
|
||||
14
TODO.md
@@ -1,14 +0,0 @@
|
||||
[x] do not embed font into SVG when embedding Excalidraw into other Excalidraw
|
||||
[x] add ```html <SVG>...</SVG> ``` codeblock to excalidraw markdown
|
||||
[x] read pre-saved `<SVG>` when generating image preview
|
||||
[x] update code to adopt change files moving from AppState to App
|
||||
- Add "files" to legacy excalidraw export
|
||||
|
||||
[x] PNG preview
|
||||
[x] markdown embed SVG 190
|
||||
[x] markdown embed PNG
|
||||
[x] embed Excalidraw into other Excalidraw
|
||||
|
||||
|
||||
|
||||
|
||||
208
ea-scripts/Alternative Pens.md
Normal file
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
IF YOU ACCIDENTLY MODIFY THIS FILE AND IT STOPS WORKING, SIMPLY DOWNLOAD IT AGAIN FROM THE SCRIPT LIBRARY.
|
||||
|
||||

|
||||
|
||||
# How to create a new pen template
|
||||
It takes a bit of experimentation and skill to create a new pen, so be patient.
|
||||
|
||||
1. Create a folder in your Vault for your pen options. The default is `Excalidraw/Pens`.
|
||||
2. Create a new markdown file in your in the `pen folder` (e.g. `My pen`).
|
||||
3. Copy the following template to the markdown file.
|
||||
```json
|
||||
{
|
||||
"highlighter": true,
|
||||
"constantPressure": false,
|
||||
"hasOutline": true,
|
||||
"outlineWidth": 4,
|
||||
"options": PASTE_PREFECT_FREEHAND_OPTIONS_HERE
|
||||
}
|
||||
```
|
||||
4. If you don't want your pen to have an outline around your line, change `hasOutline` to `false`. You can also modify `outlineWidth` if you want a thinner or thicker outline around your line.
|
||||
5. If you want your pen to be pressure sensitive (when drawing with a mouse the pressure is simulated based on the speed of your hand) leave `constantPressure` as `false`. If you want a constant line width regardless of speed and pen pressure, change it to `true`.
|
||||
6. `highlighter` true will place the new line behind the existing strokes (i.e. like a highlighter pen). If `highlighter` is missing or it is set to `false` the new line will appear at the top of the existing strokes (the default behavior of Excalidraw pens).
|
||||
7. Go to https://perfect-freehand-example.vercel.app/ and configure your pen.
|
||||
8. Click `Copy Options`.
|
||||
9. Go back to the pen file you created in step No.2 and replace the placeholder text with the options you just copied from perfect-freehand.
|
||||
10. Look for `easing` in the file and replace the function e.g. `(t) => t*t,` with the name of the function in brackets (in this example it would be `easeInQuad`). You will find the function name on the perfect-freehand website, only change the first letter to be lower case.
|
||||
11. Test your pen in Excalidraw by clicking the `Alternative Pens` script and selecting your new pen.
|
||||
|
||||
# Example pens
|
||||
My pens: https://github.com/zsviczian/obsidian-excalidraw-plugin/tree/master/ea-scripts/pens
|
||||
|
||||
**Fine tipped pen:**
|
||||
```json
|
||||
{
|
||||
constantPressure: true,
|
||||
options: {
|
||||
smoothing: 0.4,
|
||||
thinning: -0.5,
|
||||
streamline: 0.4,
|
||||
easing: "linear",
|
||||
start: {
|
||||
taper: 5,
|
||||
cap: false,
|
||||
},
|
||||
end: {
|
||||
taper: 5,
|
||||
cap: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Thick marker:**
|
||||
```json
|
||||
{
|
||||
constantPressure: true,
|
||||
hasOutline: true,
|
||||
outlineWidth: 4,
|
||||
options: {
|
||||
thinning: 1,
|
||||
smoothing: 0.5,
|
||||
streamline: 0.5,
|
||||
easing: "linear",
|
||||
start: {
|
||||
taper: 0,
|
||||
cap: true
|
||||
},
|
||||
end: {
|
||||
taper: 0,
|
||||
cap: true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Fountain pen:**
|
||||
```json
|
||||
{
|
||||
options: {
|
||||
smoothing: 0.22,
|
||||
thinning: 0.8,
|
||||
streamline: 0.22,
|
||||
easing: "easeInQuad",
|
||||
start: {
|
||||
taper: true,
|
||||
cap: true,
|
||||
},
|
||||
end: {
|
||||
taper: 1,
|
||||
cap: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
# Notes about the pen options
|
||||
|
||||
Note, that custom pens are currently not supported by Excalidraw.com. I've submitted a [PR](https://github.com/excalidraw/excalidraw/pull/6069) but there is no guarantee that it will get pushed to production. Your Excalidraw drawing can still be loaded to Excalidraw, but the special pen effects will not be visible there.
|
||||
|
||||
If you set a pen in your Excalidraw template file, that pen will be loaded automatically when you create a file using that template. Similarly, when you save a document, it will save your current pen settings as well. The next time you open the document, you can continue to use the same pen.
|
||||
|
||||
Pen options are saved with the stroke. This means, that even if you change the ped definition later on, your existing drawings will not be effected.
|
||||
|
||||
`outlineWidth` is relative to `strokeWidth`. i.e. if you make the stroke thinner in Excalidraw, the outline will become proportionally thinner as well. `outlineWidth` is only used if `hasOutline` is set to true.
|
||||
|
||||
If you don't want your pen to be pressure/speed sensitive, set `constantPressure` to `true`. Setting `constantPressure` to `true` automatically sets `simulatePressure` to `false`.
|
||||
|
||||
If you want your pen to be speed sensitive (i.e. the faster you draw the line the thinner it gets), set `options.simulatePressure` to `true`. If you omit `simulatePressure` from `options` then excalidraw will detect if you are drawing with a mouse or a pen and use pen pressures if available.
|
||||
|
||||
You can read more about configuring perfect freehand here: https://github.com/steveruizok/perfect-freehand#documentation
|
||||
|
||||
Excalidraw supports all of the easing functions listed here: https://easings.net/#, plus "linear". You can also find details about these easing functions here:
|
||||
https://github.com/ai/easings.net/blob/master/src/easings/easingsFunctions.ts
|
||||
|
||||
From a performance perspective I recommend linear easing.
|
||||
|
||||
# The script
|
||||
|
||||
```javascript */
|
||||
|
||||
//--------------------------
|
||||
// Load settings
|
||||
//--------------------------
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.8.8")) {
|
||||
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
|
||||
return;
|
||||
}
|
||||
|
||||
const api = ea.getExcalidrawAPI();
|
||||
let settings = ea.getScriptSettings();
|
||||
//set default values on first run
|
||||
if(!settings["Pen folder"]) {
|
||||
settings = {
|
||||
"Pen folder" : {
|
||||
value: "Excalidraw/Pens",
|
||||
description: "The path to the folder where you store the perfect freehand options"
|
||||
}
|
||||
};
|
||||
ea.setScriptSettings(settings);
|
||||
}
|
||||
|
||||
let penFolder = settings["Pen folder"].value.toLowerCase();
|
||||
if(penFolder === "" || penFolder === "/") {
|
||||
new Notice("The pen folder cannot be the root folder of your vault");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!penFolder.endsWith("/")) penFolder += "/";
|
||||
|
||||
|
||||
//--------------------------
|
||||
// Select pen
|
||||
//--------------------------
|
||||
const pens = app.vault.getFiles()
|
||||
.filter(f=>f.extension === "md" && f.path.toLowerCase() === penFolder + f.name.toLowerCase())
|
||||
.sort((a,b)=>a.basename.toLowerCase()<b.basename.toLowerCase()?-1:1);
|
||||
if(pens.length === 0) {
|
||||
const notice = new Notice(`You don't seem to have any pen definition files. Click this message to open the how-to guide.`,4000);
|
||||
notice.noticeEl.onclick = async () => app.workspace.openLinkText(utils.scriptFile.path,"","tab");
|
||||
return;
|
||||
}
|
||||
const file = await utils.suggester(["Excalidraw Default"].concat(pens.map(f=>(f.name.slice(0,f.name.length-3)))),["Default"].concat(pens), "Choose a pen preset, press ESC to abort");
|
||||
if(!file) return;
|
||||
|
||||
if(file === "Default") {
|
||||
api.updateScene({
|
||||
appState: {
|
||||
currentStrokeOptions: undefined
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//--------------------------
|
||||
// Load pen
|
||||
//--------------------------
|
||||
const pen = await app.vault.read(file);
|
||||
|
||||
const parseJSON = (data) => {
|
||||
try {
|
||||
return JSON.parse(data);
|
||||
} catch(e) {
|
||||
try {
|
||||
return JSON.parse(data.replaceAll(/\s(\w*)\:\s/g,' "$1": ').replaceAll(/,([^\w]*?})/gm,"$1"));
|
||||
} catch(ee) {
|
||||
const notice = new Notice(`Error loading the pen file. Maybe you accidently copy/pasted the easing function from perfect freehand website? Check the error message in Developer Console.\n(click=dismiss, right-click=Info) `,5000);
|
||||
notice.noticeEl.oncontextmenu = async () => app.workspace.openLinkText(utils.scriptFile.path,"","tab");
|
||||
console.error(ee);
|
||||
console.error(data.replaceAll(/\s(\w*)\:\s/g,' "$1": ').replaceAll(/,([^\w]*?})/gm,"$1"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
penJSON = parseJSON(pen);
|
||||
|
||||
|
||||
if(!penJSON || typeof penJSON !== 'object') return;
|
||||
|
||||
//--------------------------
|
||||
// Apply pen
|
||||
//--------------------------
|
||||
await api.updateScene({
|
||||
appState: {
|
||||
currentStrokeOptions: penJSON
|
||||
}
|
||||
});
|
||||
api.setActiveTool({type:"freedraw"});
|
||||
1
ea-scripts/Alternative Pens.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M373.5 27.1C388.5 9.9 410.2 0 433 0c43.6 0 79 35.4 79 79c0 22.8-9.9 44.6-27.1 59.6L277.7 319l-10.3-10.3-64-64L193 234.3 373.5 27.1zM170.3 256.9l10.4 10.4 64 64 10.4 10.4-19.2 83.4c-3.9 17.1-16.9 30.7-33.8 35.4L24.4 510.3l95.4-95.4c2.6 .7 5.4 1.1 8.3 1.1c17.7 0 32-14.3 32-32s-14.3-32-32-32s-32 14.3-32 32c0 2.9 .4 5.6 1.1 8.3L1.7 487.6 51.5 310c4.7-16.9 18.3-29.9 35.4-33.8l83.4-19.2z"/></svg>
|
||||
|
After Width: | Height: | Size: 632 B |
@@ -1,120 +1,120 @@
|
||||
/*
|
||||

|
||||
|
||||
THIS SCRIPT REQUIRES EXCALIDRAW 1.5.15
|
||||
|
||||
The script will
|
||||
1) send the selected image file to [taskbone.com](https://taskbone.com) to exctract the text from the image, and
|
||||
2) will add the text to your drawing as a text element
|
||||
|
||||
I recommend also installing the [Transfer TextElements to Excalidraw markdown metadata](Transfer%20TextElements%20to%20Excalidraw%20markdown%20metadata.md) script as well.
|
||||
|
||||
The script is based on [@schlundd](https://github.com/schlundd)'s [Obsidian-OCR-Plugin](https://github.com/schlundd/obsidian-ocr-plugin)
|
||||
|
||||
See ScriptEngine documentation for more details:
|
||||
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
|
||||
|
||||
```javascript
|
||||
*/
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.24")) {
|
||||
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
|
||||
return;
|
||||
}
|
||||
|
||||
let token = ea.getScriptSettings().token?.value??ea.getScriptSettings().token;
|
||||
const BASE_URL = "https://ocr.taskbone.com";
|
||||
|
||||
//convert setting to 1.5.21 format
|
||||
if(token && !ea.getScriptSettings().token.value) {
|
||||
ea.setScriptSettings({token: {value: token, hidden: true}});
|
||||
}
|
||||
|
||||
//get new token if token was not provided
|
||||
if (!token) {
|
||||
const tokenResponse = await fetch(
|
||||
BASE_URL + "/get-new-token", {
|
||||
method: 'post'
|
||||
});
|
||||
if (tokenResponse.status === 200) {
|
||||
jsonResponse = await tokenResponse.json();
|
||||
token = jsonResponse.token;
|
||||
ea.setScriptSettings({token: {value: token, hidden: true}});
|
||||
} else {
|
||||
notice(`Taskbone OCR Error: ${tokenResponse.status}\nPlease try again later.`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//get image element
|
||||
//if multiple image elements were selected prompt user to choose
|
||||
const imageElements = ea.getViewSelectedElements().filter((el)=>el.type==="image");
|
||||
|
||||
//need to save the view to ensure recently pasted images are saved as files
|
||||
await ea.targetView.save();
|
||||
|
||||
let selectedImageElement = null;
|
||||
switch (imageElements.length) {
|
||||
case 0:
|
||||
return;
|
||||
case 1:
|
||||
selectedImageElement = imageElements[0];
|
||||
break;
|
||||
default:
|
||||
const files = imageElements.map((el)=>ea.getViewFileForImageElement(el));
|
||||
selectedImageElement = await utils.suggester(files.map((f)=>f.name),imageElements);
|
||||
break;
|
||||
}
|
||||
|
||||
if(!selectedImageElement) {
|
||||
notice("No image element was selected");
|
||||
return;
|
||||
}
|
||||
const imageFile = ea.getViewFileForImageElement(selectedImageElement);
|
||||
if(!imageFile) {
|
||||
notice("Can read image file");
|
||||
return;
|
||||
}
|
||||
|
||||
//Execute the OCR
|
||||
let text = null;
|
||||
const fileBuffer = await app.vault.readBinary(imageFile);
|
||||
const formData = new FormData();
|
||||
formData.append("image", new Blob([fileBuffer]))
|
||||
try {
|
||||
const response = await fetch(
|
||||
BASE_URL + "/get-text", {
|
||||
headers: {
|
||||
Authorization: "Bearer " + token
|
||||
},
|
||||
method: "post",
|
||||
body: formData
|
||||
});
|
||||
if (response.status == 200) {
|
||||
jsonResponse = await response.json();
|
||||
text = jsonResponse?.text;
|
||||
} else {
|
||||
notice(`Could not read Text from ${file.path}:\n Error: ${response.status}`);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
notice(`The OCR service seems unavailable right now. Please try again later.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!text) {
|
||||
notice("No text found");
|
||||
return;
|
||||
}
|
||||
console.log({text});
|
||||
|
||||
//add text element to drawing
|
||||
const id = ea.addText(selectedImageElement.x,selectedImageElement.y+selectedImageElement.height,text);
|
||||
await ea.addElementsToView();
|
||||
ea.selectElementsInView([ea.getElement(id)]);
|
||||
ea.getExcalidrawAPI().zoomToFit(ea.getViewSelectedElements(),1);
|
||||
|
||||
//utility function
|
||||
function notice(message) {
|
||||
new Notice(message,10000);
|
||||
console.log(message);
|
||||
}
|
||||
/*
|
||||

|
||||
|
||||
THIS SCRIPT REQUIRES EXCALIDRAW 1.5.15
|
||||
|
||||
The script will
|
||||
1) send the selected image file to [taskbone.com](https://taskbone.com) to exctract the text from the image, and
|
||||
2) will add the text to your drawing as a text element
|
||||
|
||||
I recommend also installing the [Transfer TextElements to Excalidraw markdown metadata](Transfer%20TextElements%20to%20Excalidraw%20markdown%20metadata.md) script as well.
|
||||
|
||||
The script is based on [@schlundd](https://github.com/schlundd)'s [Obsidian-OCR-Plugin](https://github.com/schlundd/obsidian-ocr-plugin)
|
||||
|
||||
See ScriptEngine documentation for more details:
|
||||
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
|
||||
|
||||
```javascript
|
||||
*/
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.24")) {
|
||||
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
|
||||
return;
|
||||
}
|
||||
|
||||
let token = ea.getScriptSettings().token?.value??ea.getScriptSettings().token;
|
||||
const BASE_URL = "https://ocr.taskbone.com";
|
||||
|
||||
//convert setting to 1.5.21 format
|
||||
if(token && !ea.getScriptSettings().token.value) {
|
||||
ea.setScriptSettings({token: {value: token, hidden: true}});
|
||||
}
|
||||
|
||||
//get new token if token was not provided
|
||||
if (!token) {
|
||||
const tokenResponse = await fetch(
|
||||
BASE_URL + "/get-new-token", {
|
||||
method: 'post'
|
||||
});
|
||||
if (tokenResponse.status === 200) {
|
||||
jsonResponse = await tokenResponse.json();
|
||||
token = jsonResponse.token;
|
||||
ea.setScriptSettings({token: {value: token, hidden: true}});
|
||||
} else {
|
||||
notice(`Taskbone OCR Error: ${tokenResponse.status}\nPlease try again later.`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//get image element
|
||||
//if multiple image elements were selected prompt user to choose
|
||||
const imageElements = ea.getViewSelectedElements().filter((el)=>el.type==="image");
|
||||
|
||||
//need to save the view to ensure recently pasted images are saved as files
|
||||
await ea.targetView.save();
|
||||
|
||||
let selectedImageElement = null;
|
||||
switch (imageElements.length) {
|
||||
case 0:
|
||||
return;
|
||||
case 1:
|
||||
selectedImageElement = imageElements[0];
|
||||
break;
|
||||
default:
|
||||
const files = imageElements.map((el)=>ea.getViewFileForImageElement(el));
|
||||
selectedImageElement = await utils.suggester(files.map((f)=>f.name),imageElements);
|
||||
break;
|
||||
}
|
||||
|
||||
if(!selectedImageElement) {
|
||||
notice("No image element was selected");
|
||||
return;
|
||||
}
|
||||
const imageFile = ea.getViewFileForImageElement(selectedImageElement);
|
||||
if(!imageFile) {
|
||||
notice("Can read image file");
|
||||
return;
|
||||
}
|
||||
|
||||
//Execute the OCR
|
||||
let text = null;
|
||||
const fileBuffer = await app.vault.readBinary(imageFile);
|
||||
const formData = new FormData();
|
||||
formData.append("image", new Blob([fileBuffer]))
|
||||
try {
|
||||
const response = await fetch(
|
||||
BASE_URL + "/get-text", {
|
||||
headers: {
|
||||
Authorization: "Bearer " + token
|
||||
},
|
||||
method: "post",
|
||||
body: formData
|
||||
});
|
||||
if (response.status == 200) {
|
||||
jsonResponse = await response.json();
|
||||
text = jsonResponse?.text;
|
||||
} else {
|
||||
notice(`Could not read Text from ${file.path}:\n Error: ${response.status}`);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
notice(`The OCR service seems unavailable right now. Please try again later.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!text) {
|
||||
notice("No text found");
|
||||
return;
|
||||
}
|
||||
console.log({text});
|
||||
|
||||
//add text element to drawing
|
||||
const id = ea.addText(selectedImageElement.x,selectedImageElement.y+selectedImageElement.height,text);
|
||||
await ea.addElementsToView();
|
||||
ea.selectElementsInView([ea.getElement(id)]);
|
||||
ea.getExcalidrawAPI().zoomToFit(ea.getViewSelectedElements(),1);
|
||||
|
||||
//utility function
|
||||
function notice(message) {
|
||||
new Notice(message,10000);
|
||||
console.log(message);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 605 B After Width: | Height: | Size: 605 B |
@@ -1,44 +1,44 @@
|
||||
/*
|
||||

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

|
||||
|
||||
The script will delete the selected text elements from the canvas and will copy the text from these text elements into the Excalidraw markdown file as metadata. This means, that the text will no longer be visible in the drawing, however you will be able to search for the text in Obsidian and find the drawing containing this image.
|
||||
|
||||
See ScriptEngine documentation for more details:
|
||||
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
|
||||
|
||||
```javascript
|
||||
*/
|
||||
//get text elements
|
||||
|
||||
const textElements = ea.getViewSelectedElements().filter((el)=>el.type==="text");
|
||||
|
||||
if(textElements.length===0) {
|
||||
notice("No text elements were selected")
|
||||
return;
|
||||
}
|
||||
|
||||
metadata = "# Metadata\n" + textElements
|
||||
.map((el)=>el.rawText.replaceAll(/%|\^/g,"_")) //cleaning these characters for safety, might not be needed
|
||||
.join("/n") + "\n";
|
||||
|
||||
ea.deleteViewElements(textElements);
|
||||
await ea.targetView.save();
|
||||
data = await app.vault.read(ea.targetView.file);
|
||||
splitAfterFrontmatter = data.split(/(^---[\w\W]*?---\n)/);
|
||||
if(splitAfterFrontmatter.length !== 3) {
|
||||
notice("Error locating frontmatter in markdown file");
|
||||
console.log({file:ea.targetView.file});
|
||||
return;
|
||||
}
|
||||
newData = splitAfterFrontmatter[1]+metadata+splitAfterFrontmatter[2]
|
||||
await app.vault.modify(ea.targetView.file,newData);
|
||||
|
||||
//utility function
|
||||
function notice(message) {
|
||||
new Notice(message);
|
||||
console.log(message);
|
||||
}
|
||||
/*
|
||||

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

|
||||
|
||||
The script will delete the selected text elements from the canvas and will copy the text from these text elements into the Excalidraw markdown file as metadata. This means, that the text will no longer be visible in the drawing, however you will be able to search for the text in Obsidian and find the drawing containing this image.
|
||||
|
||||
See ScriptEngine documentation for more details:
|
||||
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
|
||||
|
||||
```javascript
|
||||
*/
|
||||
//get text elements
|
||||
|
||||
const textElements = ea.getViewSelectedElements().filter((el)=>el.type==="text");
|
||||
|
||||
if(textElements.length===0) {
|
||||
notice("No text elements were selected")
|
||||
return;
|
||||
}
|
||||
|
||||
metadata = "# Metadata\n" + textElements
|
||||
.map((el)=>el.rawText.replaceAll(/%|\^/g,"_")) //cleaning these characters for safety, might not be needed
|
||||
.join("/n") + "\n";
|
||||
|
||||
ea.deleteViewElements(textElements);
|
||||
await ea.targetView.save();
|
||||
data = await app.vault.read(ea.targetView.file);
|
||||
splitAfterFrontmatter = data.split(/(^---[\w\W]*?---\n)/);
|
||||
if(splitAfterFrontmatter.length !== 3) {
|
||||
notice("Error locating frontmatter in markdown file");
|
||||
console.log({file:ea.targetView.file});
|
||||
return;
|
||||
}
|
||||
newData = splitAfterFrontmatter[1]+metadata+splitAfterFrontmatter[2]
|
||||
await app.vault.modify(ea.targetView.file,newData);
|
||||
|
||||
//utility function
|
||||
function notice(message) {
|
||||
new Notice(message);
|
||||
console.log(message);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -1,348 +1,348 @@
|
||||
If you are enjoying the Excalidraw plugin then please support my work and enthusiasm by buying me a coffee on [https://ko-fi/zsolt](https://ko-fi.com/zsolt).
|
||||
|
||||
[<img src="https://user-images.githubusercontent.com/14358394/115450238-f39e8100-a21b-11eb-89d0-fa4b82cdbce8.png" class="coffee">](https://ko-fi.com/zsolt)
|
||||
|
||||
---
|
||||
|
||||
Jump ahead to the [[#List of available scripts]]
|
||||
|
||||
# Introducing Excalidraw Automate Script Engine
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/hePJcObHIso" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
|
||||
Script Engine scripts are installed in the `Downloaded` subfolder of the `Excalidraw Automate script folder` specified in plugin settings.
|
||||
|
||||
In the `Command Palette` installed scripts are prefixed with `Downloaded/`, thus you can always know if you are executing a local script of your own, or one that you have downloaded from GitHub.
|
||||
|
||||
## Attention developers and hobby hackers
|
||||
<img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/hobby-programmer.svg' align='left' style='background-color:whitesmoke; width:80px; margin-right:15px; margin-bottom:10px;'/>
|
||||
If you want to modify scripts, I recommend moving them to the `Excalidraw Automate script folder` or a different subfolder under the script folder. Scripts in the `Downloaded` folder will be overwritten when you click the `Update this script` button. Note also, that at this time, I do not check if the script file has been updated on GitHub, thus the `Update this script` button is always visible once you have installed a script, not only when an update is availble (hope to build this feature in the future).
|
||||
|
||||
I would love to include your contribution in the script library. If you have a script of your own that you would like to share with the community, please open a [PR](https://github.com/zsviczian/obsidian-excalidraw-plugin/pulls) on GitHub. Be sure to include the following in your pull request
|
||||
- The [script file](https://github.com/zsviczian/obsidian-excalidraw-plugin/tree/master/ea-scripts) with a self explanetory name. The name of the file will be the name of the script in the Command Palette.
|
||||
- An [image](https://github.com/zsviczian/obsidian-excalidraw-plugin/tree/master/images) explaining the scripts purpose. Remember a picture speaks thousand words!
|
||||
- An update to this file [ea-scripts/index.md](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/index.md)
|
||||
|
||||
---
|
||||
|
||||
# List of available scripts
|
||||
- [[#Add Connector Point]]
|
||||
- [[#Add Link to Existing File and Open]]
|
||||
- [[#Add Link to New Page and Open]]
|
||||
- [[#Add Next Step in Process]]
|
||||
- [[#Box Each Selected Groups]]
|
||||
- [[#Box Selected Elements]]
|
||||
- [[#Change shape of selected elements]]
|
||||
- [[#Connect elements]]
|
||||
- [[#Convert freedraw to line]]
|
||||
- [[#Convert selected text elements to sticky notes]]
|
||||
- [[#Convert text to link with folder and alias]]
|
||||
- [[#Copy Selected Element Styles to Global]]
|
||||
- [[#Create new markdown file and embed into active drawing]]
|
||||
- [[#Darken background color]]
|
||||
- [[#Elbow connectors]]
|
||||
- [[#Expand rectangles horizontally keep text centered]]
|
||||
- [[#Expand rectangles horizontally]]
|
||||
- [[#Expand rectangles vertically keep text centered]]
|
||||
- [[#Expand rectangles vertically]]
|
||||
- [[#Fixed horizontal distance between centers]]
|
||||
- [[#Fixed inner distance]]
|
||||
- [[#Fixed spacing]]
|
||||
- [[#Fixed vertical distance between centers]]
|
||||
- [[#Fixed vertical distance]]
|
||||
- [[#Lighten background color]]
|
||||
- [[Mindmap connector]]
|
||||
- [[#Modify background color opacity]]
|
||||
- [[#Normalize Selected Arrows]]
|
||||
- [[#OCR - Optical Character Recognition]]
|
||||
- [[#Organic Line]]
|
||||
- [[#Repeat Elements]]
|
||||
- [[#Reverse arrows]]
|
||||
- [[#Scribble Helper]]
|
||||
- [[#Select Elements of Type]]
|
||||
- [[#Set background color of unclosed line object by adding a shadow clone]]
|
||||
- [[#Set Dimensions]]
|
||||
- [[#Set Font Family]]
|
||||
- [[#Set Grid]]
|
||||
- [[#Set Link Alias]]
|
||||
- [[#Set Stroke Width of Selected Elements]]
|
||||
- [[#Set Text Alignment]]
|
||||
- [[#Split text by lines]]
|
||||
- [[#Toggle Fullscreen on Mobile]]
|
||||
- [[#Transfer TextElements to Excalidraw markdown metadata]]
|
||||
- [[#Zoom to Fit Selected Elements]]
|
||||
|
||||
## Add Connector Point
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Connector%20Point.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Add%20Connector%20Point.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will add a small circle to the top left of each text element in the selection and add the text and the "connector point" to a group. You can use the connector points to link text elements with an arrow (in for example a Wardley Map).<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-bullet-point.jpg'></td></tr></table>
|
||||
|
||||
## Add Link to Existing File and Open
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Link%20to%20Existing%20File%20and%20Open.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Add%20Link%20to%20Existing%20File%20and%20Open.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Prompts for a file from the vault. Adds a link to the selected element pointing to the selected file. You can control in settings to open the file in the current active pane or an adjacent pane.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-link-and-open.jpg'></td></tr></table>
|
||||
|
||||
## Add Link to New Page and Open
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Link%20to%20New%20Page%20and%20Open.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Add%20Link%20to%20New%20Page%20and%20Open.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Prompts for filename. Offers option to create and open a new Markdown or Excalidraw document. Adds link pointing to the new file, to the selected objects in the drawing. You can control in settings to open the file in the current active pane or an adjacent pane.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-link-to-new-page-and-pen.jpg'></td></tr></table>
|
||||
|
||||
## Add Next Step in Process
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Next%20Step%20in%20Process.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Add%20Next%20Step%20in%20Process.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will prompt you for the title of the process step, then will create a stick note with the text. If an element is selected then the script will connect this new step with an arrow to the previous step (the selected element). If no element is selected, then the script assumes this is the first step in the process and will only output the sticky note with the text that was entered.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-process-step.jpg'></td></tr></table>
|
||||
|
||||
## Box Each Selected Groups
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Box%20Each%20Selected%20Groups.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Box%20Each%20Selected%20Groups.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will add encapsulating boxes around each of the currently selected groups in Excalidraw.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-box-each-selected-groups.png'></td></tr></table>
|
||||
|
||||
## Box Selected Elements
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Box%20Selected%20Elements.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Box%20Selected%20Elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will add an encapsulating box around the currently selected elements in Excalidraw.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-box-elements.jpg'></td></tr></table>
|
||||
|
||||
## Change shape of selected elements
|
||||
```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>
|
||||
|
||||
## Connect elements
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Connect%20elements.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Connect%20elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will connect two objects with an arrow. If either of the objects are a set of grouped elements (e.g. a text element grouped with an encapsulating rectangle), the script will identify these groups, and connect the arrow to the largest object in the group (assuming you want to connect the arrow to the box around the text element).<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-connect-elements.jpg'></td></tr></table>
|
||||
|
||||
## Convert freedraw to line
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Convert%20freedraw%20to%20line.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Convert%20freedraw%20to%20line.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Convert selected freedraw objects into editable lines. This will allow you to adjust your drawings by dragging line points and will also allow you to select shape fill in case of enclosed lines. You can adjust conversion point density in settings.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-convert-freedraw-to-line.jpg'></td></tr></table>
|
||||
|
||||
## Convert selected text elements to sticky notes
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Convert%20selected%20text%20elements%20to%20sticky%20notes.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Convert%20selected%20text%20elements%20to%20sticky%20notes.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Converts selected plain text elements to sticky notes with transparent background and transparent stroke color (default setting, can be changed in plugin settings). Essentially converts text element into a wrappable format.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-textelement-to-transparent-stickynote.png'></td></tr></table>
|
||||
|
||||
## Convert text to link with folder and alias
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Convert%20text%20to%20link%20with%20folder%20and%20alias.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Convert%20text%20to%20link%20with%20folder%20and%20alias.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Converts text elements to links pointing to a file in a selected folder and with the alias set as the original text. The script will prompt the user to select an existing folder from the vault.<br><code>original text</code> - <code>[[selected folder/original text|original text]]</code></td></tr></table>
|
||||
|
||||
## Copy Selected Element Styles to Global
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Copy%20Selected%20Element%20Styles%20to%20Global.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Copy%20Selected%20Element%20Styles%20to%20Global.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will copy styles of any selected element into Excalidraw's global styles.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-copy-selected-element-styles-to-global.png'></td></tr></table>
|
||||
|
||||
## Create new markdown file and embed into active drawing
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Create%20new%20markdown%20file%20and%20embed%20into%20active%20drawing.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Create%20new%20markdown%20file%20and%20embed%20into%20active%20drawing.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script will prompt you for a filename, then create a new markdown document with the file name provided, open the new markdown document in an adjacent pane, and embed the markdown document into the active Excalidraw drawing.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-create-and-embed-new-markdown-file.jpg'></td></tr></table>
|
||||
|
||||
## Darken background color
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Darken%20background%20color.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Darken%20background%20color.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script darkens the background color of the selected element by 2% at a time. You can use this script several times until you are satisfied. It is recommended to set a shortcut key for this script so that you can quickly try to DARKEN and LIGHTEN the color effect. In contrast to the `Modify background color opacity` script, the advantage is that the background color of the element is not affected by the canvas color, and the color value does not appear in a strange rgba() form.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/darken-lighten-background-color.png'></td></tr></table>
|
||||
|
||||
## Elbow connectors
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Elbow%20connectors.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Elbow%20connectors.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script converts the selected connectors to elbows.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/elbow-connectors.png'></td></tr></table>
|
||||
|
||||
## Expand rectangles horizontally keep text centered
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Expand%20rectangles%20horizontally%20keep%20text%20centered.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Expand%20rectangles%20horizontally%20keep%20text%20centered.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script expands the width of the selected rectangles until they are all the same width and keep the text centered.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-expand-rectangles.gif'></td></tr></table>
|
||||
|
||||
## Expand rectangles horizontally
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Expand%20rectangles%20horizontally.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Expand%20rectangles%20horizontally.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script expands the width of the selected rectangles until they are all the same width.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-expand-rectangles.gif'></td></tr></table>
|
||||
|
||||
## Expand rectangles vertically keep text centered
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Expand%20rectangles%20vertically%20keep%20text%20centered.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Expand%20rectangles%20vertically%20keep%20text%20centered.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script expands the height of the selected rectangles until they are all the same height and keep the text centered.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-expand-rectangles.gif'></td></tr></table>
|
||||
|
||||
## Expand rectangles vertically
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Expand%20rectangles%20vertically.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Expand%20rectangles%20vertically.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script expands the height of the selected rectangles until they are all the same height.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-expand-rectangles.gif'></td></tr></table>
|
||||
|
||||
## Fixed horizontal distance between centers
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20horizontal%20distance%20between%20centers.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Fixed%20horizontal%20distance%20between%20centers.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script arranges the selected elements horizontally with a fixed center spacing.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fixed-horizontal-distance-between-centers.png'></td></tr></table>
|
||||
|
||||
## Fixed inner distance
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20inner%20distance.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Fixed%20inner%20distance.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script arranges selected elements and groups with a fixed inner distance.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fixed-inner-distance.png'></td></tr></table>
|
||||
|
||||
## Fixed spacing
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20spacing.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Fixed%20spacing.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script arranges the selected elements horizontally with a fixed spacing. When we create an architecture diagram or mind map, we often need to arrange a large number of elements in a fixed spacing. `Fixed spacing` and `Fixed vertical Distance` scripts can save us a lot of time.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fix-space-demo.png'></td></tr></table>
|
||||
|
||||
## Fixed vertical distance between centers
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20vertical%20distance%20between%20centers.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Fixed%20vertical%20distance%20between%20centers.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script arranges the selected elements vertically with a fixed center spacing.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fixed-vertical-distance-between-centers.png'></td></tr></table>
|
||||
|
||||
## Fixed vertical distance
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20vertical%20distance.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Fixed%20vertical%20distance.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script arranges the selected elements vertically with a fixed spacing. When we create an architecture diagram or mind map, we often need to arrange a large number of elements in a fixed spacing. `Fixed spacing` and `Fixed vertical Distance` scripts can save us a lot of time.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fixed-vertical-distance.png'></td></tr></table>
|
||||
|
||||
## Lighten background color
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Lighten%20background%20color.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Lighten%20background%20color.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script lightens the background color of the selected element by 2% at a time. You can use this script several times until you are satisfied. It is recommended to set a shortcut key for this script so that you can quickly try to DARKEN and LIGHTEN the color effect.In contrast to the `Modify background color opacity` script, the advantage is that the background color of the element is not affected by the canvas color, and the color value does not appear in a strange rgba() form.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/darken-lighten-background-color.png'></td></tr></table>
|
||||
|
||||
## Mindmap connector
|
||||
```excalidraw-script-install
|
||||
https://github.com/xllowl/obsidian-excalidraw-plugin/blob/master/ea-scripts/Mindmap%20connector.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/xllowl'>@xllowl</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/xllowl/obsidian-excalidraw-plugin/blob/master/ea-scripts/Mindmap%20connector.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script creates mindmap like lines(only right side available). The line will starts according to the creation time of the elements. So you may need to create the header element first.<br><img src='https://github.com/xllowl/obsidian-excalidraw-plugin/blob/master/images/mindmap%20connector.png'></td></tr></table>
|
||||
|
||||
## Modify background color opacity
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Modify%20background%20color%20opacity.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Modify%20background%20color%20opacity.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script changes the opacity of the background color of the selected boxes. The default background color in Excalidraw is so dark that the text is hard to read. You can lighten the color a bit by setting transparency. And you can tweak the transparency over and over again until you're happy with it. Although excalidraw has the opacity option in its native property Settings, it also changes the transparency of the border. Use this script to change only the opacity of the background color without affecting the border.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-modify-background-color-opacity.png'></td></tr></table>
|
||||
|
||||
## Normalize Selected Arrows
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Normalize%20Selected%20Arrows.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Normalize%20Selected%20Arrows.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will reset the start and end positions of the selected arrows. The arrow will point to the center of the connected box and will have a gap of 8px from the box.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-normalize-selected-arrows.png'></td></tr></table>
|
||||
|
||||
## OCR - Optical Character Recognition
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/OCR%20-%20Optical%20Character%20Recognition.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/OCR%20-%20Optical%20Character%20Recognition.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">REQUIRES EXCALIDRAW 1.5.15<br>The script will 1) send the selected image file to [taskbone.com](https://taskbone.com) to exctract the text from the image, and 2) will add the text to your drawing as a text element.<br><mark>⚠ Note that you will need to manually paste your token into the script after the first run! ⚠</mark><br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-ocr.jpg'><br><iframe width="560" height="315" src="https://www.youtube.com/embed/W2NMzR8s4eE" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></td></tr></table>
|
||||
|
||||
## Organic Line
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Organic%20Line.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Organic%20Line.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Converts selected freedraw lines such that pencil pressure will decrease from maximum to minimum from the beginning of the line to its end. The resulting line is placed at the back of the layers, under all other items. Helpful when drawing organic mindmaps.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-organic-line.jpg'></td></tr></table>
|
||||
|
||||
## Repeat Elements
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Repeat%20Elements.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Repeat%20Elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will detect the difference between 2 selected elements, including position, size, angle, stroke and background color, and create several elements that repeat these differences based on the number of repetitions entered by the user.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-repeat-elements.png'></td></tr></table>
|
||||
|
||||
## Reverse arrows
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Reverse%20arrows.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Reverse%20arrows.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Reverse the direction of **arrows** within the scope of selected elements.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-reverse-arrow.jpg'></td></tr></table>
|
||||
|
||||
## Scribble Helper
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Scribble%20Helper.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Scribble%20Helper.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">iOS scribble helper for better handwriting experience with text elements. If no elements are selected then the creates a text element at pointer position and you can use the edit box to modify the text with scribble. If a text element is selected then opens the input prompt where you can modify this text with scribble.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-scribble-helper.jpg'></td></tr></table>
|
||||
|
||||
## Select Elements of Type
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Select%20Elements%20of%20Type.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Select%20Elements%20of%20Type.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Prompts you with a list of the different element types in the active image. Only elements of the selected type will be selected on the canvas. If nothing is selected when running the script, then the script will process all the elements on the canvas. If some elements are selected when the script is executed, then the script will only process the selected elements.<br>The script is useful when, for example, you want to bring to front all the arrows, or want to change the color of all the text elements, etc.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-select-element-of-type.jpg'></td></tr></table>
|
||||
|
||||
## Set background color of unclosed line object by adding a shadow clone
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20background%20color%20of%20unclosed%20line%20object%20by%20adding%20a%20shadow%20clone.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20background%20color%20of%20unclosed%20line%20object%20by%20adding%20a%20shadow%20clone.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Use this script to set the background color of unclosed (i.e. open) line objects by creating a clone of the object. The script will set the stroke color of the clone to transparent and will add a straight line to close the object. Use settings to define the default background color, the fill style, and the strokeWidth of the clone. By default the clone will be grouped with the original object, you can disable this also in settings.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-set-background-color-of-unclosed-line.jpg'></td></tr></table>
|
||||
|
||||
## Set Dimensions
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Dimensions.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20Dimensions.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Currently there is no way to specify the exact location and size of objects in Excalidraw. You can bridge this gap with the following simple script.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-dimensions.jpg'></td></tr></table>
|
||||
|
||||
## Set Font Family
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Font%20Family.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20Font%20Family.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Sets font family of the text block (Virgil, Helvetica, Cascadia). Useful if you want to set a keyboard shortcut for selecting font family.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-font-family.jpg'></td></tr></table>
|
||||
|
||||
## Set Grid
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Grid.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20Grid.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The default grid size in Excalidraw is 20. Currently there is no way to change the grid size via the user interface. This script offers a way to bridge this gap.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-grid.jpg'></td></tr></table>
|
||||
|
||||
## Set Link Alias
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Link%20Alias.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20Link%20Alias.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Iterates all of the links in the selected TextElements and prompts the user to set or modify the alias for each link found.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-set-link-alias.jpg'></td></tr></table>
|
||||
|
||||
## Set Stroke Width of Selected Elements
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Stroke%20Width%20of%20Selected%20Elements.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20Stroke%20Width%20of%20Selected%20Elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will set the stroke width of selected elements. This is helpful, for example, when you scale freedraw sketches and want to reduce or increase their line width.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-stroke-width.jpg'></td></tr></table>
|
||||
|
||||
## Set Text Alignment
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Text%20Alignment.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20Text%20Alignment.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Sets text alignment of text block (cetner, right, left). Useful if you want to set a keyboard shortcut for selecting text alignment.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-text-align.jpg'></td></tr></table>
|
||||
|
||||
## Split text by lines
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Split%20text%20by%20lines.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Split%20text%20by%20lines.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Split lines of text into separate text elements for easier reorganization<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-split-lines.jpg'></td></tr></table>
|
||||
|
||||
## TheBrain-navigation
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/TheBrain-navigation.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/TheBrain-navigation.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">An Excalidraw based graph user interface for your Vault. Requires the <a href='https://github.com/SkepticMystic/breadcrumbs'>Breadcrumbs plugin</a> to be installed and configured as well. Generates a user interface similar to that of <a href='https://TheBrain.com'>TheBrain</a>. Watch this introduction to this script on <a href='https://youtu.be/J4T5KHERH_o'>YouTube</a>.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/TheBrain.jpg'></td></tr></table>
|
||||
|
||||
## Toggle Fullscreen on Mobile
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Toggle%20Fullscreen%20on%20Mobile.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Toggle%20Fullscreen%20on%20Mobile.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Hides Obsidian workspace leaf padding and header (based on option in settings, default is "hide header" = false) which will take Excalidraw to full screen. ⚠ Note that if the header is not visible, it will be very difficult to invoke the command palette to end full screen. Only hide the header if you have a keyboard or you've practiced opening command palette!<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/ea-toggle-fullscreen.jpg'></td></tr></table>
|
||||
|
||||
## Transfer TextElements to Excalidraw markdown metadata
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Transfer%20TextElements%20to%20Excalidraw%20markdown%20metadata.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Transfer%20TextElements%20to%20Excalidraw%20markdown%20metadata.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script will delete the selected text elements from the canvas and will copy the text from these text elements into the Excalidraw markdown file as metadata. This means, that the text will no longer be visible in the drawing, however you will be able to search for the text in Obsidian and find the drawing containing this image.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-text-to-metadata.jpg'></td></tr></table>
|
||||
|
||||
## Zoom to Fit Selected Elements
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Zoom%20to%20Fit%20Selected%20Elements.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Zoom%20to%20Fit%20Selected%20Elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Similar to Excalidraw standard <kbd>SHIFT+2</kbd> feature: Zoom to fit selected elements, but with the ability to zoom to 1000%. Inspiration: [#272](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/272)</td></tr></table>
|
||||
If you are enjoying the Excalidraw plugin then please support my work and enthusiasm by buying me a coffee on [https://ko-fi/zsolt](https://ko-fi.com/zsolt).
|
||||
|
||||
[<img src="https://user-images.githubusercontent.com/14358394/115450238-f39e8100-a21b-11eb-89d0-fa4b82cdbce8.png" class="coffee">](https://ko-fi.com/zsolt)
|
||||
|
||||
---
|
||||
|
||||
Jump ahead to the [[#List of available scripts]]
|
||||
|
||||
# Introducing Excalidraw Automate Script Engine
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/hePJcObHIso" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
|
||||
Script Engine scripts are installed in the `Downloaded` subfolder of the `Excalidraw Automate script folder` specified in plugin settings.
|
||||
|
||||
In the `Command Palette` installed scripts are prefixed with `Downloaded/`, thus you can always know if you are executing a local script of your own, or one that you have downloaded from GitHub.
|
||||
|
||||
## Attention developers and hobby hackers
|
||||
<img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/hobby-programmer.svg' align='left' style='background-color:whitesmoke; width:80px; margin-right:15px; margin-bottom:10px;'/>
|
||||
If you want to modify scripts, I recommend moving them to the `Excalidraw Automate script folder` or a different subfolder under the script folder. Scripts in the `Downloaded` folder will be overwritten when you click the `Update this script` button. Note also, that at this time, I do not check if the script file has been updated on GitHub, thus the `Update this script` button is always visible once you have installed a script, not only when an update is availble (hope to build this feature in the future).
|
||||
|
||||
I would love to include your contribution in the script library. If you have a script of your own that you would like to share with the community, please open a [PR](https://github.com/zsviczian/obsidian-excalidraw-plugin/pulls) on GitHub. Be sure to include the following in your pull request
|
||||
- The [script file](https://github.com/zsviczian/obsidian-excalidraw-plugin/tree/master/ea-scripts) with a self explanetory name. The name of the file will be the name of the script in the Command Palette.
|
||||
- An [image](https://github.com/zsviczian/obsidian-excalidraw-plugin/tree/master/images) explaining the scripts purpose. Remember a picture speaks thousand words!
|
||||
- An update to this file [ea-scripts/index.md](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/index.md)
|
||||
|
||||
---
|
||||
|
||||
# List of available scripts
|
||||
- [[#Add Connector Point]]
|
||||
- [[#Add Link to Existing File and Open]]
|
||||
- [[#Add Link to New Page and Open]]
|
||||
- [[#Add Next Step in Process]]
|
||||
- [[#Box Each Selected Groups]]
|
||||
- [[#Box Selected Elements]]
|
||||
- [[#Change shape of selected elements]]
|
||||
- [[#Connect elements]]
|
||||
- [[#Convert freedraw to line]]
|
||||
- [[#Convert selected text elements to sticky notes]]
|
||||
- [[#Convert text to link with folder and alias]]
|
||||
- [[#Copy Selected Element Styles to Global]]
|
||||
- [[#Create new markdown file and embed into active drawing]]
|
||||
- [[#Darken background color]]
|
||||
- [[#Elbow connectors]]
|
||||
- [[#Expand rectangles horizontally keep text centered]]
|
||||
- [[#Expand rectangles horizontally]]
|
||||
- [[#Expand rectangles vertically keep text centered]]
|
||||
- [[#Expand rectangles vertically]]
|
||||
- [[#Fixed horizontal distance between centers]]
|
||||
- [[#Fixed inner distance]]
|
||||
- [[#Fixed spacing]]
|
||||
- [[#Fixed vertical distance between centers]]
|
||||
- [[#Fixed vertical distance]]
|
||||
- [[#Lighten background color]]
|
||||
- [[Mindmap connector]]
|
||||
- [[#Modify background color opacity]]
|
||||
- [[#Normalize Selected Arrows]]
|
||||
- [[#OCR - Optical Character Recognition]]
|
||||
- [[#Organic Line]]
|
||||
- [[#Repeat Elements]]
|
||||
- [[#Reverse arrows]]
|
||||
- [[#Scribble Helper]]
|
||||
- [[#Select Elements of Type]]
|
||||
- [[#Set background color of unclosed line object by adding a shadow clone]]
|
||||
- [[#Set Dimensions]]
|
||||
- [[#Set Font Family]]
|
||||
- [[#Set Grid]]
|
||||
- [[#Set Link Alias]]
|
||||
- [[#Set Stroke Width of Selected Elements]]
|
||||
- [[#Set Text Alignment]]
|
||||
- [[#Split text by lines]]
|
||||
- [[#Toggle Fullscreen on Mobile]]
|
||||
- [[#Transfer TextElements to Excalidraw markdown metadata]]
|
||||
- [[#Zoom to Fit Selected Elements]]
|
||||
|
||||
## Add Connector Point
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Connector%20Point.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Add%20Connector%20Point.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will add a small circle to the top left of each text element in the selection and add the text and the "connector point" to a group. You can use the connector points to link text elements with an arrow (in for example a Wardley Map).<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-bullet-point.jpg'></td></tr></table>
|
||||
|
||||
## Add Link to Existing File and Open
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Link%20to%20Existing%20File%20and%20Open.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Add%20Link%20to%20Existing%20File%20and%20Open.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Prompts for a file from the vault. Adds a link to the selected element pointing to the selected file. You can control in settings to open the file in the current active pane or an adjacent pane.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-link-and-open.jpg'></td></tr></table>
|
||||
|
||||
## Add Link to New Page and Open
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Link%20to%20New%20Page%20and%20Open.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Add%20Link%20to%20New%20Page%20and%20Open.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Prompts for filename. Offers option to create and open a new Markdown or Excalidraw document. Adds link pointing to the new file, to the selected objects in the drawing. You can control in settings to open the file in the current active pane or an adjacent pane.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-link-to-new-page-and-pen.jpg'></td></tr></table>
|
||||
|
||||
## Add Next Step in Process
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Next%20Step%20in%20Process.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Add%20Next%20Step%20in%20Process.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will prompt you for the title of the process step, then will create a stick note with the text. If an element is selected then the script will connect this new step with an arrow to the previous step (the selected element). If no element is selected, then the script assumes this is the first step in the process and will only output the sticky note with the text that was entered.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-process-step.jpg'></td></tr></table>
|
||||
|
||||
## Box Each Selected Groups
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Box%20Each%20Selected%20Groups.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Box%20Each%20Selected%20Groups.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will add encapsulating boxes around each of the currently selected groups in Excalidraw.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-box-each-selected-groups.png'></td></tr></table>
|
||||
|
||||
## Box Selected Elements
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Box%20Selected%20Elements.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Box%20Selected%20Elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will add an encapsulating box around the currently selected elements in Excalidraw.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-box-elements.jpg'></td></tr></table>
|
||||
|
||||
## Change shape of selected elements
|
||||
```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>
|
||||
|
||||
## Connect elements
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Connect%20elements.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Connect%20elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will connect two objects with an arrow. If either of the objects are a set of grouped elements (e.g. a text element grouped with an encapsulating rectangle), the script will identify these groups, and connect the arrow to the largest object in the group (assuming you want to connect the arrow to the box around the text element).<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-connect-elements.jpg'></td></tr></table>
|
||||
|
||||
## Convert freedraw to line
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Convert%20freedraw%20to%20line.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Convert%20freedraw%20to%20line.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Convert selected freedraw objects into editable lines. This will allow you to adjust your drawings by dragging line points and will also allow you to select shape fill in case of enclosed lines. You can adjust conversion point density in settings.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-convert-freedraw-to-line.jpg'></td></tr></table>
|
||||
|
||||
## Convert selected text elements to sticky notes
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Convert%20selected%20text%20elements%20to%20sticky%20notes.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Convert%20selected%20text%20elements%20to%20sticky%20notes.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Converts selected plain text elements to sticky notes with transparent background and transparent stroke color (default setting, can be changed in plugin settings). Essentially converts text element into a wrappable format.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-textelement-to-transparent-stickynote.png'></td></tr></table>
|
||||
|
||||
## Convert text to link with folder and alias
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Convert%20text%20to%20link%20with%20folder%20and%20alias.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Convert%20text%20to%20link%20with%20folder%20and%20alias.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Converts text elements to links pointing to a file in a selected folder and with the alias set as the original text. The script will prompt the user to select an existing folder from the vault.<br><code>original text</code> - <code>[[selected folder/original text|original text]]</code></td></tr></table>
|
||||
|
||||
## Copy Selected Element Styles to Global
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Copy%20Selected%20Element%20Styles%20to%20Global.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Copy%20Selected%20Element%20Styles%20to%20Global.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will copy styles of any selected element into Excalidraw's global styles.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-copy-selected-element-styles-to-global.png'></td></tr></table>
|
||||
|
||||
## Create new markdown file and embed into active drawing
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Create%20new%20markdown%20file%20and%20embed%20into%20active%20drawing.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Create%20new%20markdown%20file%20and%20embed%20into%20active%20drawing.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script will prompt you for a filename, then create a new markdown document with the file name provided, open the new markdown document in an adjacent pane, and embed the markdown document into the active Excalidraw drawing.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-create-and-embed-new-markdown-file.jpg'></td></tr></table>
|
||||
|
||||
## Darken background color
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Darken%20background%20color.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Darken%20background%20color.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script darkens the background color of the selected element by 2% at a time. You can use this script several times until you are satisfied. It is recommended to set a shortcut key for this script so that you can quickly try to DARKEN and LIGHTEN the color effect. In contrast to the `Modify background color opacity` script, the advantage is that the background color of the element is not affected by the canvas color, and the color value does not appear in a strange rgba() form.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/darken-lighten-background-color.png'></td></tr></table>
|
||||
|
||||
## Elbow connectors
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Elbow%20connectors.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Elbow%20connectors.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script converts the selected connectors to elbows.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/elbow-connectors.png'></td></tr></table>
|
||||
|
||||
## Expand rectangles horizontally keep text centered
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Expand%20rectangles%20horizontally%20keep%20text%20centered.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Expand%20rectangles%20horizontally%20keep%20text%20centered.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script expands the width of the selected rectangles until they are all the same width and keep the text centered.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-expand-rectangles.gif'></td></tr></table>
|
||||
|
||||
## Expand rectangles horizontally
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Expand%20rectangles%20horizontally.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Expand%20rectangles%20horizontally.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script expands the width of the selected rectangles until they are all the same width.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-expand-rectangles.gif'></td></tr></table>
|
||||
|
||||
## Expand rectangles vertically keep text centered
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Expand%20rectangles%20vertically%20keep%20text%20centered.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Expand%20rectangles%20vertically%20keep%20text%20centered.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script expands the height of the selected rectangles until they are all the same height and keep the text centered.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-expand-rectangles.gif'></td></tr></table>
|
||||
|
||||
## Expand rectangles vertically
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Expand%20rectangles%20vertically.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Expand%20rectangles%20vertically.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script expands the height of the selected rectangles until they are all the same height.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-expand-rectangles.gif'></td></tr></table>
|
||||
|
||||
## Fixed horizontal distance between centers
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20horizontal%20distance%20between%20centers.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Fixed%20horizontal%20distance%20between%20centers.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script arranges the selected elements horizontally with a fixed center spacing.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fixed-horizontal-distance-between-centers.png'></td></tr></table>
|
||||
|
||||
## Fixed inner distance
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20inner%20distance.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Fixed%20inner%20distance.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script arranges selected elements and groups with a fixed inner distance.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fixed-inner-distance.png'></td></tr></table>
|
||||
|
||||
## Fixed spacing
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20spacing.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Fixed%20spacing.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script arranges the selected elements horizontally with a fixed spacing. When we create an architecture diagram or mind map, we often need to arrange a large number of elements in a fixed spacing. `Fixed spacing` and `Fixed vertical Distance` scripts can save us a lot of time.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fix-space-demo.png'></td></tr></table>
|
||||
|
||||
## Fixed vertical distance between centers
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20vertical%20distance%20between%20centers.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Fixed%20vertical%20distance%20between%20centers.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script arranges the selected elements vertically with a fixed center spacing.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fixed-vertical-distance-between-centers.png'></td></tr></table>
|
||||
|
||||
## Fixed vertical distance
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20vertical%20distance.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Fixed%20vertical%20distance.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script arranges the selected elements vertically with a fixed spacing. When we create an architecture diagram or mind map, we often need to arrange a large number of elements in a fixed spacing. `Fixed spacing` and `Fixed vertical Distance` scripts can save us a lot of time.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fixed-vertical-distance.png'></td></tr></table>
|
||||
|
||||
## Lighten background color
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Lighten%20background%20color.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Lighten%20background%20color.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script lightens the background color of the selected element by 2% at a time. You can use this script several times until you are satisfied. It is recommended to set a shortcut key for this script so that you can quickly try to DARKEN and LIGHTEN the color effect.In contrast to the `Modify background color opacity` script, the advantage is that the background color of the element is not affected by the canvas color, and the color value does not appear in a strange rgba() form.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/darken-lighten-background-color.png'></td></tr></table>
|
||||
|
||||
## Mindmap connector
|
||||
```excalidraw-script-install
|
||||
https://github.com/xllowl/obsidian-excalidraw-plugin/blob/master/ea-scripts/Mindmap%20connector.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/xllowl'>@xllowl</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/xllowl/obsidian-excalidraw-plugin/blob/master/ea-scripts/Mindmap%20connector.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script creates mindmap like lines(only right side available). The line will starts according to the creation time of the elements. So you may need to create the header element first.<br><img src='https://github.com/xllowl/obsidian-excalidraw-plugin/blob/master/images/mindmap%20connector.png'></td></tr></table>
|
||||
|
||||
## Modify background color opacity
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Modify%20background%20color%20opacity.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Modify%20background%20color%20opacity.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script changes the opacity of the background color of the selected boxes. The default background color in Excalidraw is so dark that the text is hard to read. You can lighten the color a bit by setting transparency. And you can tweak the transparency over and over again until you're happy with it. Although excalidraw has the opacity option in its native property Settings, it also changes the transparency of the border. Use this script to change only the opacity of the background color without affecting the border.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-modify-background-color-opacity.png'></td></tr></table>
|
||||
|
||||
## Normalize Selected Arrows
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Normalize%20Selected%20Arrows.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Normalize%20Selected%20Arrows.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will reset the start and end positions of the selected arrows. The arrow will point to the center of the connected box and will have a gap of 8px from the box.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-normalize-selected-arrows.png'></td></tr></table>
|
||||
|
||||
## OCR - Optical Character Recognition
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/OCR%20-%20Optical%20Character%20Recognition.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/OCR%20-%20Optical%20Character%20Recognition.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">REQUIRES EXCALIDRAW 1.5.15<br>The script will 1) send the selected image file to [taskbone.com](https://taskbone.com) to exctract the text from the image, and 2) will add the text to your drawing as a text element.<br><mark>⚠ Note that you will need to manually paste your token into the script after the first run! ⚠</mark><br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-ocr.jpg'><br><iframe width="560" height="315" src="https://www.youtube.com/embed/W2NMzR8s4eE" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></td></tr></table>
|
||||
|
||||
## Organic Line
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Organic%20Line.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Organic%20Line.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Converts selected freedraw lines such that pencil pressure will decrease from maximum to minimum from the beginning of the line to its end. The resulting line is placed at the back of the layers, under all other items. Helpful when drawing organic mindmaps.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-organic-line.jpg'></td></tr></table>
|
||||
|
||||
## Repeat Elements
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Repeat%20Elements.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Repeat%20Elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will detect the difference between 2 selected elements, including position, size, angle, stroke and background color, and create several elements that repeat these differences based on the number of repetitions entered by the user.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-repeat-elements.png'></td></tr></table>
|
||||
|
||||
## Reverse arrows
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Reverse%20arrows.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Reverse%20arrows.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Reverse the direction of **arrows** within the scope of selected elements.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-reverse-arrow.jpg'></td></tr></table>
|
||||
|
||||
## Scribble Helper
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Scribble%20Helper.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Scribble%20Helper.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">iOS scribble helper for better handwriting experience with text elements. If no elements are selected then the creates a text element at pointer position and you can use the edit box to modify the text with scribble. If a text element is selected then opens the input prompt where you can modify this text with scribble.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-scribble-helper.jpg'></td></tr></table>
|
||||
|
||||
## Select Elements of Type
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Select%20Elements%20of%20Type.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Select%20Elements%20of%20Type.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Prompts you with a list of the different element types in the active image. Only elements of the selected type will be selected on the canvas. If nothing is selected when running the script, then the script will process all the elements on the canvas. If some elements are selected when the script is executed, then the script will only process the selected elements.<br>The script is useful when, for example, you want to bring to front all the arrows, or want to change the color of all the text elements, etc.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-select-element-of-type.jpg'></td></tr></table>
|
||||
|
||||
## Set background color of unclosed line object by adding a shadow clone
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20background%20color%20of%20unclosed%20line%20object%20by%20adding%20a%20shadow%20clone.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20background%20color%20of%20unclosed%20line%20object%20by%20adding%20a%20shadow%20clone.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Use this script to set the background color of unclosed (i.e. open) line objects by creating a clone of the object. The script will set the stroke color of the clone to transparent and will add a straight line to close the object. Use settings to define the default background color, the fill style, and the strokeWidth of the clone. By default the clone will be grouped with the original object, you can disable this also in settings.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-set-background-color-of-unclosed-line.jpg'></td></tr></table>
|
||||
|
||||
## Set Dimensions
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Dimensions.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20Dimensions.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Currently there is no way to specify the exact location and size of objects in Excalidraw. You can bridge this gap with the following simple script.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-dimensions.jpg'></td></tr></table>
|
||||
|
||||
## Set Font Family
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Font%20Family.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20Font%20Family.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Sets font family of the text block (Virgil, Helvetica, Cascadia). Useful if you want to set a keyboard shortcut for selecting font family.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-font-family.jpg'></td></tr></table>
|
||||
|
||||
## Set Grid
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Grid.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20Grid.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The default grid size in Excalidraw is 20. Currently there is no way to change the grid size via the user interface. This script offers a way to bridge this gap.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-grid.jpg'></td></tr></table>
|
||||
|
||||
## Set Link Alias
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Link%20Alias.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20Link%20Alias.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Iterates all of the links in the selected TextElements and prompts the user to set or modify the alias for each link found.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-set-link-alias.jpg'></td></tr></table>
|
||||
|
||||
## Set Stroke Width of Selected Elements
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Stroke%20Width%20of%20Selected%20Elements.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20Stroke%20Width%20of%20Selected%20Elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will set the stroke width of selected elements. This is helpful, for example, when you scale freedraw sketches and want to reduce or increase their line width.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-stroke-width.jpg'></td></tr></table>
|
||||
|
||||
## Set Text Alignment
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Text%20Alignment.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20Text%20Alignment.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Sets text alignment of text block (cetner, right, left). Useful if you want to set a keyboard shortcut for selecting text alignment.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-text-align.jpg'></td></tr></table>
|
||||
|
||||
## Split text by lines
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Split%20text%20by%20lines.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Split%20text%20by%20lines.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Split lines of text into separate text elements for easier reorganization<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-split-lines.jpg'></td></tr></table>
|
||||
|
||||
## TheBrain-navigation
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/TheBrain-navigation.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/TheBrain-navigation.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">An Excalidraw based graph user interface for your Vault. Requires the <a href='https://github.com/SkepticMystic/breadcrumbs'>Breadcrumbs plugin</a> to be installed and configured as well. Generates a user interface similar to that of <a href='https://TheBrain.com'>TheBrain</a>. Watch this introduction to this script on <a href='https://youtu.be/J4T5KHERH_o'>YouTube</a>.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/TheBrain.jpg'></td></tr></table>
|
||||
|
||||
## Toggle Fullscreen on Mobile
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Toggle%20Fullscreen%20on%20Mobile.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Toggle%20Fullscreen%20on%20Mobile.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Hides Obsidian workspace leaf padding and header (based on option in settings, default is "hide header" = false) which will take Excalidraw to full screen. ⚠ Note that if the header is not visible, it will be very difficult to invoke the command palette to end full screen. Only hide the header if you have a keyboard or you've practiced opening command palette!<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/ea-toggle-fullscreen.jpg'></td></tr></table>
|
||||
|
||||
## Transfer TextElements to Excalidraw markdown metadata
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Transfer%20TextElements%20to%20Excalidraw%20markdown%20metadata.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Transfer%20TextElements%20to%20Excalidraw%20markdown%20metadata.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script will delete the selected text elements from the canvas and will copy the text from these text elements into the Excalidraw markdown file as metadata. This means, that the text will no longer be visible in the drawing, however you will be able to search for the text in Obsidian and find the drawing containing this image.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-text-to-metadata.jpg'></td></tr></table>
|
||||
|
||||
## Zoom to Fit Selected Elements
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Zoom%20to%20Fit%20Selected%20Elements.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Zoom%20to%20Fit%20Selected%20Elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Similar to Excalidraw standard <kbd>SHIFT+2</kbd> feature: Zoom to fit selected elements, but with the ability to zoom to 1000%. Inspiration: [#272](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/272)</td></tr></table>
|
||||
399
ea-scripts/Auto Layout.md
Normal file
@@ -0,0 +1,399 @@
|
||||
/*
|
||||
|
||||

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

|
||||
|
||||
This script performs automatic layout for the selected top-level grouping objects. It is powered by [elkjs](https://github.com/kieler/elkjs) and needs to be connected to the Internet.
|
||||
|
||||
|
||||
See documentation for more details:
|
||||
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
|
||||
|
||||
```javascript
|
||||
*/
|
||||
|
||||
if (
|
||||
!ea.verifyMinimumPluginVersion ||
|
||||
!ea.verifyMinimumPluginVersion("1.5.21")
|
||||
) {
|
||||
new Notice(
|
||||
"This script requires a newer version of Excalidraw. Please install the latest version."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
settings = ea.getScriptSettings();
|
||||
//set default values on first run
|
||||
if (!settings["Layout Options JSON"]) {
|
||||
settings = {
|
||||
"Layout Options JSON": {
|
||||
height: "450px",
|
||||
value: `{\n "org.eclipse.elk.layered.crossingMinimization.semiInteractive": "true",\n "org.eclipse.elk.layered.considerModelOrder.components": "FORCE_MODEL_ORDER"\n}`,
|
||||
description: `You can use layout options to configure the layout algorithm. A list of all options and further details of their exact effects is available in <a href="http://www.eclipse.org/elk/reference.html" rel="nofollow">ELK's documentation</a>.`,
|
||||
},
|
||||
};
|
||||
ea.setScriptSettings(settings);
|
||||
}
|
||||
|
||||
if (typeof ELK === "undefined") {
|
||||
loadELK(doAutoLayout);
|
||||
} else {
|
||||
doAutoLayout();
|
||||
}
|
||||
|
||||
async function doAutoLayout() {
|
||||
const selectedElements = ea.getViewSelectedElements();
|
||||
const groups = ea
|
||||
.getMaximumGroups(selectedElements)
|
||||
.map((g) => g.filter((el) => el.containerId == null)) // ignore text in stickynote
|
||||
.filter((els) => els.length > 0);
|
||||
|
||||
const stickynotesMap = selectedElements
|
||||
.filter((el) => el.containerId != null)
|
||||
.reduce((result, el) => {
|
||||
result.set(el.containerId, el);
|
||||
return result;
|
||||
}, new Map());
|
||||
|
||||
const elk = new ELK();
|
||||
const knownLayoutAlgorithms = await elk.knownLayoutAlgorithms();
|
||||
const layoutAlgorithms = knownLayoutAlgorithms
|
||||
.map((knownLayoutAlgorithm) => ({
|
||||
id: knownLayoutAlgorithm.id,
|
||||
displayText:
|
||||
knownLayoutAlgorithm.id === "org.eclipse.elk.layered" ||
|
||||
knownLayoutAlgorithm.id === "org.eclipse.elk.radial" ||
|
||||
knownLayoutAlgorithm.id === "org.eclipse.elk.mrtree"
|
||||
? "* " +
|
||||
knownLayoutAlgorithm.name +
|
||||
": " +
|
||||
knownLayoutAlgorithm.description
|
||||
: knownLayoutAlgorithm.name + ": " + knownLayoutAlgorithm.description,
|
||||
}))
|
||||
.sort((lha, rha) => lha.displayText.localeCompare(rha.displayText));
|
||||
|
||||
const layoutAlgorithmsSimple = knownLayoutAlgorithms
|
||||
.map((knownLayoutAlgorithm) => ({
|
||||
id: knownLayoutAlgorithm.id,
|
||||
displayText:
|
||||
knownLayoutAlgorithm.id === "org.eclipse.elk.layered" ||
|
||||
knownLayoutAlgorithm.id === "org.eclipse.elk.radial" ||
|
||||
knownLayoutAlgorithm.id === "org.eclipse.elk.mrtree"
|
||||
? "* " + knownLayoutAlgorithm.name
|
||||
: knownLayoutAlgorithm.name,
|
||||
}))
|
||||
.sort((lha, rha) => lha.displayText.localeCompare(rha.displayText));
|
||||
|
||||
// const knownOptions = knownLayoutAlgorithms
|
||||
// .reduce(
|
||||
// (result, knownLayoutAlgorithm) => [
|
||||
// ...result,
|
||||
// ...knownLayoutAlgorithm.knownOptions,
|
||||
// ],
|
||||
// []
|
||||
// )
|
||||
// .filter((value, index, self) => self.indexOf(value) === index) // remove duplicates
|
||||
// .sort((lha, rha) => lha.localeCompare(rha));
|
||||
// console.log("knownOptions", knownOptions);
|
||||
|
||||
const selectedAlgorithm = await utils.suggester(
|
||||
layoutAlgorithms.map((algorithmInfo) => algorithmInfo.displayText),
|
||||
layoutAlgorithms.map((algorithmInfo) => algorithmInfo.id),
|
||||
"Layout algorithm"
|
||||
);
|
||||
|
||||
const knownNodePlacementStrategy = [
|
||||
"SIMPLE",
|
||||
"INTERACTIVE",
|
||||
"LINEAR_SEGMENTS",
|
||||
"BRANDES_KOEPF",
|
||||
"NETWORK_SIMPLEX",
|
||||
];
|
||||
|
||||
const knownDirections = [
|
||||
"UNDEFINED",
|
||||
"RIGHT",
|
||||
"LEFT",
|
||||
"DOWN",
|
||||
"UP"
|
||||
];
|
||||
|
||||
let nodePlacementStrategy = "BRANDES_KOEPF";
|
||||
let componentComponentSpacing = "10";
|
||||
let nodeNodeSpacing = "100";
|
||||
let nodeNodeBetweenLayersSpacing = "100";
|
||||
let discoComponentLayoutAlgorithm = "org.eclipse.elk.layered";
|
||||
let direction = "UNDEFINED";
|
||||
|
||||
if (selectedAlgorithm === "org.eclipse.elk.layered") {
|
||||
nodePlacementStrategy = await utils.suggester(
|
||||
knownNodePlacementStrategy,
|
||||
knownNodePlacementStrategy,
|
||||
"Node placement strategy"
|
||||
);
|
||||
|
||||
selectedDirection = await utils.suggester(
|
||||
knownDirections,
|
||||
knownDirections,
|
||||
"Direction"
|
||||
);
|
||||
direction = selectedDirection??"UNDEFINED";
|
||||
} else if (selectedAlgorithm === "org.eclipse.elk.disco") {
|
||||
const componentLayoutAlgorithms = layoutAlgorithmsSimple.filter(al => al.id !== "org.eclipse.elk.disco");
|
||||
const selectedDiscoComponentLayoutAlgorithm = await utils.suggester(
|
||||
componentLayoutAlgorithms.map((algorithmInfo) => algorithmInfo.displayText),
|
||||
componentLayoutAlgorithms.map((algorithmInfo) => algorithmInfo.id),
|
||||
"Disco Connected Components Layout Algorithm"
|
||||
);
|
||||
discoComponentLayoutAlgorithm = selectedDiscoComponentLayoutAlgorithm??"org.eclipse.elk.layered";
|
||||
}
|
||||
|
||||
if (
|
||||
selectedAlgorithm === "org.eclipse.elk.box" ||
|
||||
selectedAlgorithm === "org.eclipse.elk.rectpacking"
|
||||
) {
|
||||
nodeNodeSpacing = await utils.inputPrompt("Node Spacing", "number", "10");
|
||||
} else {
|
||||
let userSpacingStr = await utils.inputPrompt(
|
||||
"Components Spacing, Node Spacing, Node Node Between Layers Spacing",
|
||||
"number, number, number",
|
||||
"10, 100, 100"
|
||||
);
|
||||
let userSpacingArr = (userSpacingStr??"").split(",");
|
||||
componentComponentSpacing = userSpacingArr[0] ?? "10";
|
||||
nodeNodeSpacing = userSpacingArr[1] ?? "100";
|
||||
nodeNodeBetweenLayersSpacing = userSpacingArr[2] ?? "100";
|
||||
}
|
||||
|
||||
let layoutOptionsJson = {};
|
||||
try {
|
||||
layoutOptionsJson = JSON.parse(settings["Layout Options JSON"].value);
|
||||
} catch (e) {
|
||||
new Notice(
|
||||
"Error reading Layout Options JSON, see developer console for more information",
|
||||
4000
|
||||
);
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
layoutOptionsJson["elk.algorithm"] = selectedAlgorithm;
|
||||
layoutOptionsJson["org.eclipse.elk.spacing.componentComponent"] =
|
||||
componentComponentSpacing;
|
||||
layoutOptionsJson["org.eclipse.elk.spacing.nodeNode"] = nodeNodeSpacing;
|
||||
layoutOptionsJson["org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers"] =
|
||||
nodeNodeBetweenLayersSpacing;
|
||||
layoutOptionsJson["org.eclipse.elk.layered.nodePlacement.strategy"] =
|
||||
nodePlacementStrategy;
|
||||
layoutOptionsJson["org.eclipse.elk.disco.componentCompaction.componentLayoutAlgorithm"] =
|
||||
discoComponentLayoutAlgorithm;
|
||||
layoutOptionsJson["org.eclipse.elk.direction"] = direction;
|
||||
|
||||
const graph = {
|
||||
id: "root",
|
||||
layoutOptions: layoutOptionsJson,
|
||||
children: [],
|
||||
edges: [],
|
||||
};
|
||||
|
||||
let groupMap = new Map();
|
||||
let targetElkMap = new Map();
|
||||
let arrowEls = [];
|
||||
|
||||
for (let i = 0; i < groups.length; i++) {
|
||||
const elements = groups[i];
|
||||
if (
|
||||
elements.length === 1 &&
|
||||
(elements[0].type === "arrow" || elements[0].type === "line")
|
||||
) {
|
||||
if (
|
||||
elements[0].type === "arrow" &&
|
||||
elements[0].startBinding &&
|
||||
elements[0].endBinding
|
||||
) {
|
||||
arrowEls.push(elements[0]);
|
||||
}
|
||||
} else {
|
||||
let elkId = "g" + i;
|
||||
elements.reduce((result, el) => {
|
||||
result.set(el.id, elkId);
|
||||
return result;
|
||||
}, targetElkMap);
|
||||
|
||||
const box = ea.getBoundingBox(elements);
|
||||
groupMap.set(elkId, {
|
||||
elements: elements,
|
||||
boundingBox: box,
|
||||
});
|
||||
|
||||
graph.children.push({
|
||||
id: elkId,
|
||||
width: box.width,
|
||||
height: box.height,
|
||||
x: box.topX,
|
||||
y: box.topY,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < arrowEls.length; i++) {
|
||||
const arrowEl = arrowEls[i];
|
||||
const startElkId = targetElkMap.get(arrowEl.startBinding.elementId);
|
||||
const endElkId = targetElkMap.get(arrowEl.endBinding.elementId);
|
||||
|
||||
graph.edges.push({
|
||||
id: "e" + i,
|
||||
sources: [startElkId],
|
||||
targets: [endElkId],
|
||||
});
|
||||
}
|
||||
|
||||
const initTopX =
|
||||
Math.min(...Array.from(groupMap.values()).map((v) => v.boundingBox.topX)) -
|
||||
12;
|
||||
const initTopY =
|
||||
Math.min(...Array.from(groupMap.values()).map((v) => v.boundingBox.topY)) -
|
||||
12;
|
||||
|
||||
elk
|
||||
.layout(graph)
|
||||
.then((resultGraph) => {
|
||||
for (const elkEl of resultGraph.children) {
|
||||
const group = groupMap.get(elkEl.id);
|
||||
for (const groupEl of group.elements) {
|
||||
const originalDistancX = groupEl.x - group.boundingBox.topX;
|
||||
const originalDistancY = groupEl.y - group.boundingBox.topY;
|
||||
const groupElDistanceX =
|
||||
elkEl.x + initTopX + originalDistancX - groupEl.x;
|
||||
const groupElDistanceY =
|
||||
elkEl.y + initTopY + originalDistancY - groupEl.y;
|
||||
|
||||
groupEl.x = groupEl.x + groupElDistanceX;
|
||||
groupEl.y = groupEl.y + groupElDistanceY;
|
||||
|
||||
if (stickynotesMap.has(groupEl.id)) {
|
||||
const stickynote = stickynotesMap.get(groupEl.id);
|
||||
stickynote.x = stickynote.x + groupElDistanceX;
|
||||
stickynote.y = stickynote.y + groupElDistanceY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ea.copyViewElementsToEAforEditing(selectedElements);
|
||||
ea.addElementsToView(false, false);
|
||||
|
||||
normalizeSelectedArrows();
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
function loadELK(doAfterLoaded) {
|
||||
let script = document.createElement("script");
|
||||
script.onload = function () {
|
||||
if (typeof ELK !== "undefined") {
|
||||
doAfterLoaded();
|
||||
}
|
||||
};
|
||||
script.src =
|
||||
"https://cdn.jsdelivr.net/npm/elkjs@0.8.2/lib/elk.bundled.min.js";
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
/*
|
||||
* Normalize Selected Arrows
|
||||
*/
|
||||
|
||||
function normalizeSelectedArrows() {
|
||||
let gapValue = 2;
|
||||
|
||||
const selectedIndividualArrows = ea.getMaximumGroups(ea.getViewSelectedElements())
|
||||
.reduce((result, g) => [...result, ...g.filter(el => el.type === 'arrow')], []);
|
||||
|
||||
const allElements = ea.getViewElements();
|
||||
for (const arrow of selectedIndividualArrows) {
|
||||
const startBindingEl = allElements.filter(
|
||||
(el) => el.id === (arrow.startBinding || {}).elementId
|
||||
)[0];
|
||||
const endBindingEl = allElements.filter(
|
||||
(el) => el.id === (arrow.endBinding || {}).elementId
|
||||
)[0];
|
||||
|
||||
if (startBindingEl) {
|
||||
recalculateStartPointOfLine(
|
||||
arrow,
|
||||
startBindingEl,
|
||||
endBindingEl,
|
||||
gapValue
|
||||
);
|
||||
}
|
||||
if (endBindingEl) {
|
||||
recalculateEndPointOfLine(arrow, endBindingEl, startBindingEl, gapValue);
|
||||
}
|
||||
}
|
||||
|
||||
ea.copyViewElementsToEAforEditing(selectedIndividualArrows);
|
||||
ea.addElementsToView(false, false);
|
||||
}
|
||||
|
||||
function recalculateStartPointOfLine(line, el, elB, gapValue) {
|
||||
const aX = el.x + el.width / 2;
|
||||
const bX =
|
||||
line.points.length <= 2 && elB
|
||||
? elB.x + elB.width / 2
|
||||
: line.x + line.points[1][0];
|
||||
const aY = el.y + el.height / 2;
|
||||
const bY =
|
||||
line.points.length <= 2 && elB
|
||||
? elB.y + elB.height / 2
|
||||
: line.y + line.points[1][1];
|
||||
|
||||
line.startBinding.gap = gapValue;
|
||||
line.startBinding.focus = 0;
|
||||
const intersectA = ea.intersectElementWithLine(
|
||||
el,
|
||||
[bX, bY],
|
||||
[aX, aY],
|
||||
line.startBinding.gap
|
||||
);
|
||||
|
||||
if (intersectA.length > 0) {
|
||||
line.points[0] = [0, 0];
|
||||
for (let i = 1; i < line.points.length; i++) {
|
||||
line.points[i][0] -= intersectA[0][0] - line.x;
|
||||
line.points[i][1] -= intersectA[0][1] - line.y;
|
||||
}
|
||||
line.x = intersectA[0][0];
|
||||
line.y = intersectA[0][1];
|
||||
}
|
||||
}
|
||||
|
||||
function recalculateEndPointOfLine(line, el, elB, gapValue) {
|
||||
const aX = el.x + el.width / 2;
|
||||
const bX =
|
||||
line.points.length <= 2 && elB
|
||||
? elB.x + elB.width / 2
|
||||
: line.x + line.points[line.points.length - 2][0];
|
||||
const aY = el.y + el.height / 2;
|
||||
const bY =
|
||||
line.points.length <= 2 && elB
|
||||
? elB.y + elB.height / 2
|
||||
: line.y + line.points[line.points.length - 2][1];
|
||||
|
||||
line.endBinding.gap = gapValue;
|
||||
line.endBinding.focus = 0;
|
||||
const intersectA = ea.intersectElementWithLine(
|
||||
el,
|
||||
[bX, bY],
|
||||
[aX, aY],
|
||||
line.endBinding.gap
|
||||
);
|
||||
|
||||
if (intersectA.length > 0) {
|
||||
line.points[line.points.length - 1] = [
|
||||
intersectA[0][0] - line.x,
|
||||
intersectA[0][1] - line.y,
|
||||
];
|
||||
}
|
||||
}
|
||||
1
ea-scripts/Auto Layout.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1670131481615" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3504" width="128" height="128" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M947.2 0H76.8C33.6 0 0 33.6 0 76.8v870.4C0 990.4 33.6 1024 76.8 1024h870.4c38.4 0 72-30.4 76.8-68.8V76.8C1024 33.6 990.4 0 947.2 0zM84.8 84.8h852.8V256H84.8V84.8z m256 256h596.8v256H340.8v-256z m-256 598.4V340.8H256v596.8H84.8z m256 0v-256h596.8v256H340.8z" p-id="3505"></path></svg>
|
||||
|
After Width: | Height: | Size: 616 B |
@@ -9,13 +9,12 @@ if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.21")) {
|
||||
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
|
||||
return;
|
||||
}
|
||||
|
||||
settings = ea.getScriptSettings();
|
||||
let settings = ea.getScriptSettings();
|
||||
//set default values on first run
|
||||
if(!settings["Border color"]) {
|
||||
settings = {
|
||||
"Border color" : {
|
||||
value: "transparent",
|
||||
value: "#000000",
|
||||
description: "Any legal HTML color (#000000, rgb, color-name, etc.). Set to 'transparent' for transparent color."
|
||||
},
|
||||
"Background color" : {
|
||||
@@ -28,27 +27,44 @@ if(!settings["Border color"]) {
|
||||
valueset: ["hachure","cross-hatch","solid"]
|
||||
}
|
||||
};
|
||||
ea.setScriptSettings(settings);
|
||||
await ea.setScriptSettings(settings);
|
||||
}
|
||||
|
||||
if(!settings["Max sticky note width"]) {
|
||||
settings["Max sticky note width"] = {
|
||||
value: "600",
|
||||
description: "Maximum width of new sticky note. If text is longer, it will be wrapped",
|
||||
valueset: ["400","600","800","1000","1200","1400","2000"]
|
||||
}
|
||||
await ea.setScriptSettings(settings);
|
||||
}
|
||||
const maxWidth = parseInt(settings["Max sticky note width"].value);
|
||||
const strokeColor = settings["Border color"].value;
|
||||
const backgroundColor = settings["Background color"].value;
|
||||
const fillStyle = settings["Background fill style"].value;
|
||||
|
||||
elements = ea.getViewSelectedElements()
|
||||
.filter((el)=>(el.type==="text")&&(el.containerId===null));
|
||||
if(elements.length===0) return;
|
||||
const elements = ea
|
||||
.getViewSelectedElements()
|
||||
.filter((el)=>(el.type==="text")&&(el.containerId===null));
|
||||
if(elements.length===0) {
|
||||
new Notice("Please select a text element");
|
||||
return;
|
||||
}
|
||||
ea.style.strokeColor = strokeColor;
|
||||
ea.style.backgroundColor = backgroundColor;
|
||||
ea.style.fillStyle = fillStyle;
|
||||
const padding = 6;
|
||||
let boxes = [];
|
||||
elements.forEach((el)=>{
|
||||
const id = ea.addRect(el.x-padding,el.y-padding,el.width+2*padding,el.height+2*padding);
|
||||
const boxes = [];
|
||||
ea.copyViewElementsToEAforEditing(elements);
|
||||
ea.getElements().forEach((el)=>{
|
||||
const width = el.width+2*padding;
|
||||
const widthOK = width<=maxWidth;
|
||||
const id = ea.addRect(el.x-padding,el.y-padding,widthOK?width:maxWidth,el.height+2*padding);
|
||||
boxes.push(id);
|
||||
ea.getElement(id).boundElements=[{type:"text",id:el.id}];
|
||||
el.containerId = id;
|
||||
});
|
||||
ea.copyViewElementsToEAforEditing(elements);
|
||||
await ea.addElementsToView(false,false);
|
||||
ea.selectElementsInView(ea.getViewElements().filter(el=>boxes.includes(el.id)));
|
||||
await ea.addElementsToView(false,true);
|
||||
const containers = ea.getViewElements().filter(el=>boxes.includes(el.id));
|
||||
ea.getExcalidrawAPI().updateContainerSize(containers);
|
||||
ea.selectElementsInView(containers);
|
||||
72
ea-scripts/Deconstruct selected elements into new drawing.md
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||

|
||||
|
||||
Select some elements in the scene. The script will take these elements and move them into a new Excalidraw file, and open that file. The selected elements will also be replaced in your original drawing with the embedded Excalidraw file (the one that was just created). You will be prompted for the file name of the new deconstructed image. The script is useful if you want to break a larger drawing into smaller reusable parts that you want to reference in multiple drawings.
|
||||
|
||||
```javascript
|
||||
*/
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.7.29")) {
|
||||
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
|
||||
return;
|
||||
}
|
||||
|
||||
const els = ea.getViewSelectedElements();
|
||||
if (els.length === 0) {
|
||||
new Notice("You must select elements first")
|
||||
return;
|
||||
}
|
||||
|
||||
const bb = ea.getBoundingBox(els);
|
||||
ea.copyViewElementsToEAforEditing(els);
|
||||
|
||||
ea.getElements().filter(el=>el.type==="image").forEach(el=>{
|
||||
const img = ea.targetView.excalidrawData.getFile(el.fileId);
|
||||
const path = (img?.linkParts?.original)??(img?.file?.path);
|
||||
if(img && path) {
|
||||
ea.imagesDict[el.fileId] = {
|
||||
mimeType: img.mimeType,
|
||||
id: el.fileId,
|
||||
dataURL: img.img,
|
||||
created: img.mtime,
|
||||
file: path,
|
||||
hasSVGwithBitmap: img.isSVGwithBitmap,
|
||||
latex: null,
|
||||
};
|
||||
return;
|
||||
}
|
||||
const equation = ea.targetView.excalidrawData.getEquation(el.fileId);
|
||||
eqImg = ea.targetView.getScene()?.files[el.fileId]
|
||||
if(equation && eqImg) {
|
||||
ea.imagesDict[el.fileId] = {
|
||||
mimeType: eqImg.mimeType,
|
||||
id: el.fileId,
|
||||
dataURL: eqImg.dataURL,
|
||||
created: eqImg.created,
|
||||
file: null,
|
||||
hasSVGwithBitmap: null,
|
||||
latex: equation.latex,
|
||||
};
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
let folder = ea.targetView.file.path;
|
||||
folder = folder.lastIndexOf("/")===-1?"":folder.substring(0,folder.lastIndexOf("/"))+"/";
|
||||
const fname = await utils.inputPrompt("Filename for new file","Filename","");
|
||||
const template = app.metadataCache.getFirstLinkpathDest(ea.plugin.settings.templateFilePath,"");
|
||||
|
||||
const newPath = await ea.create ({
|
||||
filename: fname + ".md",
|
||||
foldername: folder,
|
||||
templatePath: template?.path,
|
||||
onNewPane: true
|
||||
});
|
||||
|
||||
setTimeout(async ()=>{
|
||||
const file = app.metadataCache.getFirstLinkpathDest(newPath,"")
|
||||
ea.deleteViewElements(els);
|
||||
ea.clear();
|
||||
await ea.addImage(bb.topX,bb.topY,file,false);
|
||||
await ea.addElementsToView(false, true, true);
|
||||
ea.getExcalidrawAPI().history.clear(); //to avoid undo/redo messing up the decomposition
|
||||
},1000);
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M169.7 .9c-22.8-1.6-41.9 14-47.5 34.7L110.4 80c.5 0 1.1 0 1.6 0c176.7 0 320 143.3 320 320c0 .5 0 1.1 0 1.6l44.4-11.8c20.8-5.5 36.3-24.7 34.7-47.5C498.5 159.5 352.5 13.5 169.7 .9zM399.8 410.2c.1-3.4 .2-6.8 .2-10.2c0-159.1-128.9-288-288-288c-3.4 0-6.8 .1-10.2 .2L.5 491.9c-1.5 5.5 .1 11.4 4.1 15.4s9.9 5.6 15.4 4.1L399.8 410.2zM176 272c-17.7 0-32-14.3-32-32s14.3-32 32-32s32 14.3 32 32s-14.3 32-32 32zm128 64c0 17.7-14.3 32-32 32s-32-14.3-32-32s14.3-32 32-32s32 14.3 32 32zM160 384c0 17.7-14.3 32-32 32s-32-14.3-32-32s14.3-32 32-32s32 14.3 32 32z"/></svg>
|
||||
|
After Width: | Height: | Size: 624 B |
@@ -13,12 +13,49 @@ https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.h
|
||||
|
||||
```javascript
|
||||
*/
|
||||
const selectedCenterConnectPoints = await utils.suggester(
|
||||
['Yes', 'No'],
|
||||
[true, false],
|
||||
"Center connect points?"
|
||||
);
|
||||
const centerConnectPoints = selectedCenterConnectPoints??false;
|
||||
|
||||
const allElements = ea.getViewElements();
|
||||
const elements = ea.getViewSelectedElements();
|
||||
|
||||
const lines = elements.filter((el)=>el.type==="arrow" || el.type==="line");
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.points.length >= 3) {
|
||||
if(centerConnectPoints) {
|
||||
const startBindingEl = allElements.filter(el => el.id === (line.startBinding||{}).elementId)[0];
|
||||
const endBindingEl = allElements.filter(el => el.id === (line.endBinding||{}).elementId)[0];
|
||||
|
||||
if(startBindingEl) {
|
||||
const startPointX = line.x +line.points[0][0];
|
||||
if(startPointX >= startBindingEl.x && startPointX <= startBindingEl.x + startBindingEl.width) {
|
||||
line.points[0][0] = startBindingEl.x + startBindingEl.width / 2 - line.x;
|
||||
}
|
||||
|
||||
const startPointY = line.y +line.points[0][1];
|
||||
if(startPointY >= startBindingEl.y && startPointY <= startBindingEl.y + startBindingEl.height) {
|
||||
line.points[0][1] = startBindingEl.y + startBindingEl.height / 2 - line.y;
|
||||
}
|
||||
}
|
||||
|
||||
if(endBindingEl) {
|
||||
const startPointX = line.x +line.points[line.points.length-1][0];
|
||||
if(startPointX >= endBindingEl.x && startPointX <= endBindingEl.x + endBindingEl.width) {
|
||||
line.points[line.points.length-1][0] = endBindingEl.x + endBindingEl.width / 2 - line.x;
|
||||
}
|
||||
|
||||
const startPointY = line.y +line.points[line.points.length-1][1];
|
||||
if(startPointY >= endBindingEl.y && startPointY <= endBindingEl.y + endBindingEl.height) {
|
||||
line.points[line.points.length-1][1] = endBindingEl.y + endBindingEl.height / 2 - line.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < line.points.length - 2; i++) {
|
||||
var p1;
|
||||
var p3;
|
||||
|
||||
361
ea-scripts/Mindmap format.md
Normal file
@@ -0,0 +1,361 @@
|
||||
/*
|
||||
|
||||
format **the left to right** mind map
|
||||
|
||||

|
||||
|
||||
# tree
|
||||
|
||||
Mind map is actually a tree, so you must have a **root node**. The script will determine **the leftmost element** of the selected element as the root element (node is excalidraw element, e.g. rectangle, diamond, ellipse, text, image, but it can't be arrow, line, freedraw, **group**)
|
||||
|
||||
The element connecting node and node must be an **arrow** and have the correct direction, e.g. **parent node -> children node**
|
||||
|
||||
# sort
|
||||
|
||||
The order of nodes in the Y axis or vertical direction is determined by **the creation time** of the arrow connecting it
|
||||
|
||||

|
||||
|
||||
So if you want to readjust the order, you can **delete arrows and reconnect them**
|
||||
|
||||
# setting
|
||||
|
||||
Script provides options to adjust the style of mind map, The option is at the bottom of the option of the exalidraw plugin(e.g. Settings -> Community plugins -> Excalidraw -> drag to bottom)
|
||||
|
||||
# problem
|
||||
|
||||
1. since the start bingding and end bingding of the arrow are easily disconnected from the node, so if there are unformatted parts, please **check the connection** and use the script to **reformat**
|
||||
|
||||
```javascript
|
||||
*/
|
||||
|
||||
let settings = ea.getScriptSettings();
|
||||
//set default values on first run
|
||||
if (!settings["MindMap Format"]) {
|
||||
settings = {
|
||||
"MindMap Format": {
|
||||
value: "Excalidraw/MindMap Format",
|
||||
description:
|
||||
"This is prepared for the namespace of MindMap Format and does not need to be modified",
|
||||
},
|
||||
"default gap": {
|
||||
value: 10,
|
||||
description: "Interval size of element",
|
||||
},
|
||||
"curve length": {
|
||||
value: 40,
|
||||
description: "The length of the curve part in the mind map line",
|
||||
},
|
||||
"length between element and line": {
|
||||
value: 50,
|
||||
description:
|
||||
"The distance between the tail of the connection and the connecting elements of the mind map",
|
||||
},
|
||||
};
|
||||
ea.setScriptSettings(settings);
|
||||
}
|
||||
|
||||
// default X coordinate of the middle point of the arc
|
||||
const defaultDotX = Number(settings["curve length"].value);
|
||||
// The default length from the middle point of the arc on the X axis
|
||||
const defaultLengthWithCenterDot = Number(
|
||||
settings["length between element and line"].value
|
||||
);
|
||||
// Initial trimming distance of the end point on the Y axis
|
||||
const initAdjLength = 4;
|
||||
// default gap
|
||||
const defaultGap = Number(settings["default gap"].value);
|
||||
|
||||
const setCenter = (parent, line) => {
|
||||
// Focus and gap need the api calculation of excalidraw
|
||||
// e.g. determineFocusDistance, but they are not available now
|
||||
// so they are uniformly set to 0/1
|
||||
line.startBinding.focus = 0;
|
||||
line.startBinding.gap = 1;
|
||||
line.endBinding.focus = 0;
|
||||
line.endBinding.gap = 1;
|
||||
line.x = parent.x + parent.width;
|
||||
line.y = parent.y + parent.height / 2;
|
||||
};
|
||||
|
||||
/**
|
||||
* set the middle point of curve
|
||||
* @param {any} lineEl the line element of excalidraw
|
||||
* @param {number} height height of dot on Y axis
|
||||
* @param {number} [ratio=1] ,coefficient of the initial trimming distance of the end point on the Y axis, default is 1
|
||||
*/
|
||||
const setTopCurveDotOnLine = (lineEl, height, ratio = 1) => {
|
||||
if (lineEl.points.length < 3) {
|
||||
lineEl.points.splice(1, 0, [defaultDotX, lineEl.points[0][1] - height]);
|
||||
} else if (lineEl.points.length === 3) {
|
||||
lineEl.points[1] = [defaultDotX, lineEl.points[0][1] - height];
|
||||
} else {
|
||||
lineEl.points.splice(2, lineEl.points.length - 3);
|
||||
lineEl.points[1] = [defaultDotX, lineEl.points[0][1] - height];
|
||||
}
|
||||
lineEl.points[2][0] = lineEl.points[1][0] + defaultLengthWithCenterDot;
|
||||
// adjust the curvature of the second line segment
|
||||
lineEl.points[2][1] = lineEl.points[1][1] - initAdjLength * ratio * 0.8;
|
||||
};
|
||||
|
||||
const setMidCurveDotOnLine = (lineEl) => {
|
||||
if (lineEl.points.length < 3) {
|
||||
lineEl.points.splice(1, 0, [defaultDotX, lineEl.points[0][1]]);
|
||||
} else if (lineEl.points.length === 3) {
|
||||
lineEl.points[1] = [defaultDotX, lineEl.points[0][1]];
|
||||
} else {
|
||||
lineEl.points.splice(2, lineEl.points.length - 3);
|
||||
lineEl.points[1] = [defaultDotX, lineEl.points[0][1]];
|
||||
}
|
||||
lineEl.points[2][0] = lineEl.points[1][0] + defaultLengthWithCenterDot;
|
||||
lineEl.points[2][1] = lineEl.points[1][1];
|
||||
};
|
||||
|
||||
/**
|
||||
* set the middle point of curve
|
||||
* @param {any} lineEl the line element of excalidraw
|
||||
* @param {number} height height of dot on Y axis
|
||||
* @param {number} [ratio=1] ,coefficient of the initial trimming distance of the end point on the Y axis, default is 1
|
||||
*/
|
||||
const setBottomCurveDotOnLine = (lineEl, height, ratio = 1) => {
|
||||
if (lineEl.points.length < 3) {
|
||||
lineEl.points.splice(1, 0, [defaultDotX, lineEl.points[0][1] + height]);
|
||||
} else if (lineEl.points.length === 3) {
|
||||
lineEl.points[1] = [defaultDotX, lineEl.points[0][1] + height];
|
||||
} else {
|
||||
lineEl.points.splice(2, lineEl.points.length - 3);
|
||||
lineEl.points[1] = [defaultDotX, lineEl.points[0][1] + height];
|
||||
}
|
||||
lineEl.points[2][0] = lineEl.points[1][0] + defaultLengthWithCenterDot;
|
||||
// adjust the curvature of the second line segment
|
||||
lineEl.points[2][1] = lineEl.points[1][1] + initAdjLength * ratio * 0.8;
|
||||
};
|
||||
|
||||
const setTextXY = (rect, text) => {
|
||||
text.x = rect.x + (rect.width - text.width) / 2;
|
||||
text.y = rect.y + (rect.height - text.height) / 2;
|
||||
};
|
||||
|
||||
const setChildrenXY = (parent, children, line, elementsMap) => {
|
||||
children.x = parent.x + parent.width + line.points[2][0];
|
||||
children.y =
|
||||
parent.y + parent.height / 2 + line.points[2][1] - children.height / 2;
|
||||
if (
|
||||
["rectangle", "diamond", "ellipse"].includes(children.type) &&
|
||||
![null, undefined].includes(children.boundElements)
|
||||
) {
|
||||
const textDesc = children.boundElements.filter(
|
||||
(el) => el.type === "text"
|
||||
)[0];
|
||||
if (textDesc !== undefined) {
|
||||
const textEl = elementsMap.get(textDesc.id);
|
||||
setTextXY(children, textEl);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* returns the height of the upper part of all child nodes
|
||||
* and the height of the lower part of all child nodes
|
||||
* @param {Number[]} childrenTotalHeightArr
|
||||
* @returns {Number[]} [topHeight, bottomHeight]
|
||||
*/
|
||||
const getNodeCurrentHeight = (childrenTotalHeightArr) => {
|
||||
if (childrenTotalHeightArr.length <= 0) return [0, 0];
|
||||
else if (childrenTotalHeightArr.length === 1)
|
||||
return [childrenTotalHeightArr[0] / 2, childrenTotalHeightArr[0] / 2];
|
||||
const heightArr = childrenTotalHeightArr;
|
||||
let topHeight = 0,
|
||||
bottomHeight = 0;
|
||||
const isEven = heightArr.length % 2 === 0;
|
||||
const mid = Math.floor(heightArr.length / 2);
|
||||
const topI = mid - 1;
|
||||
const bottomI = isEven ? mid : mid + 1;
|
||||
topHeight = isEven ? 0 : heightArr[mid] / 2;
|
||||
for (let i = topI; i >= 0; i--) {
|
||||
topHeight += heightArr[i];
|
||||
}
|
||||
bottomHeight = isEven ? 0 : heightArr[mid] / 2;
|
||||
for (let i = bottomI; i < heightArr.length; i++) {
|
||||
bottomHeight += heightArr[i];
|
||||
}
|
||||
return [topHeight, bottomHeight];
|
||||
};
|
||||
|
||||
/**
|
||||
* handle the height of each point in the single-level tree
|
||||
* @param {Array} lines
|
||||
* @param {Map} elementsMap
|
||||
* @param {Boolean} isEven
|
||||
* @param {Number} mid 'lines' array midpoint index
|
||||
* @returns {Array} height array corresponding to 'lines'
|
||||
*/
|
||||
const handleDotYValue = (lines, elementsMap, isEven, mid) => {
|
||||
const getTotalHeight = (line, elementsMap) => {
|
||||
return elementsMap.get(line.endBinding.elementId).totalHeight;
|
||||
};
|
||||
const getTopHeight = (line, elementsMap) => {
|
||||
return elementsMap.get(line.endBinding.elementId).topHeight;
|
||||
};
|
||||
const getBottomHeight = (line, elementsMap) => {
|
||||
return elementsMap.get(line.endBinding.elementId).bottomHeight;
|
||||
};
|
||||
const heightArr = new Array(lines.length).fill(0);
|
||||
const upI = mid === 0 ? 0 : mid - 1;
|
||||
const bottomI = isEven ? mid : mid + 1;
|
||||
let initHeight = isEven ? 0 : getTopHeight(lines[mid], elementsMap);
|
||||
for (let i = upI; i >= 0; i--) {
|
||||
heightArr[i] = initHeight + getBottomHeight(lines[i], elementsMap);
|
||||
initHeight += getTotalHeight(lines[i], elementsMap);
|
||||
}
|
||||
initHeight = isEven ? 0 : getBottomHeight(lines[mid], elementsMap);
|
||||
for (let i = bottomI; i < lines.length; i++) {
|
||||
heightArr[i] = initHeight + getTopHeight(lines[i], elementsMap);
|
||||
initHeight += getTotalHeight(lines[i], elementsMap);
|
||||
}
|
||||
return heightArr;
|
||||
};
|
||||
|
||||
/**
|
||||
* format single-level tree
|
||||
* @param {any} parent
|
||||
* @param {Array} lines
|
||||
* @param {Map} childrenDescMap
|
||||
* @param {Map} elementsMap
|
||||
*/
|
||||
const formatTree = (parent, lines, childrenDescMap, elementsMap) => {
|
||||
lines.forEach((item) => setCenter(parent, item));
|
||||
|
||||
const isEven = lines.length % 2 === 0;
|
||||
const mid = Math.floor(lines.length / 2);
|
||||
const heightArr = handleDotYValue(lines, childrenDescMap, isEven, mid);
|
||||
lines.forEach((item, index) => {
|
||||
if (isEven) {
|
||||
if (index < mid) setTopCurveDotOnLine(item, heightArr[index], index + 1);
|
||||
else setBottomCurveDotOnLine(item, heightArr[index], index - mid + 1);
|
||||
} else {
|
||||
if (index < mid) setTopCurveDotOnLine(item, heightArr[index], index + 1);
|
||||
else if (index === mid) setMidCurveDotOnLine(item);
|
||||
else setBottomCurveDotOnLine(item, heightArr[index], index - mid);
|
||||
}
|
||||
});
|
||||
lines.forEach((item) => {
|
||||
if (item.endBinding !== null) {
|
||||
setChildrenXY(
|
||||
parent,
|
||||
elementsMap.get(item.endBinding.elementId),
|
||||
item,
|
||||
elementsMap
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const generateTree = (elements) => {
|
||||
const elIdMap = new Map([[elements[0].id, elements[0]]]);
|
||||
let minXEl = elements[0];
|
||||
for (let i = 1; i < elements.length; i++) {
|
||||
elIdMap.set(elements[i].id, elements[i]);
|
||||
if (
|
||||
!(elements[i].type === "arrow" || elements[i].type === "line") &&
|
||||
elements[i].x < minXEl.x
|
||||
) {
|
||||
minXEl = elements[i];
|
||||
}
|
||||
}
|
||||
const root = {
|
||||
el: minXEl,
|
||||
totalHeight: minXEl.height,
|
||||
topHeight: 0,
|
||||
bottomHeight: 0,
|
||||
linkChildrensLines: [],
|
||||
isLeafNode: false,
|
||||
children: [],
|
||||
};
|
||||
const preIdSet = new Set(); // The id_set of Elements that is already in the tree, avoid a dead cycle
|
||||
const dfsForTreeData = (root) => {
|
||||
if (preIdSet.has(root.el.id)) {
|
||||
return 0;
|
||||
}
|
||||
preIdSet.add(root.el.id);
|
||||
let lines = root.el.boundElements.filter(
|
||||
(el) =>
|
||||
el.type === "arrow" &&
|
||||
!preIdSet.has(el.id) &&
|
||||
elIdMap.get(el.id)?.startBinding?.elementId === root.el.id
|
||||
);
|
||||
if (lines.length === 0) {
|
||||
root.isLeafNode = true;
|
||||
root.totalHeight = root.el.height + 2 * defaultGap;
|
||||
[root.topHeight, root.bottomHeight] = [
|
||||
root.totalHeight / 2,
|
||||
root.totalHeight / 2,
|
||||
];
|
||||
return root.totalHeight;
|
||||
} else {
|
||||
lines = lines.map((elementDesc) => {
|
||||
preIdSet.add(elementDesc.id);
|
||||
return elIdMap.get(elementDesc.id);
|
||||
});
|
||||
}
|
||||
|
||||
const linkChildrensLines = [];
|
||||
lines.forEach((el) => {
|
||||
const line = el;
|
||||
if (
|
||||
line &&
|
||||
line.endBinding !== null &&
|
||||
line.endBinding !== undefined &&
|
||||
!preIdSet.has(elIdMap.get(line.endBinding.elementId).id)
|
||||
) {
|
||||
const children = elIdMap.get(line.endBinding.elementId);
|
||||
linkChildrensLines.push(line);
|
||||
root.children.push({
|
||||
el: children,
|
||||
totalHeight: 0,
|
||||
topHeight: 0,
|
||||
bottomHeight: 0,
|
||||
linkChildrensLines: [],
|
||||
isLeafNode: false,
|
||||
children: [],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let totalHeight = 0;
|
||||
root.children.forEach((el) => (totalHeight += dfsForTreeData(el)));
|
||||
|
||||
root.linkChildrensLines = linkChildrensLines;
|
||||
if (root.children.length === 0) {
|
||||
root.isLeafNode = true;
|
||||
root.totalHeight = root.el.height + 2 * defaultGap;
|
||||
[root.topHeight, root.bottomHeight] = [
|
||||
root.totalHeight / 2,
|
||||
root.totalHeight / 2,
|
||||
];
|
||||
} else if (root.children.length > 0) {
|
||||
root.totalHeight = Math.max(root.el.height + 2 * defaultGap, totalHeight);
|
||||
[root.topHeight, root.bottomHeight] = getNodeCurrentHeight(
|
||||
root.children.map((item) => item.totalHeight)
|
||||
);
|
||||
}
|
||||
|
||||
return totalHeight;
|
||||
};
|
||||
dfsForTreeData(root);
|
||||
const dfsForFormat = (root) => {
|
||||
if (root.isLeafNode) return;
|
||||
const childrenDescMap = new Map(
|
||||
root.children.map((item) => [item.el.id, item])
|
||||
);
|
||||
formatTree(root.el, root.linkChildrensLines, childrenDescMap, elIdMap);
|
||||
root.children.forEach((el) => dfsForFormat(el));
|
||||
};
|
||||
dfsForFormat(root);
|
||||
};
|
||||
|
||||
const elements = ea.getViewSelectedElements();
|
||||
generateTree(elements);
|
||||
|
||||
ea.copyViewElementsToEAforEditing(elements);
|
||||
await ea.addElementsToView(false, false);
|
||||
1
ea-scripts/Mindmap format.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1673428425027" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1642" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24"><path d="M388.7 542.88c-16.57 0-30-13.43-30-30s13.43-30 30-30c52.3 0 94.85-42.55 94.85-94.85v-67.81c0-40.96 15.84-79.58 44.6-108.74 28.76-29.16 67.16-45.53 108.12-46.1l3.43-0.05c16.57-0.22 30.18 13.02 30.41 29.58 0.23 16.57-13.02 30.18-29.58 30.41l-3.43 0.05c-51.58 0.71-93.55 43.25-93.55 94.84v67.81c0 85.4-69.47 154.86-154.85 154.86z" fill="#000000" p-id="1643"></path><path d="M640.12 860.42h-0.42l-3.43-0.05c-40.96-0.56-79.36-16.93-108.12-46.09s-44.6-67.78-44.6-108.74v-67.8c0-52.3-42.55-94.85-94.85-94.85-16.57 0-30-13.43-30-30s13.43-30 30-30c85.38 0 154.85 69.47 154.85 154.85v67.8c0 51.59 41.96 94.13 93.55 94.84l3.43 0.05c16.57 0.23 29.81 13.84 29.59 30.41-0.24 16.42-13.62 29.58-30 29.58z" fill="#000000" p-id="1644"></path><path d="M640.11 542.88H388.7c-16.57 0-30-13.43-30-30s13.43-30 30-30h251.42c16.57 0 30 13.43 30 30-0.01 16.57-13.44 30-30.01 30z" fill="#000000" p-id="1645"></path><path d="M343.89 638.95H137.78c-38.6 0-70-31.4-70-70V456.81c0-38.6 31.4-70 70-70h206.11c38.6 0 70 31.4 70 70v112.13c0 38.6-31.4 70.01-70 70.01zM137.78 446.81c-5.51 0-10 4.49-10 10v112.13c0 5.51 4.49 10 10 10h206.11c5.51 0 10-4.49 10-10V456.81c0-5.51-4.49-10-10-10H137.78zM830.16 316.96h-93.98c-69.51 0-126.07-56.55-126.07-126.07S666.66 64.83 736.18 64.83h93.98c69.51 0 126.07 56.55 126.07 126.07-0.01 69.5-56.56 126.06-126.07 126.06z m-93.98-192.13c-36.43 0-66.07 29.64-66.07 66.07s29.64 66.07 66.07 66.07h93.98c36.43 0 66.07-29.64 66.07-66.07s-29.64-66.07-66.07-66.07h-93.98zM830.16 638.95h-93.98c-69.51 0-126.07-56.55-126.07-126.07 0-69.51 56.55-126.07 126.07-126.07h93.98c69.51 0 126.07 56.55 126.07 126.07-0.01 69.51-56.56 126.07-126.07 126.07z m-93.98-192.14c-36.43 0-66.07 29.64-66.07 66.07 0 36.43 29.64 66.07 66.07 66.07h93.98c36.43 0 66.07-29.64 66.07-66.07 0-36.43-29.64-66.07-66.07-66.07h-93.98z" fill="#000000" p-id="1646"></path><path d="M830.16 959.17h-93.98c-69.51 0-126.07-56.55-126.07-126.07s56.55-126.07 126.07-126.07h93.98c69.51 0 126.07 56.55 126.07 126.07s-56.56 126.07-126.07 126.07z m-93.98-192.13c-36.43 0-66.07 29.64-66.07 66.07s29.64 66.07 66.07 66.07h93.98c36.43 0 66.07-29.64 66.07-66.07s-29.64-66.07-66.07-66.07h-93.98z" fill="#000000" p-id="1647"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -26,8 +26,7 @@ if(!settings["Gap"]) {
|
||||
let gapValue = settings["Gap"].value;
|
||||
|
||||
const selectedIndividualArrows = ea.getMaximumGroups(ea.getViewSelectedElements())
|
||||
.reduce((result, group) => (group.length === 1 && (group[0].type === 'arrow' || group[0].type === 'line')) ?
|
||||
[...result, group[0]] : result, []);
|
||||
.reduce((result, g) => [...result, ...g.filter(el => el.type === 'arrow')], []);
|
||||
|
||||
const allElements = ea.getViewElements();
|
||||
for(const arrow of selectedIndividualArrows) {
|
||||
|
||||
@@ -5,7 +5,15 @@ Converts selected freedraw lines such that pencil pressure will decrease from ma
|
||||
|
||||
```javascript
|
||||
*/
|
||||
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.8.8")) {
|
||||
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
|
||||
return;
|
||||
}
|
||||
|
||||
let elements = ea.getViewSelectedElements().filter((el)=>["freedraw","line","arrow"].includes(el.type));
|
||||
|
||||
//if nothing is selected find the last element that was drawn and use it if it is the right element type
|
||||
if(elements.length === 0) {
|
||||
elements = ea.getViewSelectedElements();
|
||||
const len = elements.length;
|
||||
@@ -14,14 +22,59 @@ if(elements.length === 0) {
|
||||
}
|
||||
elements = [elements[len]];
|
||||
}
|
||||
elements.forEach((el)=>{
|
||||
|
||||
const lineType = await utils.suggester(["Thick to thin", "Thin to thick to thin"],["l1","l2"],"Select the type of line");
|
||||
if(!lineType) return;
|
||||
|
||||
ea.copyViewElementsToEAforEditing(elements);
|
||||
|
||||
ea.getElements().forEach((el)=>{
|
||||
el.simulatePressure = false;
|
||||
el.type = "freedraw";
|
||||
el.pressures = [];
|
||||
const len = el.points.length;
|
||||
for(i=0;i<len;i++)
|
||||
el.pressures.push((len-i)/len);
|
||||
el.pressures = Array(el.points.length).fill(1);
|
||||
el.customData = {
|
||||
strokeOptions: {
|
||||
... lineType === "l1"
|
||||
? {
|
||||
options: {
|
||||
thinning: 1,
|
||||
smoothing: 0.5,
|
||||
streamline: 0.5,
|
||||
easing: "linear",
|
||||
start: {
|
||||
taper: 0,
|
||||
cap: true
|
||||
},
|
||||
end: {
|
||||
taper: true,
|
||||
easing: "linear",
|
||||
cap: false
|
||||
}
|
||||
}
|
||||
}
|
||||
: {
|
||||
options: {
|
||||
thinning: 4,
|
||||
smoothing: 0.5,
|
||||
streamline: 0.5,
|
||||
easing: "linear",
|
||||
start: {
|
||||
taper: true,
|
||||
easing: "linear",
|
||||
cap: true
|
||||
},
|
||||
end: {
|
||||
taper: true,
|
||||
easing: "linear",
|
||||
cap: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
ea.copyViewElementsToEAforEditing(elements);
|
||||
await ea.addElementsToView(false,false);
|
||||
elements.forEach((el)=>ea.moveViewElementToZIndex(el.id,0));
|
||||
|
||||
await ea.addElementsToView(false,true);
|
||||
elements.forEach((el)=>ea.moveViewElementToZIndex(el.id,0));
|
||||
const ids=ea.getElements().map(el=>el.id);
|
||||
ea.selectElementsInView(ea.getViewElements().filter(el=>ids.contains(el.id)));
|
||||
@@ -8,11 +8,17 @@ https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.h
|
||||
|
||||
```javascript
|
||||
*/
|
||||
const grid = parseInt(await utils.inputPrompt("Grid size?",null,"20"));
|
||||
if(isNaN(grid)) return; //this is to avoid passing an illegal value to Excalidraw
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.8.11")) {
|
||||
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
|
||||
return;
|
||||
}
|
||||
|
||||
const api = ea.getExcalidrawAPI();
|
||||
let appState = api.getAppState();
|
||||
const grid = parseInt(await utils.inputPrompt("Grid size?",null,appState.previousGridSize?.toString()??"20"));
|
||||
if(isNaN(grid)) return; //this is to avoid passing an illegal value to Excalidraw
|
||||
appState.gridSize = grid;
|
||||
appState.previousGridSize = grid;
|
||||
api.updateScene({
|
||||
appState,
|
||||
commitToHistory:false
|
||||
|
||||
488
ea-scripts/Slideshow.md
Normal file
@@ -0,0 +1,488 @@
|
||||
/*
|
||||

|
||||

|
||||
The script will convert your drawing into a slideshow presentation.
|
||||
|
||||
```javascript
|
||||
*/
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.8.17")) {
|
||||
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
|
||||
return;
|
||||
}
|
||||
|
||||
//constants
|
||||
const STEPCOUNT = 100;
|
||||
const FRAME_SLEEP = 1; //milliseconds
|
||||
const EDIT_ZOOMOUT = 0.7; //70% of original slide zoom, set to a value between 1 and 0
|
||||
|
||||
//utility & convenience functions
|
||||
const doc = ea.targetView.ownerDocument;
|
||||
const win = ea.targetView.ownerWindow;
|
||||
const api = ea.getExcalidrawAPI();
|
||||
const contentEl = ea.targetView.contentEl;
|
||||
const sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
//clean up potential clutter from previous run
|
||||
window.removePresentationEventHandlers?.();
|
||||
|
||||
//check if line or arrow is selected, if not inform the user and terminate presentation
|
||||
let lineEl = ea.getViewElements().filter(el=>["line","arrow"].contains(el.type) && el.customData?.slideshow)[0];
|
||||
const selectedEl = ea.getViewSelectedElement();
|
||||
let preventHideAction = false;
|
||||
if(lineEl && selectedEl && ["line","arrow"].contains(selectedEl.type)) {
|
||||
api.setToast({
|
||||
message:"Using selected line instead of hidden line. Note that there is a hidden presentation path for this drawing. Run the slideshow script without selecting any elements to access the hidden presentation path",
|
||||
duration: 5000,
|
||||
closable: true
|
||||
})
|
||||
preventHideAction = true;
|
||||
lineEl = selectedEl;
|
||||
}
|
||||
if(!lineEl) lineEl = selectedEl;
|
||||
if(!lineEl || !["line","arrow"].contains(lineEl.type)) {
|
||||
api.setToast({
|
||||
message:"Please select the line or arrow for the presentation path",
|
||||
duration: 3000,
|
||||
closable: true
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
//goto fullscreen
|
||||
const gotoFullscreen = async () => {
|
||||
if(app.isMobile) {
|
||||
ea.viewToggleFullScreen(true);
|
||||
} else {
|
||||
await contentEl.webkitRequestFullscreen();
|
||||
await sleep(500);
|
||||
ea.setViewModeEnabled(true);
|
||||
}
|
||||
const deltaWidth = () => contentEl.clientWidth-api.getAppState().width;
|
||||
let watchdog = 0;
|
||||
while (deltaWidth()>50 && watchdog++<20) await sleep(100); //wait for Excalidraw to resize to fullscreen
|
||||
contentEl.querySelector(".layer-ui__wrapper").addClass("excalidraw-hidden");
|
||||
}
|
||||
|
||||
//hide the arrow and save the arrow color before doing so
|
||||
const originalProps = lineEl.customData?.slideshow?.hidden
|
||||
? lineEl.customData.slideshow.originalProps
|
||||
: {
|
||||
strokeColor: lineEl.strokeColor,
|
||||
backgroundColor: lineEl.backgroundColor,
|
||||
locked: lineEl.locked,
|
||||
};
|
||||
let hidden = lineEl.customData?.slideshow?.hidden ?? false;
|
||||
|
||||
const hideArrow = async (setToHidden) => {
|
||||
ea.clear();
|
||||
ea.copyViewElementsToEAforEditing(ea.getViewElements().filter(el=>el.id === lineEl.id));
|
||||
const el = ea.getElement(lineEl.id);
|
||||
el.strokeColor = "transparent";
|
||||
el.backgroundColor = "transparent";
|
||||
const customData = el.customData;
|
||||
if(setToHidden && !preventHideAction) {
|
||||
el.locked = true;
|
||||
el.customData = {
|
||||
...customData,
|
||||
slideshow: {
|
||||
originalProps,
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
hidden = true;
|
||||
} else {
|
||||
if(customData) delete el.customData.slideshow;
|
||||
hidden = false;
|
||||
}
|
||||
await ea.addElementsToView();
|
||||
}
|
||||
|
||||
//----------------------------
|
||||
//scroll-to-location functions
|
||||
//----------------------------
|
||||
let slide = -1;
|
||||
const slideCount = Math.floor(lineEl.points.length/2)-1;
|
||||
|
||||
const getNextSlide = (forward) => {
|
||||
slide = forward
|
||||
? slide < slideCount ? slide + 1 : 0
|
||||
: slide <= 0 ? slideCount : slide - 1;
|
||||
return {
|
||||
pointA:lineEl.points[slide*2],
|
||||
pointB:lineEl.points[slide*2+1]
|
||||
}
|
||||
}
|
||||
|
||||
const getSlideRect = ({pointA, pointB}) => {
|
||||
const {width, height} = api.getAppState();
|
||||
const x1 = lineEl.x+pointA[0];
|
||||
const y1 = lineEl.y+pointA[1];
|
||||
const x2 = lineEl.x+pointB[0];
|
||||
const y2 = lineEl.y+pointB[1];
|
||||
const ratioX = width/Math.abs(x1-x2);
|
||||
const ratioY = height/Math.abs(y1-y2);
|
||||
let ratio = ratioX<ratioY?ratioX:ratioY;
|
||||
if (ratio < 0.1) ratio = 0.1;
|
||||
if (ratio > 10) ratio = 10;
|
||||
const deltaX = (ratio===ratioY)?(width/ratio - Math.abs(x1-x2))/2:0;
|
||||
const deltaY = (ratio===ratioX)?(height/ratio - Math.abs(y1-y2))/2:0;
|
||||
return {
|
||||
left: (x1<x2?x1:x2)-deltaX,
|
||||
top: (y1<y2?y1:y2)-deltaY,
|
||||
right: (x1<x2?x2:x1)+deltaX,
|
||||
bottom: (y1<y2?y2:y1)+deltaY,
|
||||
nextZoom: ratio
|
||||
};
|
||||
}
|
||||
|
||||
let busy = false;
|
||||
const scrollToNextRect = async ({left,top,right,bottom,nextZoom},steps = STEPCOUNT) => {
|
||||
let watchdog = 0;
|
||||
while(busy && watchdog++<15) await(100);
|
||||
if(busy && watchdog >= 15) return;
|
||||
busy = true;
|
||||
api.updateScene({appState:{shouldCacheIgnoreZoom:true}});
|
||||
const {scrollX, scrollY, zoom} = api.getAppState();
|
||||
const zoomStep = (zoom.value-nextZoom)/steps;
|
||||
const xStep = (left+scrollX)/steps;
|
||||
const yStep = (top+scrollY)/steps;
|
||||
for(i=1;i<=steps;i++) {
|
||||
api.updateScene({
|
||||
appState: {
|
||||
scrollX:scrollX-(xStep*i),
|
||||
scrollY:scrollY-(yStep*i),
|
||||
zoom:{value:zoom.value-zoomStep*i},
|
||||
}
|
||||
});
|
||||
await sleep(FRAME_SLEEP);
|
||||
}
|
||||
api.updateScene({appState:{shouldCacheIgnoreZoom:false}});
|
||||
busy = false;
|
||||
}
|
||||
|
||||
const navigate = async (dir) => {
|
||||
const forward = dir === "fwd";
|
||||
const prevSlide = slide;
|
||||
const nextSlide = getNextSlide(forward);
|
||||
|
||||
//exit if user navigates from last slide forward or first slide backward
|
||||
const shouldExit = forward
|
||||
? slide<=prevSlide
|
||||
: slide>=prevSlide;
|
||||
if(shouldExit) {
|
||||
exitPresentation();
|
||||
return;
|
||||
}
|
||||
if(slideNumberEl) slideNumberEl.innerText = `${slide+1}/${slideCount+1}`;
|
||||
const nextRect = getSlideRect(nextSlide);
|
||||
await scrollToNextRect(nextRect);
|
||||
if(settingsModal) {
|
||||
slideNumberDropdown.setValue(`${slide}`.padStart(3,"0"));
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------
|
||||
// Settings Modal
|
||||
//--------------------------
|
||||
let settingsModal;
|
||||
let slideNumberDropdown;
|
||||
const presentationSettings = () => {
|
||||
let dirty = false;
|
||||
settingsModal = new ea.obsidian.Modal(app);
|
||||
|
||||
const getSlideNumberLabel = (i) => {
|
||||
switch(i) {
|
||||
case 0: return "1 - Start";
|
||||
case slideCount: return `${i+1} - End`;
|
||||
default: return `${i+1}`;
|
||||
}
|
||||
}
|
||||
|
||||
const getSlidesList = () => {
|
||||
const options = {};
|
||||
for(i=0;i<=slideCount;i++) {
|
||||
options[`${i}`.padStart(3,"0")] = getSlideNumberLabel(i);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
settingsModal.onOpen = () => {
|
||||
settingsModal.contentEl.createEl("h1",{text: "Slideshow Actions"});
|
||||
settingsModal.contentEl.createEl("p",{text: "To open this window double click presentation script icon or press ENTER during presentation."});
|
||||
new ea.obsidian.Setting(settingsModal.contentEl)
|
||||
.setName("Jump to slide")
|
||||
.addDropdown(dropdown => {
|
||||
slideNumberDropdown = dropdown;
|
||||
dropdown
|
||||
.addOptions(getSlidesList())
|
||||
.setValue(`${slide}`.padStart(3,"0"))
|
||||
.onChange(value => {
|
||||
slide = parseInt(value)-1;
|
||||
navigate("fwd");
|
||||
})
|
||||
})
|
||||
|
||||
if(!preventHideAction) {
|
||||
new ea.obsidian.Setting(settingsModal.contentEl)
|
||||
.setName("Hide navigation arrow after slideshow")
|
||||
.setDesc("Toggle on: arrow hidden, toggle off: arrow visible")
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(hidden)
|
||||
.onChange(value => hideArrow(value))
|
||||
)
|
||||
}
|
||||
|
||||
new ea.obsidian.Setting(settingsModal.contentEl)
|
||||
.setName("Edit current slide")
|
||||
.setDesc("Pressing 'e' during the presentation will open the current slide for editing.")
|
||||
.addButton(button => button
|
||||
.setButtonText("Edit")
|
||||
.onClick(async ()=>{
|
||||
await hideArrow(false);
|
||||
exitPresentation(true);
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
settingsModal.onClose = () => {
|
||||
setTimeout(()=>delete settingsModal);
|
||||
}
|
||||
|
||||
settingsModal.open();
|
||||
contentEl.appendChild(settingsModal.containerEl);
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
//Slideshow control
|
||||
//--------------------------------------
|
||||
let controlPanelEl;
|
||||
let slideNumberEl;
|
||||
const createNavigationPanel = () => {
|
||||
//create slideshow controlpanel container
|
||||
const top = contentEl.innerHeight;
|
||||
const left = contentEl.innerWidth;
|
||||
controlPanelEl = contentEl.createDiv({
|
||||
cls: ["excalidraw","excalidraw-presentation-panel"],
|
||||
attr: {
|
||||
style: `
|
||||
width: calc(var(--default-button-size)*3);
|
||||
z-index:5;
|
||||
position: absolute;
|
||||
top:calc(${top}px - var(--default-button-size)*2);
|
||||
left:calc(${left}px - var(--default-button-size)*3.5);`
|
||||
}
|
||||
});
|
||||
const panelColumn = controlPanelEl.createDiv({
|
||||
cls: "panelColumn",
|
||||
});
|
||||
panelColumn.createDiv({
|
||||
cls: ["Island", "buttonList"],
|
||||
attr: {
|
||||
style: `
|
||||
height: calc(var(--default-button-size)*1.5);
|
||||
width: 100%;
|
||||
background: var(--island-bg-color);`,
|
||||
}
|
||||
}, el=>{
|
||||
el.createEl("button",{
|
||||
text: "<",
|
||||
attr: {
|
||||
style: `
|
||||
margin-top: calc(var(--default-button-size)*0.25);
|
||||
margin-left: calc(var(--default-button-size)*0.25);`
|
||||
}
|
||||
}, button => button .onclick = () => navigate("bkwd"));
|
||||
el.createEl("button",{
|
||||
text: ">",
|
||||
attr: {
|
||||
style: `
|
||||
margin-top: calc(var(--default-button-size)*0.25);
|
||||
margin-right: calc(var(--default-button-size)*0.25);`
|
||||
}
|
||||
}, button => button.onclick = () => navigate("fwd"));
|
||||
slideNumberEl = el.createEl("span",{
|
||||
text: "1",
|
||||
cls: ["ToolIcon__keybinding"],
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
//keyboard navigation
|
||||
const keydownListener = (e) => {
|
||||
e.preventDefault();
|
||||
switch(e.key) {
|
||||
case "escape":
|
||||
if(app.isMobile) exitPresentation();
|
||||
break;
|
||||
case "ArrowRight":
|
||||
case "ArrowDown":
|
||||
navigate("fwd");
|
||||
break;
|
||||
case "ArrowLeft":
|
||||
case "ArrowUp":
|
||||
navigate("bkwd");
|
||||
break;
|
||||
case "Enter":
|
||||
presentationSettings();
|
||||
break;
|
||||
case "End":
|
||||
slide = slideCount - 1;
|
||||
navigate("fwd");
|
||||
break;
|
||||
case "Home":
|
||||
slide = -1;
|
||||
navigate("fwd");
|
||||
break;
|
||||
case "e":
|
||||
(async ()=>{
|
||||
await hideArrow(false);
|
||||
exitPresentation(true);
|
||||
})()
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//slideshow panel drag
|
||||
let pos1 = pos2 = pos3 = pos4 = 0;
|
||||
|
||||
const updatePosition = (deltaY = 0, deltaX = 0) => {
|
||||
const {
|
||||
offsetTop,
|
||||
offsetLeft,
|
||||
clientWidth: width,
|
||||
clientHeight: height,
|
||||
} = controlPanelEl;
|
||||
controlPanelEl.style.top = (offsetTop - deltaY) + 'px';
|
||||
controlPanelEl.style.left = (offsetLeft - deltaX) + 'px';
|
||||
}
|
||||
|
||||
const pointerUp = () => {
|
||||
win.removeEventListener('pointermove', onDrag, true);
|
||||
}
|
||||
|
||||
let dblClickTimer = 0;
|
||||
const pointerDown = (e) => {
|
||||
const now = Date.now();
|
||||
pos3 = e.clientX;
|
||||
pos4 = e.clientY;
|
||||
win.addEventListener('pointermove', onDrag, true);
|
||||
if(now-dblClickTimer < 400) {
|
||||
presentationSettings();
|
||||
}
|
||||
dblClickTimer = now;
|
||||
}
|
||||
|
||||
const onDrag = (e) => {
|
||||
e.preventDefault();
|
||||
pos1 = pos3 - e.clientX;
|
||||
pos2 = pos4 - e.clientY;
|
||||
pos3 = e.clientX;
|
||||
pos4 = e.clientY;
|
||||
updatePosition(pos2, pos1);
|
||||
}
|
||||
|
||||
const initializeEventListners = () => {
|
||||
doc.addEventListener('keydown',keydownListener);
|
||||
controlPanelEl.addEventListener('pointerdown', pointerDown, false);
|
||||
win.addEventListener('pointerup', pointerUp, false);
|
||||
|
||||
//event listners for terminating the presentation
|
||||
window.removePresentationEventHandlers = () => {
|
||||
ea.onLinkClickHook = null;
|
||||
controlPanelEl.parentElement?.removeChild(controlPanelEl);
|
||||
if(!app.isMobile) win.removeEventListener('fullscreenchange', fullscreenListener);
|
||||
doc.removeEventListener('keydown',keydownListener);
|
||||
win.removeEventListener('pointerup',pointerUp);
|
||||
contentEl.querySelector(".layer-ui__wrapper")?.removeClass("excalidraw-hidden");
|
||||
delete window.removePresentationEventHandlers;
|
||||
}
|
||||
|
||||
ea.onLinkClickHook = () => {
|
||||
exitPresentation();
|
||||
return true;
|
||||
};
|
||||
|
||||
if(!app.isMobile) {
|
||||
win.addEventListener('fullscreenchange', fullscreenListener);
|
||||
}
|
||||
}
|
||||
|
||||
const exitPresentation = async (openForEdit = false) => {
|
||||
if(openForEdit) ea.targetView.preventAutozoom();
|
||||
if(!app.isMobile) await doc.exitFullscreen();
|
||||
if(app.isMobile) {
|
||||
ea.viewToggleFullScreen(true);
|
||||
} else {
|
||||
ea.setViewModeEnabled(false);
|
||||
}
|
||||
if(settingsModal) settingsModal.close();
|
||||
ea.clear();
|
||||
ea.copyViewElementsToEAforEditing(ea.getViewElements().filter(el=>el.id === lineEl.id));
|
||||
const el = ea.getElement(lineEl.id);
|
||||
if(!hidden) {
|
||||
el.strokeColor = originalProps.strokeColor;
|
||||
el.backgroundProps = originalProps.backgroundColor;
|
||||
el.locked = openForEdit ? false : originalProps.locked;
|
||||
}
|
||||
await ea.addElementsToView();
|
||||
ea.selectElementsInView([el]);
|
||||
if(openForEdit) {
|
||||
const nextSlide = getNextSlide(--slide);
|
||||
let nextRect = getSlideRect(nextSlide);
|
||||
const offsetW = (nextRect.right-nextRect.left)*(1-EDIT_ZOOMOUT)/2;
|
||||
const offsetH = (nextRect.bottom-nextRect.top)*(1-EDIT_ZOOMOUT)/2
|
||||
nextRect = {
|
||||
left: nextRect.left-offsetW,
|
||||
right: nextRect.right+offsetW,
|
||||
top: nextRect.top-offsetH,
|
||||
bottom: nextRect.bottom+offsetH,
|
||||
nextZoom: nextRect.nextZoom*EDIT_ZOOMOUT > 0.1 ? nextRect.nextZoom*EDIT_ZOOMOUT : 0.1 //0.1 is the minimu zoom value
|
||||
};
|
||||
await scrollToNextRect(nextRect,1);
|
||||
api.startLineEditor(
|
||||
ea.getViewSelectedElement(),
|
||||
[slide*2,slide*2+1]
|
||||
);
|
||||
}
|
||||
window.removePresentationEventHandlers?.();
|
||||
setTimeout(()=>{
|
||||
//Resets pointer offsets. Ugly solution.
|
||||
//During testing offsets were wrong after presentation, but don't know why.
|
||||
//This should solve it even if they are wrong.
|
||||
ea.targetView.refresh();
|
||||
})
|
||||
}
|
||||
|
||||
const fullscreenListener = (e) => {
|
||||
e.preventDefault();
|
||||
exitPresentation();
|
||||
}
|
||||
|
||||
|
||||
//--------------------------
|
||||
// Start presentation or open presentation settings on double click
|
||||
//--------------------------
|
||||
const start = async () => {
|
||||
await gotoFullscreen();
|
||||
await hideArrow(hidden);
|
||||
createNavigationPanel();
|
||||
initializeEventListners();
|
||||
//navigate to the first slide on start
|
||||
setTimeout(()=>navigate("fwd"));
|
||||
}
|
||||
|
||||
const timestamp = Date.now();
|
||||
if(window.ExcalidrawSlideshow && (window.ExcalidrawSlideshow.script === utils.scriptFile.path) && (timestamp - window.ExcalidrawSlideshow.timestamp <400) ) {
|
||||
if(window.ExcalidrawSlideshowStartTimer) {
|
||||
clearTimeout(window.ExcalidrawSlideshowStartTimer);
|
||||
delete window.ExcalidrawSlideshowStartTimer;
|
||||
}
|
||||
await start();
|
||||
presentationSettings();
|
||||
} else {
|
||||
window.ExcalidrawSlideshow = {
|
||||
script: utils.scriptFile.path,
|
||||
timestamp
|
||||
};
|
||||
window.ExcalidrawSlideshowStartTimer = setTimeout(start,500);
|
||||
}
|
||||
1
ea-scripts/Slideshow.svg
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" stroke="#000" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><g fill="none"><circle cx="12" cy="12" r="10"></circle><path d="M17 12h.01"></path><path d="M12 12h.01"></path><path d="M7 12h.01"></path></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" stroke="#000" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><g stroke-width="2" fill="none"><circle cx="12" cy="12" r="10"></circle><path d="M17 12h.01"></path><path d="M12 12h.01"></path><path d="M7 12h.01"></path></g></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 309 B After Width: | Height: | Size: 327 B |
52
ea-scripts/Uniform size.md
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
|
||||

|
||||
|
||||
The script will standardize the sizes of rectangles, diamonds and ellipses adjusting all the elements to match the largest width and height within the group.
|
||||
|
||||
```javascript
|
||||
*/
|
||||
const boxShapesDispaly=["○ ellipse","□ rectangle","◇ diamond"];
|
||||
const boxShapes=["ellipse","rectangle","diamond"];
|
||||
|
||||
let editedElements = [];
|
||||
|
||||
const elements = ea.getViewSelectedElements().filter(el=>boxShapes.contains(el.type));
|
||||
if(elements.length===0) {
|
||||
new Notice("No rectangle, or diamond or ellipse elements are selected. Please select some elements");
|
||||
return;
|
||||
}
|
||||
|
||||
const typeSet = new Set();
|
||||
elements.forEach(el=>typeSet.add(el.type));
|
||||
|
||||
const elementType = await utils.suggester(
|
||||
Array.from(typeSet).map((item) => {
|
||||
switch(item) {
|
||||
case "ellipse": return "○ ellipse";
|
||||
case "rectangle": return "□ rectangle";
|
||||
case "diamond": return "◇ diamond";
|
||||
default: return item;
|
||||
}
|
||||
}),
|
||||
Array.from(typeSet),
|
||||
"Select element types to resize"
|
||||
);
|
||||
|
||||
if(!elementType) return;
|
||||
|
||||
ea.copyViewElementsToEAforEditing(elements.filter(el=>el.type===elementType));
|
||||
let width = height = 0;
|
||||
ea.getElements().forEach(el=>{
|
||||
if(el.width>width) width = el.width;
|
||||
if(el.height>height) height = el.height;
|
||||
})
|
||||
|
||||
ea.getElements().forEach(el=>{
|
||||
el.width = width;
|
||||
el.height = height;
|
||||
})
|
||||
|
||||
const ids = ea.getElements().map(el=>el.id);
|
||||
await ea.addElementsToView(false,true);
|
||||
ea.getExcalidrawAPI().updateContainerSize(ea.getViewElements().filter(el=>ids.contains(el.id)));
|
||||
1
ea-scripts/Uniform size.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M40 352c-22.1 0-40 17.9-40 40l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48c0-22.1-17.9-40-40-40l-48 0zm192 0c-22.1 0-40 17.9-40 40l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48c0-22.1-17.9-40-40-40l-48 0zM40 320l48 0c22.1 0 40-17.9 40-40l0-48c0-22.1-17.9-40-40-40l-48 0c-22.1 0-40 17.9-40 40l0 48c0 22.1 17.9 40 40 40zM232 192c-22.1 0-40 17.9-40 40l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48c0-22.1-17.9-40-40-40l-48 0zM40 160l48 0c22.1 0 40-17.9 40-40l0-48c0-22.1-17.9-40-40-40L40 32C17.9 32 0 49.9 0 72l0 48c0 22.1 17.9 40 40 40zM232 32c-22.1 0-40 17.9-40 40l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48c0-22.1-17.9-40-40-40l-48 0z"/></svg>
|
||||
|
After Width: | Height: | Size: 930 B |
@@ -31,6 +31,8 @@ I would love to include your contribution in the script library. If you have a s
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Link%20to%20Existing%20File%20and%20Open.svg"/></div>|[[#Add Link to Existing File and Open]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Link%20to%20New%20Page%20and%20Open.svg"/></div>|[[#Add Link to New Page and Open]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Add%20Next%20Step%20in%20Process.svg"/></div>|[[#Add Next Step in Process]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Alternative%20Pens.svg"/></div>|[[#Alternative Pens]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Auto%20Layout.svg"/></div>|[[#Auto Layout]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Box%20Each%20Selected%20Groups.svg"/></div>|[[#Box Each Selected Groups]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Box%20Selected%20Elements.svg"/></div>|[[#Box Selected Elements]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Change%20shape%20of%20selected%20elements.svg"/></div>|[[#Change shape of selected elements]]|
|
||||
@@ -41,6 +43,7 @@ I would love to include your contribution in the script library. If you have a s
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Copy%20Selected%20Element%20Styles%20to%20Global.svg"/></div>|[[#Copy Selected Element Styles to Global]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Create%20new%20markdown%20file%20and%20embed%20into%20active%20drawing.svg"/></div>|[[#Create new markdown file and embed into active drawing]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Darken%20background%20color.svg"/></div>|[[#Darken background color]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Deconstruct%20selected%20elements%20into%20new%20drawing.svg"/></div>|[[#Deconstruct selected elements into new drawing]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Elbow%20connectors.svg"/></div>|[[#Elbow connectors]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Expand%20rectangles%20horizontally%20keep%20text%20centered.svg"/></div>|[[#Expand rectangles horizontally keep text centered]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Expand%20rectangles%20horizontally.svg"/></div>|[[#Expand rectangles horizontally]]|
|
||||
@@ -54,9 +57,9 @@ I would love to include your contribution in the script library. If you have a s
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Grid%20Selected%20Images.svg"/></div>|[[#Grid selected images]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Lighten%20background%20color.svg"/></div>|[[#Lighten background color]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Mindmap%20connector.svg"/></div>|[[#Mindmap connector]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Mindmap%20format.svg"/></div>|[[#Mindmap format]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Modify%20background%20color%20opacity.svg"/></div>|[[#Modify background color opacity]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Normalize%20Selected%20Arrows.svg"/></div>|[[#Normalize Selected Arrows]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/OCR%20-%20Optical%20Character%20Recognition.svg"/></div>|[[#OCR - Optical Character Recognition]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Organic%20Line.svg"/></div>|[[#Organic Line]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Palette%20loader.svg"/></div>|[[#Palette Loader]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Rename%20Image.svg"/></div>|[[#Rename Image]]|
|
||||
@@ -71,9 +74,10 @@ I would love to include your contribution in the script library. If you have a s
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Link%20Alias.svg"/></div>|[[#Set Link Alias]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Stroke%20Width%20of%20Selected%20Elements.svg"/></div>|[[#Set Stroke Width of Selected Elements]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Text%20Alignment.svg"/></div>|[[#Set Text Alignment]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Slideshow.svg"/></div>|[[#Slideshow]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Split%20text%20by%20lines.svg"/></div>|[[#Split text by lines]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Text%20Arch.svg"/></div>|[[#Text Arch]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Transfer%20TextElements%20to%20Excalidraw%20markdown%20metadata.svg"/></div>|[[#Transfer TextElements to Excalidraw markdown metadata]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Uniform%20size.svg"/></div>|[[#Uniform Size]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Zoom%20to%20Fit%20Selected%20Elements.svg"/></div>|[[#Zoom to Fit Selected Elements]]|
|
||||
|
||||
## Add Connector Point
|
||||
@@ -100,6 +104,19 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Add%20Next%20Step%20in%20Process.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will prompt you for the title of the process step, then will create a stick note with the text. If an element is selected then the script will connect this new step with an arrow to the previous step (the selected element). If no element is selected, then the script assumes this is the first step in the process and will only output the sticky note with the text that was entered.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-process-step.jpg'></td></tr></table>
|
||||
|
||||
## Alternative Pens
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Alternative%20Pens.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/Alternative%20Pens.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will load pen presets overriding the default freedraw line in Excalidraw. Once you've downloaded this script, check the script description for a detailed how to guide.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-alternative-pens.jpg'></td></tr></table>
|
||||
|
||||
|
||||
## Auto Layout
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Auto%20Layout.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Auto%20Layout.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script performs automatic layout for the selected top-level grouping objects. It is powered by <a href='https://github.com/kieler/elkjs'>elkjs</a> and needs to be connected to the Internet.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-auto-layout.png'></td></tr></table>
|
||||
|
||||
## Box Each Selected Groups
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Box%20Each%20Selected%20Groups.md
|
||||
@@ -160,6 +177,12 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Darken%20background%20color.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script darkens the background color of the selected element by 2% at a time. You can use this script several times until you are satisfied. It is recommended to set a shortcut key for this script so that you can quickly try to DARKEN and LIGHTEN the color effect. In contrast to the `Modify background color opacity` script, the advantage is that the background color of the element is not affected by the canvas color, and the color value does not appear in a strange rgba() form.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/darken-lighten-background-color.png'></td></tr></table>
|
||||
|
||||
## Deconstruct selected elements into new drawing
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Deconstruct%20selected%20elements%20into%20new%20drawing.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Deconstruct%20selected%20elements%20into%20new%20drawing.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Select some elements in the scene. The script will take these elements and move them into a new Excalidraw file, and open that file. The selected elements will also be replaced in your original drawing with the embedded Excalidraw file (the one that was just created). You will be prompted for the file name of the new deconstructed image. The script is useful if you want to break a larger drawing into smaller reusable parts that you want to reference in multiple drawings.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-deconstruct.jpg'></td></tr></table>
|
||||
|
||||
## Elbow connectors
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Elbow%20connectors.md
|
||||
@@ -238,6 +261,12 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/xllowl'>@xllowl</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/Mindmap%20connector.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script creates mindmap like lines (only right side and down available currently) for selected elements. The line will start according to the creation time of the elements. So you should create the header element first.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/mindmap%20connector.png'></td></tr></table>
|
||||
|
||||
## Mindmap format
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Mindmap%20format.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/pandoralink'>@pandoralink</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/Mindmap%20format.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Automatically formats a mindmap from left to right based on the creation sequence of arrows.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-mindmap-format-1.png'><br>A mindmap is actually a tree, so you must have a <b>root node</b>. The script will determine <b>the leftmost element</b> of the selected element as the root element (the node must be a rectangle, diamond, ellipse, text, image, but it can't be an arrow, line, freedraw, or <b>group</b>)<br>The element connecting node and node must be an <b>arrow</b> and have the correct direction, e.g. <b>parent node -> child node</b>.<br>The order of nodes in the Y axis or vertical direction is determined by <b>the creation time</b> of the arrow connecting it.<br><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-mindmap-format-2.png"><br>If you want to readjust the order, you can <b>delete arrows and reconnect them</b>.<br>The script provides options to adjust the style of the mindmap. Options are at the bottom of excalidraw plugin options (Settings -> Community plugins -> Excalidraw -> drag to bottom).<br>Since the start bingding and end bingding of the arrows are easily disconnected from the node, if there are unformatted parts, please <b>check the connection</b> and use the script to <b>reformat</b>.</td></tr></table>
|
||||
|
||||
## Modify background color opacity
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Modify%20background%20color%20opacity.md
|
||||
@@ -250,12 +279,6 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Normalize%20Selected%20Arrows.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script will reset the start and end positions of the selected arrows. The arrow will point to the center of the connected box and will have a gap of 8px from the box.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-normalize-selected-arrows.png'></td></tr></table>
|
||||
|
||||
## OCR - Optical Character Recognition
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/OCR%20-%20Optical%20Character%20Recognition.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/OCR%20-%20Optical%20Character%20Recognition.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">REQUIRES EXCALIDRAW 1.5.15<br>The script will 1) send the selected image file to [taskbone.com](https://taskbone.com) to exctract the text from the image, and 2) will add the text to your drawing as a text element.<br><mark>⚠ Note that you will need to manually paste your token into the script after the first run! ⚠</mark><br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-ocr.jpg'><br><iframe width="560" height="315" src="https://www.youtube.com/embed/W2NMzR8s4eE" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></td></tr></table>
|
||||
|
||||
## Organic Line
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Organic%20Line.md
|
||||
@@ -340,6 +363,12 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20Text%20Alignment.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Sets text alignment of text block (cetner, right, left). Useful if you want to set a keyboard shortcut for selecting text alignment.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-text-align.jpg'></td></tr></table>
|
||||
|
||||
## Slideshow
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Slideshow.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/Slideshow.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script will convert your drawing into a slideshow presentation.<br><iframe width="560" height="315" src="https://www.youtube.com/embed/HhRHFhWkmCk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></td></tr></table>
|
||||
|
||||
## Split text by lines
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Split%20text%20by%20lines.md
|
||||
@@ -352,11 +381,11 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Text%20Arch.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Fit a text to the arch of a circle. The script will prompt you for the radius of the circle and then split your text to individual letters and place each letter to the arch defined by the radius. Setting a lower radius value will increase the arching of the text. Note that the arched-text will no longer be editable as a text element and it will no longer function as a markdown link. Emojis are currently not supported.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/text-arch.jpg'></td></tr></table>
|
||||
|
||||
## Transfer TextElements to Excalidraw markdown metadata
|
||||
## Uniform Size
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Transfer%20TextElements%20to%20Excalidraw%20markdown%20metadata.md
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Uniform%20size.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Transfer%20TextElements%20to%20Excalidraw%20markdown%20metadata.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script will delete the selected text elements from the canvas and will copy the text from these text elements into the Excalidraw markdown file as metadata. This means, that the text will no longer be visible in the drawing, however you will be able to search for the text in Obsidian and find the drawing containing this image.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-text-to-metadata.jpg'></td></tr></table>
|
||||
<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/Uniform%20size.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data"><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-uniform-size.jpg"><br>The script will standardize the sizes of rectangles, diamonds and ellipses adjusting all the elements to match the largest width and height within the group.</td></tr></table>
|
||||
|
||||
## Zoom to Fit Selected Elements
|
||||
```excalidraw-script-install
|
||||
|
||||
17
ea-scripts/pens/Fine tipped pen - fixed pressure.md
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
constantPressure: true,
|
||||
options: {
|
||||
smoothing: 0.4,
|
||||
thinning: -0.5,
|
||||
streamline: 0.4,
|
||||
easing: "linear",
|
||||
start: {
|
||||
taper: 5,
|
||||
cap: false,
|
||||
},
|
||||
end: {
|
||||
taper: 5,
|
||||
cap: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
16
ea-scripts/pens/Fountain pen.md
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
options: {
|
||||
smoothing: 0.22,
|
||||
thinning: 0.8,
|
||||
streamline: 0.22,
|
||||
easing: "easeInQuad",
|
||||
start: {
|
||||
taper: true,
|
||||
cap: true,
|
||||
},
|
||||
end: {
|
||||
taper: 1,
|
||||
cap: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
16
ea-scripts/pens/Mindmap - thick-thin-thick.md
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
constantPressure: true,
|
||||
options: {
|
||||
thinning: 4,
|
||||
smoothing: 0.5,
|
||||
streamline: 0.5,
|
||||
start: {
|
||||
taper: true,
|
||||
easing: "linear",
|
||||
},
|
||||
end: {
|
||||
taper: true,
|
||||
easing: "linear",
|
||||
}
|
||||
}
|
||||
}
|
||||
16
ea-scripts/pens/Mindmap - thick-thin.md
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
constantPressure: true,
|
||||
options: {
|
||||
thinning: 4,
|
||||
smoothing: 0.5,
|
||||
streamline: 0.5,
|
||||
start: {
|
||||
taper: 0,
|
||||
cap: true
|
||||
},
|
||||
end: {
|
||||
taper: true,
|
||||
easing: "linear",
|
||||
}
|
||||
}
|
||||
}
|
||||
18
ea-scripts/pens/Thick marker - dynamic pressure.md
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
hasOutline: true,
|
||||
outlineWidth: 4,
|
||||
options: {
|
||||
thinning: 3,
|
||||
smoothing: 0.5,
|
||||
streamline: 0.5,
|
||||
easing: "easeInOutElastic",
|
||||
start: {
|
||||
taper: 50,
|
||||
cap: true
|
||||
},
|
||||
end: {
|
||||
taper: 50,
|
||||
cap: true
|
||||
}
|
||||
}
|
||||
}
|
||||
19
ea-scripts/pens/Thick marker - fixed pressure.md
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
constantPressure: true,
|
||||
hasOutline: true,
|
||||
outlineWidth: 4,
|
||||
options: {
|
||||
thinning: 1,
|
||||
smoothing: 0.5,
|
||||
streamline: 0.5,
|
||||
easing: "linear",
|
||||
start: {
|
||||
taper: 0,
|
||||
cap: true
|
||||
},
|
||||
end: {
|
||||
taper: 0,
|
||||
cap: true
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
images/excalidraw-modifiers.png
Normal file
|
After Width: | Height: | Size: 322 KiB |
BIN
images/scripts-alternative-pens.jpg
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
images/scripts-auto-layout.png
Normal file
|
After Width: | Height: | Size: 189 KiB |
BIN
images/scripts-deconstruct.jpg
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
images/scripts-mindmap-format-1.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
images/scripts-mindmap-format-2.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 224 KiB |
BIN
images/scripts-slideshow-1.jpg
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
images/scripts-slideshow-2.jpg
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
images/scripts-uniform-size.jpg
Normal file
|
After Width: | Height: | Size: 29 KiB |
10
manifest-beta.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "1.8.15-beta",
|
||||
"minAppVersion": "0.16.0",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
"authorUrl": "https://zsolt.blog",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "1.7.26",
|
||||
"minAppVersion": "0.15.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
"authorUrl": "https://zsolt.blog",
|
||||
"isDesktopOnly": false
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "1.8.19",
|
||||
"minAppVersion": "1.0.0",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
"authorUrl": "https://zsolt.blog",
|
||||
"fundingUrl": "https://ko-fi.com/zsolt",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
|
||||
66
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "obsidian-excalidraw-plugin",
|
||||
"version": "1.7.26",
|
||||
"version": "1.8.10",
|
||||
"description": "This is an Obsidian.md plugin that lets you view and edit Excalidraw drawings",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
@@ -18,52 +18,52 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/lz-string": "^1.3.34",
|
||||
"@zsviczian/excalidraw": "0.13.0-obsidian",
|
||||
"clsx": "^1.1.1",
|
||||
"@zsviczian/excalidraw": "0.14.2-obsidian-2",
|
||||
"chroma-js": "^2.4.2",
|
||||
"clsx": "^1.2.1",
|
||||
"colormaster": "^1.2.1",
|
||||
"gl-matrix": "^3.4.3",
|
||||
"lz-string": "^1.4.4",
|
||||
"monkey-around": "^2.3.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-scripts": "^5.0.1",
|
||||
"roughjs": "^4.5.2",
|
||||
"colormaster": "1.2.1",
|
||||
"chroma-js": "^2.4.2",
|
||||
"gl-matrix": "^3.4.3"
|
||||
"html2canvas": "^1.4.1",
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"nanoid": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.16.12",
|
||||
"@babel/preset-env": "^7.16.11",
|
||||
"@babel/preset-react": "^7.16.7",
|
||||
"@excalidraw/eslint-config": "1.0.0",
|
||||
"@excalidraw/prettier-config": "1.0.2",
|
||||
"@popperjs/core": "^2.11.5",
|
||||
"@rollup/plugin-babel": "^5.3.0",
|
||||
"@rollup/plugin-commonjs": "^21.0.1",
|
||||
"@rollup/plugin-node-resolve": "^13.1.3",
|
||||
"@rollup/plugin-replace": "^3.0.1",
|
||||
"@rollup/plugin-typescript": "^8.3.0",
|
||||
"@types/js-beautify": "^1.13.3",
|
||||
"@babel/core": "^7.20.12",
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@excalidraw/eslint-config": "^1.0.3",
|
||||
"@excalidraw/prettier-config": "^1.0.2",
|
||||
"@rollup/plugin-babel": "^6.0.3",
|
||||
"@rollup/plugin-commonjs": "^24.0.0",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"@rollup/plugin-replace": "^5.0.2",
|
||||
"@rollup/plugin-typescript": "^11.0.0",
|
||||
"@types/chroma-js": "^2.1.4",
|
||||
"@types/node": "^15.12.4",
|
||||
"@types/react-dom": "^17.0.2",
|
||||
"@types/js-beautify": "^1.13.3",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/react-dom": "^18.0.10",
|
||||
"@zerollup/ts-transform-paths": "^1.7.18",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint-config-prettier": "8.3.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"html2canvas": "^1.4.0",
|
||||
"nanoid": "^4.0.0",
|
||||
"obsidian": "^0.16.3",
|
||||
"prettier": "^2.5.1",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"obsidian": "^1.1.1",
|
||||
"prettier": "^2.8.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-typescript2": "^0.34.1",
|
||||
"rollup-plugin-visualizer": "^5.9.0",
|
||||
"rollup-plugin-web-worker-loader": "^1.6.1",
|
||||
"tslib": "^2.3.1",
|
||||
"ttypescript": "^1.5.13",
|
||||
"typescript": "^4.5.5"
|
||||
"tslib": "^2.4.1",
|
||||
"ttypescript": "^1.5.15",
|
||||
"typescript": "^4.9.4"
|
||||
},
|
||||
"resolutions": {
|
||||
"@typescript-eslint/typescript-estree": "5.3.0"
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
//https://stackoverflow.com/questions/2068344/how-do-i-get-a-youtube-video-thumbnail-from-the-youtube-api
|
||||
//https://img.youtube.com/vi/uZz5MgzWXiM/maxresdefault.jpg
|
||||
|
||||
import { FileId } from "@zsviczian/excalidraw/types/element/types";
|
||||
import { BinaryFileData, DataURL } from "@zsviczian/excalidraw/types/types";
|
||||
import { App, MarkdownRenderer, Notice, TFile } from "obsidian";
|
||||
import { App, MarkdownRenderer, Notice, requestUrl, RequestUrlResponse, TFile } from "obsidian";
|
||||
import {
|
||||
CASCADIA_FONT,
|
||||
DEFAULT_MD_EMBED_CSS,
|
||||
@@ -11,6 +14,7 @@ import {
|
||||
FRONTMATTER_KEY_MD_STYLE,
|
||||
IMAGE_TYPES,
|
||||
nanoid,
|
||||
URLFETCHTIMEOUT,
|
||||
VIRGIL_FONT,
|
||||
} from "./Constants";
|
||||
import { createSVG } from "./ExcalidrawAutomate";
|
||||
@@ -19,6 +23,7 @@ import { ExportSettings } from "./ExcalidrawView";
|
||||
import { t } from "./lang/helpers";
|
||||
import { tex2dataURL } from "./LaTeX";
|
||||
import ExcalidrawPlugin from "./main";
|
||||
import { getDataURLFromURL, getMimeType, getURLImageExtension } from "./utils/FileUtils";
|
||||
import {
|
||||
errorlog,
|
||||
getDataURL,
|
||||
@@ -36,6 +41,15 @@ import {
|
||||
|
||||
const THEME_FILTER = "invert(100%) hue-rotate(180deg) saturate(1.25)";
|
||||
|
||||
//An ugly workaround for the following situation.
|
||||
//File A is a markdown file that has an embedded Excalidraw file B
|
||||
//Later file A is embedded into file B as a Markdown embed
|
||||
//Because MarkdownRenderer.renderMarkdown does not take a depth parameter as input
|
||||
//EmbeddedFileLoader cannot track the recursion depth (as it can when Excalidraw drawings are embedded)
|
||||
//For this reason, the markdown TFile is added to the Watchdog when rendering starts
|
||||
//and getObsidianImage is aborted if the file is already in the Watchdog stack
|
||||
const markdownRendererRecursionWatcthdog = new Set<TFile>();
|
||||
|
||||
export declare type MimeType =
|
||||
| "image/svg+xml"
|
||||
| "image/png"
|
||||
@@ -68,7 +82,8 @@ export class EmbeddedFile {
|
||||
public linkParts: LinkParts;
|
||||
private hostPath: string;
|
||||
public attemptCounter: number = 0;
|
||||
/*public isHyperlink: boolean = false;*/
|
||||
public isHyperlink: boolean = false;
|
||||
public hyperlink:DataURL;
|
||||
|
||||
constructor(plugin: ExcalidrawPlugin, hostPath: string, imgPath: string) {
|
||||
this.plugin = plugin;
|
||||
@@ -76,14 +91,15 @@ export class EmbeddedFile {
|
||||
}
|
||||
|
||||
public resetImage(hostPath: string, imgPath: string) {
|
||||
/*if(imgPath.startsWith("https://") || imgPath.startsWith("http://")) {
|
||||
this.img=imgPath;
|
||||
this.imgInverted=imgPath;
|
||||
this.isHyperlink = true;
|
||||
return;
|
||||
}*/
|
||||
this.imgInverted = this.img = "";
|
||||
this.mtime = 0;
|
||||
|
||||
if(imgPath.startsWith("https://") || imgPath.startsWith("http://")){
|
||||
this.isHyperlink = true;
|
||||
this.hyperlink = imgPath as DataURL;
|
||||
return;
|
||||
};
|
||||
|
||||
this.linkParts = getLinkParts(imgPath);
|
||||
this.hostPath = hostPath;
|
||||
if (!this.linkParts.path) {
|
||||
@@ -111,6 +127,9 @@ export class EmbeddedFile {
|
||||
}
|
||||
|
||||
private fileChanged(): boolean {
|
||||
if(this.isHyperlink) {
|
||||
return false;
|
||||
}
|
||||
if (!this.file) {
|
||||
this.file = app.metadataCache.getFirstLinkpathDest(
|
||||
this.linkParts.path,
|
||||
@@ -131,13 +150,13 @@ export class EmbeddedFile {
|
||||
isDark: boolean,
|
||||
isSVGwithBitmap: boolean,
|
||||
) {
|
||||
if (!this.file) {
|
||||
if (!this.file && !this.isHyperlink) {
|
||||
return;
|
||||
}
|
||||
if (this.fileChanged()) {
|
||||
this.imgInverted = this.img = "";
|
||||
}
|
||||
this.mtime = this.file.stat.mtime;
|
||||
this.mtime = this.isHyperlink ? 0 : this.file.stat.mtime;
|
||||
this.size = size;
|
||||
this.mimeType = mimeType;
|
||||
switch (isDark && isSVGwithBitmap) {
|
||||
@@ -152,18 +171,20 @@ export class EmbeddedFile {
|
||||
}
|
||||
|
||||
public isLoaded(isDark: boolean): boolean {
|
||||
if (!this.file) {
|
||||
this.file = 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.isHyperlink) {
|
||||
if (!this.file) {
|
||||
this.file = 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;
|
||||
}
|
||||
}
|
||||
if (this.fileChanged()) {
|
||||
return false;
|
||||
}
|
||||
if (this.isSVGwithBitmap && isDark) {
|
||||
return this.imgInverted !== "";
|
||||
@@ -172,10 +193,7 @@ export class EmbeddedFile {
|
||||
}
|
||||
|
||||
public getImage(isDark: boolean) {
|
||||
/*if(this.isHyperlink) {
|
||||
return this.img;
|
||||
}*/
|
||||
if (!this.file) {
|
||||
if (!this.file && !this.isHyperlink) {
|
||||
return "";
|
||||
}
|
||||
if (isDark && this.isSVGwithBitmap) {
|
||||
@@ -189,7 +207,7 @@ export class EmbeddedFile {
|
||||
* @returns true if image should scale such as the updated images has the same area as the previous images, false if the image should be displayed at 100%
|
||||
*/
|
||||
public shouldScale() {
|
||||
return !Boolean(this.linkParts && this.linkParts.original && this.linkParts.original.endsWith("|100%"));
|
||||
return this.isHyperlink || !Boolean(this.linkParts && this.linkParts.original && this.linkParts.original.endsWith("|100%"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,22 +234,33 @@ export class EmbeddedFilesLoader {
|
||||
if (!this.plugin || !inFile) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isHyperlink = inFile instanceof EmbeddedFile ? inFile.isHyperlink : false;
|
||||
const hyperlink = inFile instanceof EmbeddedFile ? inFile.hyperlink : "";
|
||||
const file: TFile = inFile instanceof EmbeddedFile ? inFile.file : inFile;
|
||||
if(file && markdownRendererRecursionWatcthdog.has(file)) {
|
||||
new Notice(`Loading of ${file.path}. Please check if there is an inifinite loop of one file embedded in the other.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const linkParts =
|
||||
inFile instanceof EmbeddedFile
|
||||
? inFile.linkParts
|
||||
: {
|
||||
original: file.path,
|
||||
path: file.path,
|
||||
isBlockRef: false,
|
||||
ref: null,
|
||||
width: this.plugin.settings.mdSVGwidth,
|
||||
height: this.plugin.settings.mdSVGmaxHeight,
|
||||
};
|
||||
isHyperlink
|
||||
? null
|
||||
: inFile instanceof EmbeddedFile
|
||||
? inFile.linkParts
|
||||
: {
|
||||
original: file.path,
|
||||
path: file.path,
|
||||
isBlockRef: false,
|
||||
ref: null,
|
||||
width: this.plugin.settings.mdSVGwidth,
|
||||
height: this.plugin.settings.mdSVGmaxHeight,
|
||||
};
|
||||
|
||||
let hasSVGwithBitmap = false;
|
||||
const isExcalidrawFile = this.plugin.isExcalidrawFile(file);
|
||||
const isExcalidrawFile = !isHyperlink && this.plugin.isExcalidrawFile(file);
|
||||
if (
|
||||
!isHyperlink &&
|
||||
!(
|
||||
IMAGE_TYPES.contains(file.extension) ||
|
||||
isExcalidrawFile ||
|
||||
@@ -240,7 +269,9 @@ export class EmbeddedFilesLoader {
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const ab = await app.vault.readBinary(file);
|
||||
const ab = isHyperlink
|
||||
? null
|
||||
: await app.vault.readBinary(file);
|
||||
|
||||
const getExcalidrawSVG = async (isDark: boolean) => {
|
||||
//debug({where:"EmbeddedFileLoader.getExcalidrawSVG",uid:this.uid,file:file.name});
|
||||
@@ -292,64 +323,55 @@ export class EmbeddedFilesLoader {
|
||||
? await getExcalidrawSVG(this.isDark)
|
||||
: null;
|
||||
let mimeType: MimeType = "image/svg+xml";
|
||||
if (!isExcalidrawFile) {
|
||||
switch (file.extension) {
|
||||
case "png":
|
||||
mimeType = "image/png";
|
||||
break;
|
||||
case "jpeg":
|
||||
mimeType = "image/jpeg";
|
||||
break;
|
||||
case "jpg":
|
||||
mimeType = "image/jpeg";
|
||||
break;
|
||||
case "gif":
|
||||
mimeType = "image/gif";
|
||||
break;
|
||||
case "webp":
|
||||
mimeType = "image/webp";
|
||||
break;
|
||||
case "bmp":
|
||||
mimeType = "image/bmp";
|
||||
break;
|
||||
case "ico":
|
||||
mimeType = "image/x-icon"
|
||||
break;
|
||||
case "svg":
|
||||
case "md":
|
||||
mimeType = "image/svg+xml";
|
||||
break;
|
||||
default:
|
||||
mimeType = "application/octet-stream";
|
||||
}
|
||||
}
|
||||
let dataURL =
|
||||
excalidrawSVG ??
|
||||
(file.extension === "svg"
|
||||
? await getSVGData(app, file)
|
||||
: file.extension === "md"
|
||||
? null
|
||||
: await getDataURL(ab, mimeType));
|
||||
|
||||
if(!dataURL) {
|
||||
const result = await this.convertMarkdownToSVG(this.plugin, file, linkParts);
|
||||
const extension = isHyperlink
|
||||
? getURLImageExtension(hyperlink)
|
||||
: file.extension;
|
||||
if (!isExcalidrawFile) {
|
||||
mimeType = getMimeType(extension);
|
||||
}
|
||||
|
||||
let dataURL =
|
||||
isHyperlink
|
||||
? (
|
||||
inFile instanceof EmbeddedFile
|
||||
? await getDataURLFromURL(inFile.hyperlink, mimeType)
|
||||
: null
|
||||
)
|
||||
: excalidrawSVG ??
|
||||
(file.extension === "svg"
|
||||
? await getSVGData(app, file)
|
||||
: file.extension === "md"
|
||||
? null
|
||||
: await getDataURL(ab, mimeType));
|
||||
|
||||
if(!isHyperlink && !dataURL) {
|
||||
markdownRendererRecursionWatcthdog.add(file);
|
||||
const result = await this.convertMarkdownToSVG(this.plugin, file, linkParts, depth);
|
||||
markdownRendererRecursionWatcthdog.delete(file);
|
||||
dataURL = result.dataURL;
|
||||
hasSVGwithBitmap = result.hasSVGwithBitmap;
|
||||
}
|
||||
const size = await getImageSize(dataURL);
|
||||
return {
|
||||
mimeType,
|
||||
fileId: await generateIdFromFile(ab),
|
||||
dataURL,
|
||||
created: file.stat.mtime,
|
||||
hasSVGwithBitmap,
|
||||
size,
|
||||
};
|
||||
try{
|
||||
const size = await getImageSize(dataURL);
|
||||
return {
|
||||
mimeType,
|
||||
fileId: await generateIdFromFile(
|
||||
isHyperlink? (new TextEncoder()).encode(dataURL as string) : ab
|
||||
),
|
||||
dataURL,
|
||||
created: isHyperlink ? 0 : file.stat.mtime,
|
||||
hasSVGwithBitmap,
|
||||
size,
|
||||
};
|
||||
} catch(e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async loadSceneFiles(
|
||||
excalidrawData: ExcalidrawData,
|
||||
addFiles: Function,
|
||||
addFiles: (files: FileData[], isDark: boolean) => void,
|
||||
depth:number
|
||||
) {
|
||||
if(depth > 4) {
|
||||
@@ -428,6 +450,7 @@ export class EmbeddedFilesLoader {
|
||||
plugin: ExcalidrawPlugin,
|
||||
file: TFile,
|
||||
linkParts: LinkParts,
|
||||
depth: number,
|
||||
): Promise<{dataURL: DataURL, hasSVGwithBitmap:boolean}> {
|
||||
//1.
|
||||
//get the markdown text
|
||||
@@ -655,7 +678,7 @@ const getSVGData = async (app: App, file: TFile): Promise<DataURL> => {
|
||||
return svgToBase64(svg) as DataURL;
|
||||
};
|
||||
|
||||
const generateIdFromFile = async (file: ArrayBuffer): Promise<FileId> => {
|
||||
export const generateIdFromFile = async (file: ArrayBuffer): Promise<FileId> => {
|
||||
let id: FileId;
|
||||
try {
|
||||
const hashBuffer = await window.crypto.subtle.digest("SHA-1", file);
|
||||
|
||||
@@ -2,16 +2,19 @@ import ExcalidrawPlugin from "./main";
|
||||
import {
|
||||
FillStyle,
|
||||
StrokeStyle,
|
||||
StrokeSharpness,
|
||||
ExcalidrawElement,
|
||||
ExcalidrawBindableElement,
|
||||
FileId,
|
||||
NonDeletedExcalidrawElement,
|
||||
ExcalidrawImageElement,
|
||||
ExcalidrawTextElement,
|
||||
StrokeRoundness,
|
||||
RoundnessType,
|
||||
} from "@zsviczian/excalidraw/types/element/types";
|
||||
import { normalizePath, Notice, TFile, WorkspaceLeaf } from "obsidian";
|
||||
import * as obsidian_module from "obsidian";
|
||||
import ExcalidrawView, { ExportSettings, TextMode } from "./ExcalidrawView";
|
||||
import { ExcalidrawData } from "./ExcalidrawData";
|
||||
import { ExcalidrawData, getMarkdownDrawingSection } from "./ExcalidrawData";
|
||||
import {
|
||||
FRONTMATTER,
|
||||
nanoid,
|
||||
@@ -19,6 +22,7 @@ import {
|
||||
MAX_IMAGE_SIZE,
|
||||
COLOR_NAMES,
|
||||
fileid,
|
||||
GITHUB_RELEASES,
|
||||
} from "./Constants";
|
||||
import { getDrawingFilename, } from "./utils/FileUtils";
|
||||
import {
|
||||
@@ -32,11 +36,11 @@ import {
|
||||
isVersionNewerThanOther,
|
||||
log,
|
||||
scaleLoadedImage,
|
||||
wrapText,
|
||||
wrapTextAtCharLength,
|
||||
} from "./utils/Utils";
|
||||
import { getNewOrAdjacentLeaf, isObsidianThemeDark } from "./utils/ObsidianUtils";
|
||||
import { AppState, Point } from "@zsviczian/excalidraw/types/types";
|
||||
import { EmbeddedFilesLoader, FileData } from "./EmbeddedFileLoader";
|
||||
import { AppState, BinaryFileData, DataURL, Point } from "@zsviczian/excalidraw/types/types";
|
||||
import { EmbeddedFile, EmbeddedFilesLoader, FileData } from "./EmbeddedFileLoader";
|
||||
import { tex2dataURL } from "./LaTeX";
|
||||
//import Excalidraw from "@zsviczian/excalidraw";
|
||||
import { Prompt } from "./dialogs/Prompt";
|
||||
@@ -59,6 +63,7 @@ import RYBPlugin from "colormaster/plugins/ryb";
|
||||
import CMYKPlugin from "colormaster/plugins/cmyk";
|
||||
import { TInput } from "colormaster/types";
|
||||
import {ConversionResult, svgToExcalidraw} from "./svgToExcalidraw/parser"
|
||||
import { ROUNDNESS } from "./Constants";
|
||||
|
||||
extendPlugins([
|
||||
HarmonyPlugin,
|
||||
@@ -96,6 +101,12 @@ declare global {
|
||||
}
|
||||
|
||||
export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
/**
|
||||
* Utility function that returns the Obsidian Module object.
|
||||
*/
|
||||
get obsidian() {
|
||||
return obsidian_module;
|
||||
};
|
||||
plugin: ExcalidrawPlugin;
|
||||
targetView: ExcalidrawView = null; //the view currently edited
|
||||
elementsDict: {[key:string]:any}; //contains the ExcalidrawElements currently edited in Automate indexed by el.id
|
||||
@@ -110,7 +121,8 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
strokeStyle: StrokeStyle; //type StrokeStyle = "solid" | "dashed" | "dotted"
|
||||
roughness: number;
|
||||
opacity: number;
|
||||
strokeSharpness: StrokeSharpness; //type StrokeSharpness = "round" | "sharp"
|
||||
strokeSharpness?: StrokeRoundness; //defaults to undefined, use strokeRoundess and roundess instead. Only kept for legacy script compatibility type StrokeRoundness = "round" | "sharp"
|
||||
roundness: null | { type: RoundnessType; value?: number };
|
||||
fontFamily: number; //1: Virgil, 2:Helvetica, 3:Cascadia, 4:LocalFont
|
||||
fontSize: number;
|
||||
textAlign: string; //"left"|"right"|"center"
|
||||
@@ -123,6 +135,7 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
viewBackgroundColor: string;
|
||||
gridSize: number;
|
||||
};
|
||||
colorPalette: {};
|
||||
|
||||
constructor(plugin: ExcalidrawPlugin, view?: ExcalidrawView) {
|
||||
this.plugin = plugin;
|
||||
@@ -130,6 +143,19 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
this.targetView = view;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns the last recorded pointer position on the Excalidraw canvas
|
||||
*/
|
||||
public getViewLastPointerPosition(): {x:number, y:number} {
|
||||
//@ts-ignore
|
||||
if (!this.targetView || !this.targetView?._loaded) {
|
||||
errorMessage("targetView not set", "getExcalidrawAPI()");
|
||||
return null;
|
||||
}
|
||||
return this.targetView.currentPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns
|
||||
@@ -181,10 +207,12 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
setStrokeSharpness(val: number) {
|
||||
switch (val) {
|
||||
case 0:
|
||||
this.style.strokeSharpness = "round";
|
||||
this.style.roundness = {
|
||||
type: ROUNDNESS.LEGACY
|
||||
}
|
||||
return "round";
|
||||
default:
|
||||
this.style.strokeSharpness = "sharp";
|
||||
this.style.roundness = null; //sharp
|
||||
return "sharp";
|
||||
}
|
||||
};
|
||||
@@ -301,10 +329,14 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
"excalidraw-url-prefix"?: string;
|
||||
"excalidraw-export-transparent"?: boolean;
|
||||
"excalidraw-export-dark"?: boolean;
|
||||
"excalidraw-export-svgpadding"?: number;
|
||||
"excalidraw-export-padding"?: number;
|
||||
"excalidraw-export-pngscale"?: number;
|
||||
"excalidraw-default-mode"?: "view" | "zen";
|
||||
"excalidraw-onload-script"?: string;
|
||||
"excalidraw-linkbutton-opacity"?: number;
|
||||
"excalidraw-autoexport"?: boolean;
|
||||
};
|
||||
plaintext?: string; //text to insert above the `# Text Elements` section
|
||||
}): Promise<string> {
|
||||
const template = params?.templatePath
|
||||
? await getTemplate(
|
||||
@@ -343,7 +375,7 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
const scene = {
|
||||
type: "excalidraw",
|
||||
version: 2,
|
||||
source: "https://excalidraw.com",
|
||||
source: GITHUB_RELEASES+PLUGIN_VERSION,
|
||||
elements,
|
||||
appState: {
|
||||
theme: template?.appState?.theme ?? this.canvas.theme,
|
||||
@@ -374,23 +406,51 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
template?.appState?.currentItemFontSize ?? this.style.fontSize,
|
||||
currentItemTextAlign:
|
||||
template?.appState?.currentItemTextAlign ?? this.style.textAlign,
|
||||
currentItemStrokeSharpness:
|
||||
template?.appState?.currentItemStrokeSharpness ??
|
||||
this.style.strokeSharpness,
|
||||
currentItemStartArrowhead:
|
||||
template?.appState?.currentItemStartArrowhead ??
|
||||
this.style.startArrowHead,
|
||||
currentItemEndArrowhead:
|
||||
template?.appState?.currentItemEndArrowhead ??
|
||||
this.style.endArrowHead,
|
||||
currentItemLinearStrokeSharpness:
|
||||
template?.appState?.currentItemLinearStrokeSharpness ??
|
||||
this.style.strokeSharpness,
|
||||
currentItemRoundness: //type StrokeRoundness = "round" | "sharp"
|
||||
template?.appState?.currentItemLinearStrokeSharpness ?? //legacy compatibility
|
||||
template?.appState?.currentItemStrokeSharpness ?? //legacy compatibility
|
||||
template?.appState?.currentItemRoundness ??
|
||||
this.style.roundness ? "round":"sharp",
|
||||
gridSize: template?.appState?.gridSize ?? this.canvas.gridSize,
|
||||
colorPalette: template?.appState?.colorPalette ?? this.colorPalette,
|
||||
},
|
||||
files: template?.files ?? {},
|
||||
};
|
||||
|
||||
const generateMD = ():string => {
|
||||
let outString = params.plaintext ? params.plaintext + "\n\n" : "";
|
||||
const textElements = this.getElements().filter(el => el.type === "text") as ExcalidrawTextElement[];
|
||||
outString += "# Text Elements\n";
|
||||
textElements.forEach(te=> {
|
||||
outString += `${te.rawText ?? (te.originalText ?? te.text)} ^${te.id}\n\n`;
|
||||
});
|
||||
|
||||
const elementsWithLinks = this.getElements().filter( el => el.type !== "text" && el.link)
|
||||
elementsWithLinks.forEach(el=>{
|
||||
outString += `${el.link} ^${el.id}\n\n`;
|
||||
})
|
||||
|
||||
outString += Object.keys(this.imagesDict).length > 0
|
||||
? "\n# Embedded files\n"
|
||||
: "";
|
||||
|
||||
Object.keys(this.imagesDict).forEach((key: FileId)=> {
|
||||
const item = this.imagesDict[key];
|
||||
if(item.latex) {
|
||||
outString += `${key}: $$${item.latex}$$\n`;
|
||||
} else {
|
||||
outString += `${key}: [[${item.file}]]\n`;
|
||||
}
|
||||
})
|
||||
return outString;
|
||||
}
|
||||
|
||||
return this.plugin.createAndOpenDrawing(
|
||||
params?.filename
|
||||
? params.filename + (params.filename.endsWith(".md") ? "": ".excalidraw.md")
|
||||
@@ -399,8 +459,8 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
params?.foldername ? params.foldername : this.plugin.settings.folder,
|
||||
this.plugin.settings.compatibilityMode
|
||||
? JSON.stringify(scene, null, "\t")
|
||||
: frontmatter +
|
||||
(await this.plugin.exportSceneToMD(JSON.stringify(scene, null, "\t"))),
|
||||
: frontmatter + generateMD() +
|
||||
getMarkdownDrawingSection(JSON.stringify(scene, null, "\t"),this.plugin.settings.compress)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -454,7 +514,8 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
this.getElements(),
|
||||
this.plugin,
|
||||
0,
|
||||
padding
|
||||
padding,
|
||||
this.imagesDict
|
||||
);
|
||||
};
|
||||
|
||||
@@ -508,7 +569,8 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
this.getElements(),
|
||||
this.plugin,
|
||||
0,
|
||||
padding
|
||||
padding,
|
||||
this.imagesDict,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -519,7 +581,7 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
* @returns
|
||||
*/
|
||||
wrapText(text: string, lineLen: number): string {
|
||||
return wrapText(text, lineLen, this.plugin.settings.forceWrap);
|
||||
return wrapTextAtCharLength(text, lineLen, this.plugin.settings.forceWrap);
|
||||
};
|
||||
|
||||
private boxedElement(
|
||||
@@ -545,7 +607,11 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
strokeStyle: this.style.strokeStyle,
|
||||
roughness: this.style.roughness,
|
||||
opacity: this.style.opacity,
|
||||
strokeSharpness: this.style.strokeSharpness,
|
||||
roundness: this.style.strokeSharpness
|
||||
? (this.style.strokeSharpness === "round"
|
||||
? {type: ROUNDNESS.LEGACY}
|
||||
: null)
|
||||
: this.style.roundness,
|
||||
seed: Math.floor(Math.random() * 100000),
|
||||
version: 1,
|
||||
versionNonce: Math.floor(Math.random() * 1000000000),
|
||||
@@ -893,7 +959,7 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
async addImage(
|
||||
topX: number,
|
||||
topY: number,
|
||||
imageFile: TFile,
|
||||
imageFile: TFile | string,
|
||||
scale: boolean = true, //true will scale the image to MAX_IMAGE_SIZE, false will insert image at 100% of its size
|
||||
): Promise<string> {
|
||||
const id = nanoid();
|
||||
@@ -901,17 +967,28 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
this.plugin,
|
||||
this.canvas.theme === "dark",
|
||||
);
|
||||
const image = await loader.getObsidianImage(imageFile,0);
|
||||
const image = (typeof imageFile === "string")
|
||||
? await loader.getObsidianImage(new EmbeddedFile(this.plugin, "", imageFile),0)
|
||||
: await loader.getObsidianImage(imageFile,0);
|
||||
|
||||
if (!image) {
|
||||
return null;
|
||||
}
|
||||
const fileId = imageFile.extension === "md" ? fileid() as FileId : image.fileId;
|
||||
const fileId = typeof imageFile === "string"
|
||||
? image.fileId
|
||||
: imageFile.extension === "md" ? fileid() as FileId : image.fileId;
|
||||
this.imagesDict[fileId] = {
|
||||
mimeType: image.mimeType,
|
||||
id: fileId,
|
||||
dataURL: image.dataURL,
|
||||
created: image.created,
|
||||
file: imageFile.path + (scale ? "":"|100%"),
|
||||
isHyperlink: typeof imageFile === "string",
|
||||
hyperlink: typeof imageFile === "string"
|
||||
? imageFile
|
||||
: null,
|
||||
file: typeof imageFile === "string"
|
||||
? null
|
||||
: imageFile.path + (scale ? "":"|100%"),
|
||||
hasSVGwithBitmap: image.hasSVGwithBitmap,
|
||||
latex: null,
|
||||
};
|
||||
@@ -1147,7 +1224,7 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
strokeStyle: "solid",
|
||||
roughness: 1,
|
||||
opacity: 100,
|
||||
strokeSharpness: "sharp",
|
||||
roundness: null,
|
||||
fontFamily: 1,
|
||||
fontSize: 20,
|
||||
textAlign: "left",
|
||||
@@ -1172,11 +1249,26 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* sets the target view for EA. All the view operations and the access to Excalidraw API will be performend on this view
|
||||
* if view is null or undefined, the function will first try setView("active"), then setView("first").
|
||||
* @param view
|
||||
* @returns
|
||||
* @returns targetView
|
||||
*/
|
||||
setView(view: ExcalidrawView | "first" | "active"): ExcalidrawView {
|
||||
setView(view?: ExcalidrawView | "first" | "active"): ExcalidrawView {
|
||||
if(!view) {
|
||||
const v = app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (v instanceof ExcalidrawView) {
|
||||
this.targetView = v;
|
||||
}
|
||||
else {
|
||||
const leaves =
|
||||
app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
if (!leaves || leaves.length == 0) {
|
||||
return;
|
||||
}
|
||||
this.targetView = leaves[0].view as ExcalidrawView;
|
||||
}
|
||||
}
|
||||
if (view == "active") {
|
||||
const v = app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (!(v instanceof ExcalidrawView)) {
|
||||
@@ -1221,11 +1313,11 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
errorMessage("targetView not set", "getViewElements()");
|
||||
return [];
|
||||
}
|
||||
const current = this.targetView?.excalidrawRef?.current;
|
||||
if (!current) {
|
||||
const api = this.targetView.excalidrawAPI;
|
||||
if (!api) {
|
||||
return [];
|
||||
}
|
||||
return current?.getSceneElements();
|
||||
return api.getSceneElements();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1309,34 +1401,84 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
});
|
||||
};
|
||||
|
||||
setViewModeEnabled(enabled: boolean): void {
|
||||
//@ts-ignore
|
||||
if (!this.targetView || !this.targetView?._loaded) {
|
||||
errorMessage("targetView not set", "viewToggleFullScreen()");
|
||||
return;
|
||||
}
|
||||
const view = this.targetView as ExcalidrawView;
|
||||
view.updateScene({appState:{viewModeEnabled: enabled}});
|
||||
view.toolsPanelRef?.current?.setExcalidrawViewMode(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function gives you a more hands on access to Excalidraw.
|
||||
* @param scene - The scene you want to load to Excalidraw
|
||||
* @param restore - Use this if the scene includes legacy excalidraw file elements that need to be converted to the latest excalidraw data format (not a typical usecase)
|
||||
* @returns
|
||||
*/
|
||||
viewUpdateScene (
|
||||
scene: {
|
||||
elements?: ExcalidrawElement[],
|
||||
appState?: AppState,
|
||||
files?: BinaryFileData,
|
||||
commitToHistory?: boolean,
|
||||
},
|
||||
restore: boolean = false,
|
||||
):void {
|
||||
//@ts-ignore
|
||||
if (!this.targetView || !this.targetView?._loaded) {
|
||||
errorMessage("targetView not set", "viewToggleFullScreen()");
|
||||
return;
|
||||
}
|
||||
this.targetView.updateScene(scene,restore);
|
||||
}
|
||||
|
||||
/**
|
||||
* zoom tarteView to fit elements provided as input
|
||||
* elements === [] will zoom to fit the entire scene
|
||||
* selectElements toggles whether the elements should be in a selected state at the end of the operation
|
||||
* @param selectElements
|
||||
* @param elements
|
||||
*/
|
||||
viewZoomToElements(
|
||||
selectElements: boolean,
|
||||
elements: ExcalidrawElement[]
|
||||
):void {
|
||||
//@ts-ignore
|
||||
if (!this.targetView || !this.targetView?._loaded) {
|
||||
errorMessage("targetView not set", "viewToggleFullScreen()");
|
||||
return;
|
||||
}
|
||||
this.targetView.zoomToElements(selectElements,elements);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param forceViewMode
|
||||
* @returns
|
||||
*/
|
||||
viewToggleFullScreen(forceViewMode: boolean = false): void {
|
||||
if (app.isMobile) {
|
||||
errorMessage("mobile not supported", "viewToggleFullScreen()");
|
||||
return;
|
||||
}
|
||||
//@ts-ignore
|
||||
if (!this.targetView || !this.targetView?._loaded) {
|
||||
errorMessage("targetView not set", "viewToggleFullScreen()");
|
||||
return;
|
||||
}
|
||||
const view = this.targetView as ExcalidrawView;
|
||||
const isFullscreen = view.isFullscreen();
|
||||
if (forceViewMode) {
|
||||
const ref = this.getExcalidrawAPI();
|
||||
this.targetView.updateScene({
|
||||
view.updateScene({
|
||||
//elements: ref.getSceneElements(),
|
||||
appState: {
|
||||
viewModeEnabled: true,
|
||||
...ref.appState,
|
||||
viewModeEnabled: !isFullscreen,
|
||||
},
|
||||
commitToHistory: false,
|
||||
});
|
||||
this.targetView.toolsPanelRef?.current?.setExcalidrawViewMode(!isFullscreen);
|
||||
}
|
||||
const view = this.targetView as ExcalidrawView;
|
||||
if (view.isFullscreen()) {
|
||||
|
||||
if (isFullscreen) {
|
||||
view.exitFullscreen();
|
||||
} else {
|
||||
view.gotoFullscreen();
|
||||
@@ -1723,7 +1865,7 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
* @returns
|
||||
*/
|
||||
verifyMinimumPluginVersion(requiredVersion: string): boolean {
|
||||
return PLUGIN_VERSION === requiredVersion || isVersionNewerThanOther(PLUGIN_VERSION,requiredVersion);
|
||||
return verifyMinimumPluginVersion(requiredVersion);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -2042,6 +2184,14 @@ async function getTemplate(
|
||||
}
|
||||
}
|
||||
|
||||
if(filenameParts.hasTaskbone) {
|
||||
groupElements = groupElements.filter( el =>
|
||||
el.type==="freedraw" ||
|
||||
( el.type==="image" &&
|
||||
!plugin.isExcalidrawFile(excalidrawData.getFile(el.fileId)?.file)
|
||||
));
|
||||
}
|
||||
|
||||
return {
|
||||
elements: groupElements,
|
||||
appState: scene.appState,
|
||||
@@ -2071,6 +2221,7 @@ export async function createPNG(
|
||||
plugin: ExcalidrawPlugin,
|
||||
depth: number,
|
||||
padding?: number,
|
||||
imagesDict?: any,
|
||||
) {
|
||||
if (!loader) {
|
||||
loader = new EmbeddedFilesLoader(plugin);
|
||||
@@ -2081,18 +2232,25 @@ export async function createPNG(
|
||||
: null;
|
||||
let elements = template?.elements ?? [];
|
||||
elements = elements.concat(automateElements);
|
||||
const files = imagesDict ?? {};
|
||||
if(template?.files) {
|
||||
Object.values(template.files).forEach((f:any)=>{
|
||||
files[f.id]=f;
|
||||
});
|
||||
}
|
||||
|
||||
return await getPNG(
|
||||
{
|
||||
type: "excalidraw",
|
||||
version: 2,
|
||||
source: "https://excalidraw.com",
|
||||
source: GITHUB_RELEASES+PLUGIN_VERSION,
|
||||
elements,
|
||||
appState: {
|
||||
theme: forceTheme ?? template?.appState?.theme ?? canvasTheme,
|
||||
viewBackgroundColor:
|
||||
template?.appState?.viewBackgroundColor ?? canvasBackgroundColor,
|
||||
},
|
||||
files: template?.files ?? {},
|
||||
files,
|
||||
},
|
||||
{
|
||||
withBackground:
|
||||
@@ -2116,6 +2274,7 @@ export async function createSVG(
|
||||
plugin: ExcalidrawPlugin,
|
||||
depth: number,
|
||||
padding?: number,
|
||||
imagesDict?: any,
|
||||
): Promise<SVGSVGElement> {
|
||||
if (!loader) {
|
||||
loader = new EmbeddedFilesLoader(plugin);
|
||||
@@ -2126,19 +2285,25 @@ export async function createSVG(
|
||||
let elements = template?.elements ?? [];
|
||||
elements = elements.concat(automateElements);
|
||||
padding = padding ?? plugin.settings.exportPaddingSVG;
|
||||
const files = imagesDict ?? {};
|
||||
if(template?.files) {
|
||||
Object.values(template.files).forEach((f:any)=>{
|
||||
files[f.id]=f;
|
||||
});
|
||||
}
|
||||
const svg = await getSVG(
|
||||
{
|
||||
//createAndOpenDrawing
|
||||
type: "excalidraw",
|
||||
version: 2,
|
||||
source: "https://excalidraw.com",
|
||||
source: GITHUB_RELEASES+PLUGIN_VERSION,
|
||||
elements,
|
||||
appState: {
|
||||
theme: forceTheme ?? template?.appState?.theme ?? canvasTheme,
|
||||
viewBackgroundColor:
|
||||
template?.appState?.viewBackgroundColor ?? canvasBackgroundColor,
|
||||
},
|
||||
files: template?.files ?? {},
|
||||
files,
|
||||
},
|
||||
{
|
||||
withBackground:
|
||||
@@ -2335,4 +2500,8 @@ export const cloneElement = (el: ExcalidrawElement):any => {
|
||||
updated: Date.now(),
|
||||
versionNonce: Math.floor(Math.random() * 1000000000),
|
||||
}
|
||||
}
|
||||
|
||||
export const verifyMinimumPluginVersion = (requiredVersion: string): boolean => {
|
||||
return PLUGIN_VERSION === requiredVersion || isVersionNewerThanOther(PLUGIN_VERSION,requiredVersion);
|
||||
}
|
||||
@@ -17,8 +17,9 @@ import {
|
||||
FRONTMATTER_KEY_LINKBUTTON_OPACITY,
|
||||
FRONTMATTER_KEY_ONLOAD_SCRIPT,
|
||||
FRONTMATTER_KEY_AUTOEXPORT,
|
||||
DEVICE,
|
||||
} from "./Constants";
|
||||
import { _measureText } from "./ExcalidrawAutomate";
|
||||
import { verifyMinimumPluginVersion, _measureText } from "./ExcalidrawAutomate";
|
||||
import ExcalidrawPlugin from "./main";
|
||||
import { JSON_parse } from "./Constants";
|
||||
import { TextMode } from "./ExcalidrawView";
|
||||
@@ -27,11 +28,13 @@ import {
|
||||
decompress,
|
||||
//getBakPath,
|
||||
getBinaryFileFromDataURL,
|
||||
getContainerElement,
|
||||
getExportTheme,
|
||||
getLinkParts,
|
||||
hasExportTheme,
|
||||
isVersionNewerThanOther,
|
||||
LinkParts,
|
||||
wrapText,
|
||||
wrapTextAtCharLength,
|
||||
} from "./utils/Utils";
|
||||
import { getAttachmentsFolderAndFilePath, isObsidianThemeDark } from "./utils/ObsidianUtils";
|
||||
import {
|
||||
@@ -39,8 +42,8 @@ import {
|
||||
ExcalidrawImageElement,
|
||||
FileId,
|
||||
} from "@zsviczian/excalidraw/types/element/types";
|
||||
import { BinaryFiles, SceneData } from "@zsviczian/excalidraw/types/types";
|
||||
import { EmbeddedFile } from "./EmbeddedFileLoader";
|
||||
import { BinaryFiles, DataURL, SceneData } from "@zsviczian/excalidraw/types/types";
|
||||
import { EmbeddedFile, MimeType } from "./EmbeddedFileLoader";
|
||||
|
||||
type SceneDataWithFiles = SceneData & { files: BinaryFiles };
|
||||
|
||||
@@ -52,6 +55,13 @@ declare module "obsidian" {
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
wrapText,
|
||||
getFontString,
|
||||
getMaxContainerWidth,
|
||||
//@ts-ignore
|
||||
} = excalidrawLib;
|
||||
|
||||
export enum AutoexportPreference {
|
||||
none,
|
||||
both,
|
||||
@@ -210,15 +220,16 @@ const estimateMaxLineLen = (text: string, originalText: string): number => {
|
||||
return null;
|
||||
}
|
||||
for (const line of splitText) {
|
||||
if (line.length > maxLineLen) {
|
||||
maxLineLen = line.length;
|
||||
const l = line.trim();
|
||||
if (l.length > maxLineLen) {
|
||||
maxLineLen = l.length;
|
||||
}
|
||||
}
|
||||
return maxLineLen;
|
||||
};
|
||||
|
||||
const wrap = (text: string, lineLen: number) =>
|
||||
lineLen ? wrapText(text, lineLen, false, 0) : text;
|
||||
lineLen ? wrapTextAtCharLength(text, lineLen, false, 0) : text;
|
||||
|
||||
export class ExcalidrawData {
|
||||
public textElements: Map<
|
||||
@@ -228,7 +239,7 @@ export class ExcalidrawData {
|
||||
public elementLinks: Map<string, string> = null;
|
||||
public scene: any = null;
|
||||
public deletedElements: ExcalidrawElement[] = [];
|
||||
private file: TFile = null;
|
||||
public file: TFile = null;
|
||||
private app: App;
|
||||
private showLinkBrackets: boolean;
|
||||
private linkPrefix: string;
|
||||
@@ -257,6 +268,8 @@ export class ExcalidrawData {
|
||||
return;
|
||||
}
|
||||
|
||||
const saveVersion = this.scene.source.split("https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/")[1]??"1.8.16";
|
||||
|
||||
const elements = this.scene.elements;
|
||||
for (const el of elements) {
|
||||
if (el.boundElements) {
|
||||
@@ -347,12 +360,17 @@ export class ExcalidrawData {
|
||||
} catch (e) {}
|
||||
});
|
||||
|
||||
const ellipseAndRhombusContainerWrapping = !isVersionNewerThanOther(saveVersion,"1.8.16");
|
||||
|
||||
//Remove from bound elements references that do not exist in the scene
|
||||
const containers = elements.filter(
|
||||
(container: any) =>
|
||||
container.boundElements && container.boundElements.length > 0,
|
||||
);
|
||||
containers.forEach((container: any) => {
|
||||
if(ellipseAndRhombusContainerWrapping && !container.customData?.legacyTextWrap) {
|
||||
container.customData = {...container.customData, legacyTextWrap: true};
|
||||
}
|
||||
const filteredBoundElements = container.boundElements.filter(
|
||||
(boundEl: any) => elements.some((el: any) => el.id === boundEl.id),
|
||||
);
|
||||
@@ -508,8 +526,9 @@ export class ExcalidrawData {
|
||||
if(!elementLink.done) {
|
||||
text = text.replace(/^%%\*\*\*>>>text element-link:\[\[[^<*\]]*]]<<<\*\*\*%%/gm,"");
|
||||
textEl.link = elementLink.value[1];
|
||||
}
|
||||
}
|
||||
const parseRes = await this.parse(text);
|
||||
textEl.rawText = text;
|
||||
this.textElements.set(id, {
|
||||
raw: text,
|
||||
parsed: parseRes.parsed,
|
||||
@@ -542,6 +561,18 @@ export class ExcalidrawData {
|
||||
this.setFile(parts.value[1] as FileId, embeddedFile);
|
||||
}
|
||||
|
||||
//Load links
|
||||
const REG_LINKID_FILEPATH = /([\w\d]*):\s*(https?:\/\/[^\s]*)\n/gm;
|
||||
res = data.matchAll(REG_LINKID_FILEPATH);
|
||||
while (!(parts = res.next()).done) {
|
||||
const embeddedFile = new EmbeddedFile(
|
||||
this.plugin,
|
||||
null,
|
||||
parts.value[2],
|
||||
);
|
||||
this.setFile(parts.value[1] as FileId, embeddedFile);
|
||||
}
|
||||
|
||||
//Load Equations
|
||||
const REG_FILEID_EQUATION = /([\w\d]*):\s*\$\$(.*)(\$\$\s*\n)/gm;
|
||||
res = data.matchAll(REG_FILEID_EQUATION);
|
||||
@@ -606,6 +637,7 @@ export class ExcalidrawData {
|
||||
newText: string,
|
||||
newOriginalText: string,
|
||||
forceUpdate: boolean = false,
|
||||
containerType?: string,
|
||||
) {
|
||||
if (forceUpdate || newText != sceneTextElement.text) {
|
||||
const measure = _measureText(
|
||||
@@ -616,7 +648,7 @@ export class ExcalidrawData {
|
||||
sceneTextElement.text = newText;
|
||||
sceneTextElement.originalText = newOriginalText;
|
||||
|
||||
if (!sceneTextElement.containerId) {
|
||||
if (!sceneTextElement.containerId || containerType==="arrow") {
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/376
|
||||
//I leave the setting of text width to excalidraw, when text is in a container
|
||||
//because text width is fixed to the container width
|
||||
@@ -638,21 +670,26 @@ export class ExcalidrawData {
|
||||
//first get scene text elements
|
||||
const texts = this.scene.elements?.filter((el: any) => el.type === "text");
|
||||
for (const te of texts) {
|
||||
const container = getContainerElement(te,this.scene);
|
||||
const originalText =
|
||||
(await this.getText(te.id, false)) ?? te.originalText ?? te.text;
|
||||
(await this.getText(te.id)) ?? te.originalText ?? te.text;
|
||||
const wrapAt = this.textElements.get(te.id)?.wrapAt;
|
||||
this.updateTextElement(
|
||||
te,
|
||||
wrap(originalText, wrapAt),
|
||||
wrapAt ? wrapText(
|
||||
originalText,
|
||||
getFontString({fontSize: te.fontSize, fontFamily: te.fontFamily}),
|
||||
getMaxContainerWidth(container)
|
||||
) : originalText,
|
||||
originalText,
|
||||
forceupdate,
|
||||
container?.type,
|
||||
); //(await this.getText(te.id))??te.text serves the case when the whole #Text Elements section is deleted by accident
|
||||
}
|
||||
}
|
||||
|
||||
private async getText(
|
||||
id: string,
|
||||
wrapResult: boolean = true,
|
||||
): Promise<string> {
|
||||
const text = this.textElements.get(id);
|
||||
if (!text) {
|
||||
@@ -667,13 +704,14 @@ export class ExcalidrawData {
|
||||
});
|
||||
}
|
||||
//console.log("parsed",this.textElements.get(id).parsed);
|
||||
return wrapResult ? wrap(text.parsed, text.wrapAt) : text.parsed;
|
||||
return text.parsed;
|
||||
}
|
||||
//console.log("raw",this.textElements.get(id).raw);
|
||||
return text.raw;
|
||||
}
|
||||
|
||||
private findNewElementLinksInScene(): boolean {
|
||||
let result = false;
|
||||
const elements = this.scene.elements?.filter((el: any) => {
|
||||
return (
|
||||
el.type !== "text" &&
|
||||
@@ -683,25 +721,27 @@ export class ExcalidrawData {
|
||||
);
|
||||
});
|
||||
if (elements.length === 0) {
|
||||
return false;
|
||||
return result;
|
||||
}
|
||||
|
||||
let jsonString = JSON.stringify(this.scene);
|
||||
|
||||
let id: string; //will be used to hold the new 8 char long ID for textelements that don't yet appear under # Text Elements
|
||||
|
||||
for (const el of elements) {
|
||||
id = el.id;
|
||||
//replacing Excalidraw element IDs with my own nanoid, because default IDs may contain
|
||||
//characters not recognized by Obsidian block references
|
||||
//also Excalidraw IDs are inconveniently long
|
||||
if (el.id.length > 8) {
|
||||
result = true;
|
||||
id = nanoid();
|
||||
jsonString = jsonString.replaceAll(el.id, id); //brute force approach to replace all occurances (e.g. links, groups,etc.)
|
||||
}
|
||||
this.elementLinks.set(id, el.link);
|
||||
}
|
||||
this.scene = JSON.parse(jsonString);
|
||||
return true;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -794,7 +834,7 @@ export class ExcalidrawData {
|
||||
if (el.length === 0) {
|
||||
this.textElements.delete(key); //if no longer in the scene, delete the text element
|
||||
} else {
|
||||
const text = await this.getText(key, false);
|
||||
const text = await this.getText(key);
|
||||
const raw = this.scene.prevTextMode === TextMode.parsed
|
||||
? el[0].rawText
|
||||
: (el[0].originalText ?? el[0].text);
|
||||
@@ -887,7 +927,7 @@ export class ExcalidrawData {
|
||||
}
|
||||
outString +=
|
||||
text.substring(position, parts.value.index) +
|
||||
wrapText(
|
||||
wrapTextAtCharLength(
|
||||
contents,
|
||||
REGEX_LINK.getWrapLength(
|
||||
parts,
|
||||
@@ -1032,11 +1072,15 @@ export class ExcalidrawData {
|
||||
for (const key of this.files.keys()) {
|
||||
const PATHREG = /(^[^#\|]*)/;
|
||||
const ef = this.files.get(key);
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/829
|
||||
const path = ef.file
|
||||
? ef.linkParts.original.replace(PATHREG,app.metadataCache.fileToLinktext(ef.file,this.file.path))
|
||||
: ef.linkParts.original;
|
||||
outString += `${key}: [[${path}]]\n`;
|
||||
if(ef.isHyperlink) {
|
||||
outString += `${key}: ${ef.hyperlink}\n`;
|
||||
} else {
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/829
|
||||
const path = ef.file
|
||||
? ef.linkParts.original.replace(PATHREG,app.metadataCache.fileToLinktext(ef.file,this.file.path))
|
||||
: ef.linkParts.original;
|
||||
outString += `${key}: [[${path}]]\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
outString += this.equations.size > 0 || this.files.size > 0 ? "\n" : "";
|
||||
@@ -1058,6 +1102,54 @@ export class ExcalidrawData {
|
||||
);
|
||||
}
|
||||
|
||||
public async saveDataURLtoVault(dataURL: DataURL, mimeType: MimeType, key: FileId) {
|
||||
const scene = this.scene as SceneDataWithFiles;
|
||||
let fname = `Pasted Image ${window
|
||||
.moment()
|
||||
.format("YYYYMMDDHHmmss_SSS")}`;
|
||||
|
||||
switch (mimeType) {
|
||||
case "image/png":
|
||||
fname += ".png";
|
||||
break;
|
||||
case "image/jpeg":
|
||||
fname += ".jpg";
|
||||
break;
|
||||
case "image/svg+xml":
|
||||
fname += ".svg";
|
||||
break;
|
||||
case "image/gif":
|
||||
fname += ".gif";
|
||||
break;
|
||||
default:
|
||||
fname += ".png";
|
||||
}
|
||||
const filepath = (
|
||||
await getAttachmentsFolderAndFilePath(this.app, this.file.path, fname)
|
||||
).filepath;
|
||||
|
||||
const file = await this.app.vault.createBinary(
|
||||
filepath,
|
||||
getBinaryFileFromDataURL(dataURL),
|
||||
);
|
||||
|
||||
const embeddedFile = new EmbeddedFile(
|
||||
this.plugin,
|
||||
this.file.path,
|
||||
filepath,
|
||||
);
|
||||
|
||||
embeddedFile.setImage(
|
||||
dataURL,
|
||||
mimeType,
|
||||
{ height: 0, width: 0 },
|
||||
scene.appState?.theme === "dark",
|
||||
mimeType === "image/svg+xml", //this treat all SVGs as if they had embedded images REF:addIMAGE
|
||||
);
|
||||
this.setFile(key as FileId, embeddedFile);
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* deletes fileIds from Excalidraw data for files no longer in the scene
|
||||
* @returns
|
||||
@@ -1103,7 +1195,7 @@ export class ExcalidrawData {
|
||||
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 && (file.file.extension !== "md" || this.plugin.isExcalidrawFile(file.file))) {
|
||||
if(file && (file.isHyperlink || (file.file && (file.file.extension !== "md" || this.plugin.isExcalidrawFile(file.file))))) {
|
||||
return;
|
||||
}
|
||||
const newId = fileid();
|
||||
@@ -1127,47 +1219,11 @@ export class ExcalidrawData {
|
||||
for (const key of Object.keys(scene.files)) {
|
||||
if (!(this.hasFile(key as FileId) || this.hasEquation(key as FileId))) {
|
||||
dirty = true;
|
||||
let fname = `Pasted Image ${window
|
||||
.moment()
|
||||
.format("YYYYMMDDHHmmss_SSS")}`;
|
||||
const mimeType = scene.files[key].mimeType;
|
||||
switch (mimeType) {
|
||||
case "image/png":
|
||||
fname += ".png";
|
||||
break;
|
||||
case "image/jpeg":
|
||||
fname += ".jpg";
|
||||
break;
|
||||
case "image/svg+xml":
|
||||
fname += ".svg";
|
||||
break;
|
||||
case "image/gif":
|
||||
fname += ".gif";
|
||||
break;
|
||||
default:
|
||||
fname += ".png";
|
||||
}
|
||||
const filepath = (
|
||||
await getAttachmentsFolderAndFilePath(this.app, this.file.path, fname)
|
||||
).filepath;
|
||||
const dataURL = scene.files[key].dataURL;
|
||||
await this.app.vault.createBinary(
|
||||
filepath,
|
||||
getBinaryFileFromDataURL(dataURL),
|
||||
await this.saveDataURLtoVault(
|
||||
scene.files[key].dataURL,
|
||||
scene.files[key].mimeType,
|
||||
key as FileId
|
||||
);
|
||||
const embeddedFile = new EmbeddedFile(
|
||||
this.plugin,
|
||||
this.file.path,
|
||||
filepath,
|
||||
);
|
||||
embeddedFile.setImage(
|
||||
dataURL,
|
||||
mimeType,
|
||||
{ height: 0, width: 0 },
|
||||
scene.appState?.theme === "dark",
|
||||
mimeType === "image/svg+xml", //this treat all SVGs as if they had embedded images REF:addIMAGE
|
||||
);
|
||||
this.setFile(key as FileId, embeddedFile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1310,7 +1366,9 @@ export class ExcalidrawData {
|
||||
|
||||
public getOpenMode(): { viewModeEnabled: boolean; zenModeEnabled: boolean } {
|
||||
const fileCache = this.app.metadataCache.getFileCache(this.file);
|
||||
let mode = this.plugin.settings.defaultMode;
|
||||
let mode = this.plugin.settings.defaultMode === "view-mobile"
|
||||
? (DEVICE.isPhone ? "view" : "normal")
|
||||
: this.plugin.settings.defaultMode;
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEY_DEFAULT_MODE] != null
|
||||
@@ -1428,13 +1486,24 @@ export class ExcalidrawData {
|
||||
}
|
||||
this.files.set(fileId, data);
|
||||
|
||||
if(data.isHyperlink) {
|
||||
this.plugin.filesMaster.set(fileId, {
|
||||
isHyperlink: true,
|
||||
path: data.hyperlink,
|
||||
blockrefData: null,
|
||||
hasSVGwithBitmap: data.isSVGwithBitmap
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data.file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parts = data.linkParts.original.split("#");
|
||||
this.plugin.filesMaster.set(fileId, {
|
||||
path:data.file.path,
|
||||
isHyperlink: false,
|
||||
path:data.file.path + (data.shouldScale()?"":"|100%"),
|
||||
blockrefData: parts.length === 1
|
||||
? null
|
||||
: parts[1],
|
||||
@@ -1479,16 +1548,25 @@ export class ExcalidrawData {
|
||||
}
|
||||
if (this.plugin.filesMaster.has(fileId)) {
|
||||
const masterFile = this.plugin.filesMaster.get(fileId);
|
||||
if (!this.app.vault.getAbstractFileByPath(masterFile.path)) {
|
||||
if(masterFile.isHyperlink) {
|
||||
this.files.set(
|
||||
fileId,
|
||||
new EmbeddedFile(this.plugin,this.file.path,masterFile.path)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
const path = masterFile.path.split("|")[0].split("#")[0];
|
||||
if (!this.app.vault.getAbstractFileByPath(path)) {
|
||||
this.plugin.filesMaster.delete(fileId);
|
||||
return true;
|
||||
} // the file no longer exists
|
||||
const fixScale = masterFile.path.endsWith("100%");
|
||||
const embeddedFile = new EmbeddedFile(
|
||||
this.plugin,
|
||||
this.file.path,
|
||||
masterFile.blockrefData
|
||||
? masterFile.path + "#" + masterFile.blockrefData
|
||||
: masterFile.path
|
||||
(masterFile.blockrefData
|
||||
? path + "#" + masterFile.blockrefData
|
||||
: path) + (fixScale?"|100%":"")
|
||||
);
|
||||
this.files.set(fileId, embeddedFile);
|
||||
return true;
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
TFile,
|
||||
Vault,
|
||||
} from "obsidian";
|
||||
import { CTRL_OR_CMD, RERENDER_EVENT } from "./Constants";
|
||||
import { RERENDER_EVENT } from "./Constants";
|
||||
import { EmbeddedFilesLoader } from "./EmbeddedFileLoader";
|
||||
import { createPNG, createSVG } from "./ExcalidrawAutomate";
|
||||
import { ExportSettings } from "./ExcalidrawView";
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
svgToBase64,
|
||||
} from "./utils/Utils";
|
||||
import { isObsidianThemeDark } from "./utils/ObsidianUtils";
|
||||
import { isCTRL, isMETA, linkClickModifierType } from "./utils/ModifierkeyHelper";
|
||||
|
||||
interface imgElementAttributes {
|
||||
file?: TFile;
|
||||
@@ -55,6 +56,7 @@ export const initializeMarkdownPostProcessor = (p: ExcalidrawPlugin) => {
|
||||
*/
|
||||
const getIMG = async (
|
||||
imgAttributes: imgElementAttributes,
|
||||
onCanvas: boolean = false,
|
||||
): Promise<HTMLElement> => {
|
||||
let file = imgAttributes.file;
|
||||
if (!imgAttributes.file) {
|
||||
@@ -79,11 +81,11 @@ const getIMG = async (
|
||||
withTheme: forceTheme ? true : plugin.settings.exportWithTheme,
|
||||
};
|
||||
const img = createEl("img");
|
||||
let style = `max-width:${imgAttributes.fwidth}px !important; width:100%;`;
|
||||
let style = `max-width:${imgAttributes.fwidth}${imgAttributes.fwidth.match(/\d$/) ? "px":""}; `; //width:100%;`; //removed !important https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/886
|
||||
if (imgAttributes.fheight) {
|
||||
style += `height:${imgAttributes.fheight}px;`;
|
||||
}
|
||||
img.setAttribute("style", style);
|
||||
if(!onCanvas) img.setAttribute("style", style);
|
||||
img.addClass(imgAttributes.style);
|
||||
|
||||
const theme =
|
||||
@@ -179,60 +181,96 @@ const getIMG = async (
|
||||
return null;
|
||||
}
|
||||
svg = embedFontsInSVG(svg, plugin);
|
||||
svg.removeAttribute("width");
|
||||
svg.removeAttribute("height");
|
||||
//svg.removeAttribute("width");
|
||||
//svg.removeAttribute("height");
|
||||
img.setAttribute("src", svgToBase64(svg.outerHTML));
|
||||
return img;
|
||||
};
|
||||
|
||||
const createImgElement = async (
|
||||
attr: imgElementAttributes,
|
||||
onCanvas: boolean = false,
|
||||
) :Promise<HTMLElement> => {
|
||||
const img = await getIMG(attr,onCanvas);
|
||||
img.setAttribute("fileSource", attr.fname);
|
||||
if (attr.fwidth) {
|
||||
img.setAttribute("w", attr.fwidth);
|
||||
}
|
||||
if (attr.fheight) {
|
||||
img.setAttribute("h", attr.fheight);
|
||||
}
|
||||
img.setAttribute("draggable","false");
|
||||
img.setAttribute("onCanvas",onCanvas?"true":"false");
|
||||
|
||||
let timer:NodeJS.Timeout;
|
||||
const clickEvent = (ev:PointerEvent) => {
|
||||
if (
|
||||
ev.target instanceof Element &&
|
||||
ev.target.tagName.toLowerCase() != "img"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const src = img.getAttribute("fileSource");
|
||||
if (src) {
|
||||
const srcParts = src.match(/([^#]*)(.*)/);
|
||||
if(!srcParts) return;
|
||||
plugin.openDrawing(
|
||||
vault.getAbstractFileByPath(srcParts[1]) as TFile,
|
||||
linkClickModifierType(ev),
|
||||
true,
|
||||
srcParts[2],
|
||||
);
|
||||
} //.ctrlKey||ev.metaKey);
|
||||
};
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1003
|
||||
let pointerDownEvent:any;
|
||||
img.addEventListener("pointermove",(ev)=>{
|
||||
if(!timer) return;
|
||||
if(Math.abs(ev.screenX-pointerDownEvent.screenX)>10 || Math.abs(ev.screenY-pointerDownEvent.screenY)>10) {
|
||||
clearTimeout(timer);
|
||||
timer = null;
|
||||
}
|
||||
});
|
||||
img.addEventListener("pointerdown",(ev)=>{
|
||||
if(img?.parentElement?.hasClass("canvas-node-content")) return;
|
||||
timer = setTimeout(()=>clickEvent(ev),500);
|
||||
pointerDownEvent = ev;
|
||||
});
|
||||
img.addEventListener("pointerup",()=>{
|
||||
if(timer) clearTimeout(timer);
|
||||
timer = null;
|
||||
})
|
||||
img.addEventListener("dblclick",clickEvent);
|
||||
img.addEventListener(RERENDER_EVENT, async (e) => {
|
||||
e.stopPropagation();
|
||||
const parent = img.parentElement;
|
||||
const imgMaxWidth = img.style.maxWidth;
|
||||
const imgMaxHeigth = img.style.maxHeight;
|
||||
const fileSource = img.getAttribute("fileSource");
|
||||
const onCanvas = img.getAttribute("onCanvas") === "true";
|
||||
const newImg = await createImgElement({
|
||||
fname: fileSource,
|
||||
fwidth: img.getAttribute("w"),
|
||||
fheight: img.getAttribute("h"),
|
||||
style: img.getAttribute("class"),
|
||||
}, onCanvas);
|
||||
parent.empty();
|
||||
if(!onCanvas) {
|
||||
newImg.style.maxHeight = imgMaxHeigth;
|
||||
newImg.style.maxWidth = imgMaxWidth;
|
||||
}
|
||||
newImg.setAttribute("fileSource",fileSource);
|
||||
parent.append(newImg);
|
||||
});
|
||||
return img;
|
||||
}
|
||||
|
||||
const createImageDiv = async (
|
||||
attr: imgElementAttributes,
|
||||
onCanvas: boolean = false
|
||||
): Promise<HTMLDivElement> => {
|
||||
const img = await getIMG(attr);
|
||||
return createDiv(attr.style, (el) => {
|
||||
el.append(img);
|
||||
el.setAttribute("src", attr.fname);
|
||||
if (attr.fwidth) {
|
||||
el.setAttribute("w", attr.fwidth);
|
||||
}
|
||||
if (attr.fheight) {
|
||||
el.setAttribute("h", attr.fheight);
|
||||
}
|
||||
el.onClickEvent((ev) => {
|
||||
if (
|
||||
ev.target instanceof Element &&
|
||||
ev.target.tagName.toLowerCase() != "img"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const src = el.getAttribute("src");
|
||||
if (src) {
|
||||
const srcParts = src.match(/([^#]*)(.*)/);
|
||||
if(!srcParts) return;
|
||||
plugin.openDrawing(
|
||||
vault.getAbstractFileByPath(srcParts[1]) as TFile,
|
||||
ev[CTRL_OR_CMD]
|
||||
? "new-pane"
|
||||
: (ev.metaKey && !app.isMobile)
|
||||
? "popout-window"
|
||||
: "active-pane",
|
||||
true,
|
||||
srcParts[2],
|
||||
);
|
||||
} //.ctrlKey||ev.metaKey);
|
||||
});
|
||||
el.addEventListener(RERENDER_EVENT, async (e) => {
|
||||
e.stopPropagation();
|
||||
el.empty();
|
||||
const img = await getIMG({
|
||||
fname: el.getAttribute("src"),
|
||||
fwidth: el.getAttribute("w"),
|
||||
fheight: el.getAttribute("h"),
|
||||
style: el.getAttribute("class"),
|
||||
});
|
||||
el.append(img);
|
||||
});
|
||||
});
|
||||
const img = await createImgElement(attr, onCanvas);
|
||||
return createDiv(attr.style, (el) => el.append(img));
|
||||
};
|
||||
|
||||
const processReadingMode = async (
|
||||
@@ -336,101 +374,109 @@ const tmpObsidianWYSIWYG = async (
|
||||
return;
|
||||
}
|
||||
|
||||
//The timeout gives time for Obsidian to attach el to the displayed document
|
||||
//Once the element is attached, I can traverse up the dom tree to find .internal-embed
|
||||
//If internal embed is not found, it means the that the excalidraw.md file
|
||||
//is being rendered in "reading" mode. In that case, the image with the default width
|
||||
//specified in setting should be displayed
|
||||
//if .internal-embed is found, then contents is replaced with the image using the
|
||||
//internal-embed: Excalidraw is embedded into a markdown document
|
||||
//markdown-reading-view: we are processing the markdown reading view of an actual Excalidraw file
|
||||
//markdown-embed: we are processing the hover preview of a markdown file
|
||||
//alt, width, and height attributes of .internal-embed to size and style the image
|
||||
setTimeout(async () => {
|
||||
//wait for el to be attached to the displayed document
|
||||
let counter = 0;
|
||||
while(!el.parentElement && counter++<=50) await sleep(50);
|
||||
if(!el.parentElement) return;
|
||||
|
||||
let internalEmbedDiv: HTMLElement = el;
|
||||
while (
|
||||
!internalEmbedDiv.hasClass("dataview") &&
|
||||
!internalEmbedDiv.hasClass("cm-preview-code-block") &&
|
||||
!internalEmbedDiv.hasClass("cm-embed-block") &&
|
||||
!internalEmbedDiv.hasClass("internal-embed") &&
|
||||
internalEmbedDiv.parentElement
|
||||
) {
|
||||
internalEmbedDiv = internalEmbedDiv.parentElement;
|
||||
}
|
||||
|
||||
if(
|
||||
internalEmbedDiv.hasClass("dataview") ||
|
||||
internalEmbedDiv.hasClass("cm-preview-code-block") ||
|
||||
internalEmbedDiv.hasClass("cm-embed-block")
|
||||
) {
|
||||
return; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/835
|
||||
}
|
||||
|
||||
const attr: imgElementAttributes = {
|
||||
fname: ctx.sourcePath,
|
||||
fheight: "",
|
||||
fwidth: getDefaultWidth(plugin),
|
||||
style: "excalidraw-svg",
|
||||
};
|
||||
|
||||
attr.file = file;
|
||||
//@ts-ignore
|
||||
const containerEl = ctx.containerEl;
|
||||
let internalEmbedDiv: HTMLElement = containerEl;
|
||||
while (
|
||||
!internalEmbedDiv.hasClass("dataview") &&
|
||||
!internalEmbedDiv.hasClass("cm-preview-code-block") &&
|
||||
!internalEmbedDiv.hasClass("cm-embed-block") &&
|
||||
!internalEmbedDiv.hasClass("internal-embed") &&
|
||||
!internalEmbedDiv.hasClass("markdown-reading-view") &&
|
||||
!internalEmbedDiv.hasClass("markdown-embed") &&
|
||||
internalEmbedDiv.parentElement
|
||||
) {
|
||||
internalEmbedDiv = internalEmbedDiv.parentElement;
|
||||
}
|
||||
|
||||
if(
|
||||
internalEmbedDiv.hasClass("dataview") ||
|
||||
internalEmbedDiv.hasClass("cm-preview-code-block") ||
|
||||
internalEmbedDiv.hasClass("cm-embed-block")
|
||||
) {
|
||||
return; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/835
|
||||
}
|
||||
|
||||
if (!internalEmbedDiv.hasClass("internal-embed")) {
|
||||
//We are processing the markdown preview of an actual Excalidraw file
|
||||
//This could be in a hover preview of the file
|
||||
//Or the file could be in markdown mode and the user switched markdown
|
||||
//view of the drawing to reading mode
|
||||
el.empty();
|
||||
const mdPreviewSection = el.parentElement;
|
||||
if(!mdPreviewSection.hasClass("markdown-preview-section")) return;
|
||||
if(mdPreviewSection.hasAttribute("ready")) {
|
||||
mdPreviewSection.removeChild(el);
|
||||
return;
|
||||
}
|
||||
mdPreviewSection.setAttribute("ready","");
|
||||
const imgDiv = await createImageDiv(attr);
|
||||
el.appendChild(imgDiv);
|
||||
return;
|
||||
}
|
||||
|
||||
if(isTextOnlyEmbed(internalEmbedDiv)) {
|
||||
//legacy reference to a block or section as text
|
||||
//should be embedded as legacy text
|
||||
return;
|
||||
}
|
||||
const attr: imgElementAttributes = {
|
||||
fname: ctx.sourcePath,
|
||||
fheight: "",
|
||||
fwidth: getDefaultWidth(plugin),
|
||||
style: "excalidraw-svg",
|
||||
};
|
||||
|
||||
attr.file = file;
|
||||
|
||||
const markdownEmbed = internalEmbedDiv.hasClass("markdown-embed");
|
||||
const markdownReadingView = internalEmbedDiv.hasClass("markdown-reading-view");
|
||||
if (!internalEmbedDiv.hasClass("internal-embed") && (markdownEmbed || markdownReadingView)) {
|
||||
//We are processing the markdown preview of an actual Excalidraw file
|
||||
//the excalidraw file in markdown preview mode
|
||||
const isFrontmatterDiv = Boolean(el.querySelector(".frontmatter"));
|
||||
el.empty();
|
||||
|
||||
if(internalEmbedDiv.hasAttribute("ready")) {
|
||||
if(!isFrontmatterDiv) {
|
||||
if(el.parentElement === containerEl) containerEl.removeChild(el);
|
||||
return;
|
||||
}
|
||||
internalEmbedDiv.setAttribute("ready","");
|
||||
|
||||
internalEmbedDiv.empty();
|
||||
const imgDiv = await processInternalEmbed(internalEmbedDiv,file);
|
||||
const onCanvas = internalEmbedDiv.hasClass("canvas-node-content");
|
||||
const imgDiv = await createImageDiv(attr, onCanvas);
|
||||
if(markdownEmbed) {
|
||||
if(onCanvas) {
|
||||
internalEmbedDiv.removeClass("markdown-embed");
|
||||
internalEmbedDiv.addClass("media-embed");
|
||||
internalEmbedDiv.addClass("image-embed");
|
||||
}
|
||||
if(!onCanvas && imgDiv.firstChild instanceof HTMLElement) {
|
||||
imgDiv.firstChild.style.maxHeight = "100%";
|
||||
imgDiv.firstChild.style.maxWidth = null;
|
||||
}
|
||||
internalEmbedDiv.appendChild(imgDiv.firstChild);
|
||||
return;
|
||||
}
|
||||
internalEmbedDiv.appendChild(imgDiv);
|
||||
return;
|
||||
}
|
||||
|
||||
//timer to avoid the image flickering when the user is typing
|
||||
let timer: NodeJS.Timeout = null;
|
||||
const observer = new MutationObserver((m) => {
|
||||
if (!["alt", "width", "height"].contains(m[0]?.attributeName)) {
|
||||
return;
|
||||
}
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
timer = setTimeout(async () => {
|
||||
timer = null;
|
||||
internalEmbedDiv.empty();
|
||||
const imgDiv = await processInternalEmbed(internalEmbedDiv,file);
|
||||
internalEmbedDiv.appendChild(imgDiv);
|
||||
}, 500);
|
||||
});
|
||||
observer.observe(internalEmbedDiv, {
|
||||
attributes: true, //configure it to listen to attribute changes
|
||||
});
|
||||
if(isTextOnlyEmbed(internalEmbedDiv)) {
|
||||
//legacy reference to a block or section as text
|
||||
//should be embedded as legacy text
|
||||
return;
|
||||
}
|
||||
|
||||
el.empty();
|
||||
|
||||
if(internalEmbedDiv.hasAttribute("ready")) {
|
||||
return;
|
||||
}
|
||||
internalEmbedDiv.setAttribute("ready","");
|
||||
|
||||
internalEmbedDiv.empty();
|
||||
const imgDiv = await processInternalEmbed(internalEmbedDiv,file);
|
||||
internalEmbedDiv.appendChild(imgDiv);
|
||||
|
||||
//timer to avoid the image flickering when the user is typing
|
||||
let timer: NodeJS.Timeout = null;
|
||||
const observer = new MutationObserver((m) => {
|
||||
if (!["alt", "width", "height"].contains(m[0]?.attributeName)) {
|
||||
return;
|
||||
}
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
timer = setTimeout(async () => {
|
||||
timer = null;
|
||||
internalEmbedDiv.empty();
|
||||
const imgDiv = await processInternalEmbed(internalEmbedDiv,file);
|
||||
internalEmbedDiv.appendChild(imgDiv);
|
||||
}, 500);
|
||||
});
|
||||
observer.observe(internalEmbedDiv, {
|
||||
attributes: true, //configure it to listen to attribute changes
|
||||
});
|
||||
};
|
||||
|
||||
@@ -550,11 +596,7 @@ export const observer = new MutationObserver(async (m) => {
|
||||
if (src) {
|
||||
plugin.openDrawing(
|
||||
vault.getAbstractFileByPath(src) as TFile,
|
||||
ev[CTRL_OR_CMD]
|
||||
? "new-pane"
|
||||
: (ev.metaKey && !app.isMobile)
|
||||
? "popout-window"
|
||||
: "active-pane",
|
||||
linkClickModifierType(ev)
|
||||
);
|
||||
} //.ctrlKey||ev.metaKey);
|
||||
});
|
||||
|
||||
577
src/MarkdownPostProcessor_Legacy.ts
Normal file
@@ -0,0 +1,577 @@
|
||||
import {
|
||||
MarkdownPostProcessorContext,
|
||||
MetadataCache,
|
||||
TFile,
|
||||
Vault,
|
||||
} from "obsidian";
|
||||
import { RERENDER_EVENT } from "./Constants";
|
||||
import { EmbeddedFilesLoader } from "./EmbeddedFileLoader";
|
||||
import { createPNG, createSVG } from "./ExcalidrawAutomate";
|
||||
import { ExportSettings } from "./ExcalidrawView";
|
||||
import ExcalidrawPlugin from "./main";
|
||||
import {getIMGFilename,} from "./utils/FileUtils";
|
||||
import {
|
||||
embedFontsInSVG,
|
||||
getEmbeddedFilenameParts,
|
||||
getExportTheme,
|
||||
getQuickImagePreview,
|
||||
getExportPadding,
|
||||
getWithBackground,
|
||||
hasExportTheme,
|
||||
svgToBase64,
|
||||
} from "./utils/Utils";
|
||||
import { isObsidianThemeDark } from "./utils/ObsidianUtils";
|
||||
import { isCTRL, isMETA, linkClickModifierType } from "./utils/ModifierkeyHelper";
|
||||
|
||||
interface imgElementAttributes {
|
||||
file?: TFile;
|
||||
fname: string; //Excalidraw filename
|
||||
fwidth: string; //Display width of image
|
||||
fheight: string; //Display height of image
|
||||
style: string; //css style to apply to IMG element
|
||||
}
|
||||
|
||||
let plugin: ExcalidrawPlugin;
|
||||
let vault: Vault;
|
||||
let metadataCache: MetadataCache;
|
||||
|
||||
const getDefaultWidth = (plugin: ExcalidrawPlugin): string => {
|
||||
const width = parseInt(plugin.settings.width);
|
||||
if (isNaN(width) || width === 0 || width === null) {
|
||||
return "400";
|
||||
}
|
||||
return plugin.settings.width;
|
||||
};
|
||||
|
||||
export const initializeMarkdownPostProcessor_Legacy = (p: ExcalidrawPlugin) => {
|
||||
plugin = p;
|
||||
vault = p.app.vault;
|
||||
metadataCache = p.app.metadataCache;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates an img element with the drawing encoded as a base64 SVG or a PNG (depending on settings)
|
||||
* @param parts {imgElementAttributes} - display properties of the image
|
||||
* @returns {Promise<HTMLElement>} - the IMG HTML element containing the image
|
||||
*/
|
||||
const getIMG = async (
|
||||
imgAttributes: imgElementAttributes,
|
||||
): Promise<HTMLElement> => {
|
||||
let file = imgAttributes.file;
|
||||
if (!imgAttributes.file) {
|
||||
const f = vault.getAbstractFileByPath(imgAttributes.fname?.split("#")[0]);
|
||||
if (!(f && f instanceof TFile)) {
|
||||
return null;
|
||||
}
|
||||
file = f;
|
||||
}
|
||||
|
||||
const filenameParts = getEmbeddedFilenameParts(imgAttributes.fname);
|
||||
|
||||
// https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/387
|
||||
imgAttributes.style = imgAttributes.style.replaceAll(" ", "-");
|
||||
|
||||
const forceTheme = hasExportTheme(plugin, file)
|
||||
? getExportTheme(plugin, file, "light")
|
||||
: undefined;
|
||||
|
||||
const exportSettings: ExportSettings = {
|
||||
withBackground: getWithBackground(plugin, file),
|
||||
withTheme: forceTheme ? true : plugin.settings.exportWithTheme,
|
||||
};
|
||||
const img = createEl("img");
|
||||
let style = `max-width:${imgAttributes.fwidth}px; width:100%;`; //removed !important https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/886
|
||||
if (imgAttributes.fheight) {
|
||||
style += `height:${imgAttributes.fheight}px;`;
|
||||
}
|
||||
img.setAttribute("style", style);
|
||||
img.addClass(imgAttributes.style);
|
||||
|
||||
const theme =
|
||||
forceTheme ??
|
||||
(plugin.settings.previewMatchObsidianTheme
|
||||
? isObsidianThemeDark()
|
||||
? "dark"
|
||||
: "light"
|
||||
: !plugin.settings.exportWithTheme
|
||||
? "light"
|
||||
: undefined);
|
||||
if (theme) {
|
||||
exportSettings.withTheme = true;
|
||||
}
|
||||
const loader = new EmbeddedFilesLoader(
|
||||
plugin,
|
||||
theme ? theme === "dark" : undefined,
|
||||
);
|
||||
|
||||
if (!plugin.settings.displaySVGInPreview) {
|
||||
const width = parseInt(imgAttributes.fwidth);
|
||||
const scale = width >= 2400
|
||||
? 5
|
||||
: width >= 1800
|
||||
? 4
|
||||
: width >= 1200
|
||||
? 3
|
||||
: width >= 600
|
||||
? 2
|
||||
: 1;
|
||||
|
||||
//In case of PNG I cannot change the viewBox to select the area of the element
|
||||
//being referenced. For PNG only the group reference works
|
||||
const quickPNG = !filenameParts.hasGroupref
|
||||
? await getQuickImagePreview(plugin, file.path, "png")
|
||||
: undefined;
|
||||
|
||||
const png =
|
||||
quickPNG ??
|
||||
(await createPNG(
|
||||
filenameParts.hasGroupref
|
||||
? filenameParts.filepath + filenameParts.linkpartReference
|
||||
: file.path,
|
||||
scale,
|
||||
exportSettings,
|
||||
loader,
|
||||
theme,
|
||||
null,
|
||||
null,
|
||||
[],
|
||||
plugin,
|
||||
0
|
||||
));
|
||||
if (!png) {
|
||||
return null;
|
||||
}
|
||||
img.src = URL.createObjectURL(png);
|
||||
return img;
|
||||
}
|
||||
|
||||
if(!(filenameParts.hasBlockref || filenameParts.hasSectionref)) {
|
||||
const quickSVG = await getQuickImagePreview(plugin, file.path, "svg");
|
||||
if (quickSVG) {
|
||||
img.setAttribute("src", svgToBase64(quickSVG));
|
||||
return img;
|
||||
}
|
||||
}
|
||||
const svgSnapshot = (
|
||||
await createSVG(
|
||||
filenameParts.hasGroupref || filenameParts.hasBlockref || filenameParts.hasSectionref
|
||||
? filenameParts.filepath + filenameParts.linkpartReference
|
||||
: file.path,
|
||||
true,
|
||||
exportSettings,
|
||||
loader,
|
||||
theme,
|
||||
null,
|
||||
null,
|
||||
[],
|
||||
plugin,
|
||||
0,
|
||||
getExportPadding(plugin, file),
|
||||
)
|
||||
).outerHTML;
|
||||
let svg: SVGSVGElement = null;
|
||||
const el = document.createElement("div");
|
||||
el.innerHTML = svgSnapshot;
|
||||
const firstChild = el.firstChild;
|
||||
if (firstChild instanceof SVGSVGElement) {
|
||||
svg = firstChild;
|
||||
}
|
||||
if (!svg) {
|
||||
return null;
|
||||
}
|
||||
svg = embedFontsInSVG(svg, plugin);
|
||||
svg.removeAttribute("width");
|
||||
svg.removeAttribute("height");
|
||||
img.setAttribute("src", svgToBase64(svg.outerHTML));
|
||||
return img;
|
||||
};
|
||||
|
||||
const createImageDiv = async (
|
||||
attr: imgElementAttributes,
|
||||
): Promise<HTMLDivElement> => {
|
||||
const img = await getIMG(attr);
|
||||
return createDiv(attr.style, (el) => {
|
||||
el.append(img);
|
||||
el.setAttribute("src", attr.fname);
|
||||
if (attr.fwidth) {
|
||||
el.setAttribute("w", attr.fwidth);
|
||||
}
|
||||
if (attr.fheight) {
|
||||
el.setAttribute("h", attr.fheight);
|
||||
}
|
||||
let timer:NodeJS.Timeout;
|
||||
const clickEvent = (ev:PointerEvent) => {
|
||||
if (
|
||||
ev.target instanceof Element &&
|
||||
ev.target.tagName.toLowerCase() != "img"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const src = el.getAttribute("src");
|
||||
if (src) {
|
||||
const srcParts = src.match(/([^#]*)(.*)/);
|
||||
if(!srcParts) return;
|
||||
plugin.openDrawing(
|
||||
vault.getAbstractFileByPath(srcParts[1]) as TFile,
|
||||
linkClickModifierType(ev),
|
||||
true,
|
||||
srcParts[2],
|
||||
);
|
||||
} //.ctrlKey||ev.metaKey);
|
||||
};
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1003
|
||||
let pointerDownEvent:any;
|
||||
img.addEventListener("pointermove",(ev)=>{
|
||||
if(!timer) return;
|
||||
if(Math.abs(ev.screenX-pointerDownEvent.screenX)>10 || Math.abs(ev.screenY-pointerDownEvent.screenY)>10) {
|
||||
clearTimeout(timer);
|
||||
timer = null;
|
||||
}
|
||||
});
|
||||
|
||||
img.addEventListener("pointerdown",(ev)=>{
|
||||
timer = setTimeout(()=>clickEvent(ev),500);
|
||||
pointerDownEvent = ev;
|
||||
});
|
||||
|
||||
el.addEventListener("pointerup",()=>{
|
||||
if(timer) clearTimeout(timer);
|
||||
timer = null;
|
||||
})
|
||||
el.addEventListener("dblclick",clickEvent);
|
||||
el.addEventListener(RERENDER_EVENT, async (e) => {
|
||||
e.stopPropagation();
|
||||
el.empty();
|
||||
const img = await getIMG({
|
||||
fname: el.getAttribute("src"),
|
||||
fwidth: el.getAttribute("w"),
|
||||
fheight: el.getAttribute("h"),
|
||||
style: el.getAttribute("class"),
|
||||
});
|
||||
el.append(img);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const processReadingMode = async (
|
||||
embeddedItems: NodeListOf<Element> | [HTMLElement],
|
||||
ctx: MarkdownPostProcessorContext,
|
||||
) => {
|
||||
//We are processing a non-excalidraw file in reading mode
|
||||
//Embedded files will be displayed in an .internal-embed container
|
||||
|
||||
//Iterating all the containers in the file to check which one is an excalidraw drawing
|
||||
//This is a for loop instead of embeddedItems.forEach() because processInternalEmbed at the end
|
||||
//is awaited, otherwise excalidraw images would not display in the Kanban plugin
|
||||
for (const maybeDrawing of embeddedItems) {
|
||||
//check to see if the file in the src attribute exists
|
||||
const fname = maybeDrawing.getAttribute("src")?.split("#")[0];
|
||||
if(!fname) continue;
|
||||
|
||||
const file = metadataCache.getFirstLinkpathDest(fname, ctx.sourcePath);
|
||||
|
||||
//if the embeddedFile exits and it is an Excalidraw file
|
||||
//then lets replace the .internal-embed with the generated PNG or SVG image
|
||||
if (file && file instanceof TFile && plugin.isExcalidrawFile(file)) {
|
||||
if(isTextOnlyEmbed(maybeDrawing)) {
|
||||
//legacy reference to a block or section as text
|
||||
//should be embedded as legacy text
|
||||
continue;
|
||||
}
|
||||
|
||||
maybeDrawing.parentElement.replaceChild(
|
||||
await processInternalEmbed(maybeDrawing,file),
|
||||
maybeDrawing
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const processInternalEmbed = async (internalEmbedEl: Element, file: TFile ):Promise<HTMLDivElement> => {
|
||||
const attr: imgElementAttributes = {
|
||||
fname: "",
|
||||
fheight: "",
|
||||
fwidth: "",
|
||||
style: "",
|
||||
};
|
||||
|
||||
const src = internalEmbedEl.getAttribute("src");
|
||||
if(!src) return;
|
||||
attr.fwidth = internalEmbedEl.getAttribute("width")
|
||||
? internalEmbedEl.getAttribute("width")
|
||||
: getDefaultWidth(plugin);
|
||||
attr.fheight = internalEmbedEl.getAttribute("height");
|
||||
let alt = internalEmbedEl.getAttribute("alt");
|
||||
attr.style = "excalidraw-svg";
|
||||
processAltText(src.split("#")[0],alt,attr);
|
||||
const fnameParts = getEmbeddedFilenameParts(src);
|
||||
attr.fname = file?.path + (fnameParts.hasBlockref||fnameParts.hasSectionref?fnameParts.linkpartReference:"");
|
||||
attr.file = file;
|
||||
return await createImageDiv(attr);
|
||||
}
|
||||
|
||||
const processAltText = (
|
||||
fname: string,
|
||||
alt:string,
|
||||
attr: imgElementAttributes
|
||||
) => {
|
||||
if (alt && !alt.startsWith(fname)) {
|
||||
//2:width, 3:height, 4:style 12 3 4
|
||||
const parts = alt.match(/[^\|\d]*\|?((\d*%?)x?(\d*%?))?\|?(.*)/);
|
||||
attr.fwidth = parts[2] ?? attr.fwidth;
|
||||
attr.fheight = parts[3] ?? attr.fheight;
|
||||
if (parts[4] && !parts[4].startsWith(fname)) {
|
||||
attr.style = `excalidraw-svg${`-${parts[4]}`}`;
|
||||
}
|
||||
if (
|
||||
(!parts[4] || parts[4]==="") &&
|
||||
(!parts[2] || parts[2]==="") &&
|
||||
parts[0] && parts[0] !== ""
|
||||
) {
|
||||
attr.style = `excalidraw-svg${`-${parts[0]}`}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isTextOnlyEmbed = (internalEmbedEl: Element):boolean => {
|
||||
const src = internalEmbedEl.getAttribute("src");
|
||||
if(!src) return true; //technically this does not mean this is a text only embed, but still should abort further processing
|
||||
const fnameParts = getEmbeddedFilenameParts(src);
|
||||
return !(fnameParts.hasArearef || fnameParts.hasGroupref) &&
|
||||
(fnameParts.hasBlockref || fnameParts.hasSectionref)
|
||||
}
|
||||
|
||||
const tmpObsidianWYSIWYG = async (
|
||||
el: HTMLElement,
|
||||
ctx: MarkdownPostProcessorContext,
|
||||
) => {
|
||||
const file = app.vault.getAbstractFileByPath(ctx.sourcePath);
|
||||
if(!(file instanceof TFile)) return;
|
||||
if(!plugin.isExcalidrawFile(file)) return;
|
||||
|
||||
//@ts-ignore
|
||||
if (ctx.remainingNestLevel < 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
//The timeout gives time for Obsidian to attach el to the displayed document
|
||||
//Once the element is attached, I can traverse up the dom tree to find .internal-embed
|
||||
//If internal embed is not found, it means the that the excalidraw.md file
|
||||
//is being rendered in "reading" mode. In that case, the image with the default width
|
||||
//specified in setting should be displayed
|
||||
//if .internal-embed is found, then contents is replaced with the image using the
|
||||
//alt, width, and height attributes of .internal-embed to size and style the image
|
||||
setTimeout(async () => {
|
||||
//wait for el to be attached to the displayed document
|
||||
let counter = 0;
|
||||
while(!el.parentElement && counter++<=50) await sleep(50);
|
||||
if(!el.parentElement) return;
|
||||
|
||||
let internalEmbedDiv: HTMLElement = el;
|
||||
while (
|
||||
!internalEmbedDiv.hasClass("dataview") &&
|
||||
!internalEmbedDiv.hasClass("cm-preview-code-block") &&
|
||||
!internalEmbedDiv.hasClass("cm-embed-block") &&
|
||||
!internalEmbedDiv.hasClass("internal-embed") &&
|
||||
internalEmbedDiv.parentElement
|
||||
) {
|
||||
internalEmbedDiv = internalEmbedDiv.parentElement;
|
||||
}
|
||||
|
||||
if(
|
||||
internalEmbedDiv.hasClass("dataview") ||
|
||||
internalEmbedDiv.hasClass("cm-preview-code-block") ||
|
||||
internalEmbedDiv.hasClass("cm-embed-block")
|
||||
) {
|
||||
return; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/835
|
||||
}
|
||||
|
||||
const attr: imgElementAttributes = {
|
||||
fname: ctx.sourcePath,
|
||||
fheight: "",
|
||||
fwidth: getDefaultWidth(plugin),
|
||||
style: "excalidraw-svg",
|
||||
};
|
||||
|
||||
attr.file = file;
|
||||
|
||||
if (!internalEmbedDiv.hasClass("internal-embed")) {
|
||||
//We are processing the markdown preview of an actual Excalidraw file
|
||||
//This could be in a hover preview of the file
|
||||
//Or the file could be in markdown mode and the user switched markdown
|
||||
//view of the drawing to reading mode
|
||||
el.empty();
|
||||
const mdPreviewSection = el.parentElement;
|
||||
if(!mdPreviewSection.hasClass("markdown-preview-section")) return;
|
||||
if(mdPreviewSection.hasAttribute("ready")) {
|
||||
mdPreviewSection.removeChild(el);
|
||||
return;
|
||||
}
|
||||
mdPreviewSection.setAttribute("ready","");
|
||||
const imgDiv = await createImageDiv(attr);
|
||||
el.appendChild(imgDiv);
|
||||
return;
|
||||
}
|
||||
|
||||
if(isTextOnlyEmbed(internalEmbedDiv)) {
|
||||
//legacy reference to a block or section as text
|
||||
//should be embedded as legacy text
|
||||
return;
|
||||
}
|
||||
|
||||
el.empty();
|
||||
|
||||
if(internalEmbedDiv.hasAttribute("ready")) {
|
||||
return;
|
||||
}
|
||||
internalEmbedDiv.setAttribute("ready","");
|
||||
|
||||
internalEmbedDiv.empty();
|
||||
const imgDiv = await processInternalEmbed(internalEmbedDiv,file);
|
||||
internalEmbedDiv.appendChild(imgDiv);
|
||||
|
||||
//timer to avoid the image flickering when the user is typing
|
||||
let timer: NodeJS.Timeout = null;
|
||||
const observer = new MutationObserver((m) => {
|
||||
if (!["alt", "width", "height"].contains(m[0]?.attributeName)) {
|
||||
return;
|
||||
}
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
timer = setTimeout(async () => {
|
||||
timer = null;
|
||||
internalEmbedDiv.empty();
|
||||
const imgDiv = await processInternalEmbed(internalEmbedDiv,file);
|
||||
internalEmbedDiv.appendChild(imgDiv);
|
||||
}, 500);
|
||||
});
|
||||
observer.observe(internalEmbedDiv, {
|
||||
attributes: true, //configure it to listen to attribute changes
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param el
|
||||
* @param ctx
|
||||
*/
|
||||
export const markdownPostProcessor_Legacy = async (
|
||||
el: HTMLElement,
|
||||
ctx: MarkdownPostProcessorContext,
|
||||
) => {
|
||||
|
||||
//check to see if we are rendering in editing mode or live preview
|
||||
//if yes, then there should be no .internal-embed containers
|
||||
const embeddedItems = el.querySelectorAll(".internal-embed");
|
||||
if (embeddedItems.length === 0) {
|
||||
tmpObsidianWYSIWYG(el, ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
//If the file being processed is an excalidraw file,
|
||||
//then I want to hide all embedded items as these will be
|
||||
//transcluded text element or some other transcluded content inside the Excalidraw file
|
||||
//in reading mode these elements should be hidden
|
||||
const excalidrawFile = Boolean(ctx.frontmatter?.hasOwnProperty("excalidraw-plugin"));
|
||||
if (excalidrawFile) {
|
||||
el.style.display = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
await processReadingMode(embeddedItems, ctx);
|
||||
};
|
||||
|
||||
/**
|
||||
* internal-link quick preview
|
||||
* @param e
|
||||
* @returns
|
||||
*/
|
||||
export const hoverEvent_Legacy = (e: any) => {
|
||||
if (!e.linktext) {
|
||||
plugin.hover.linkText = null;
|
||||
return;
|
||||
}
|
||||
plugin.hover.linkText = e.linktext;
|
||||
plugin.hover.sourcePath = e.sourcePath;
|
||||
};
|
||||
|
||||
//monitoring for div.popover.hover-popover.file-embed.is-loaded to be added to the DOM tree
|
||||
export const observer_Legacy = new MutationObserver(async (m) => {
|
||||
if (m.length == 0) {
|
||||
return;
|
||||
}
|
||||
if (!plugin.hover.linkText) {
|
||||
return;
|
||||
}
|
||||
const file = metadataCache.getFirstLinkpathDest(
|
||||
plugin.hover.linkText,
|
||||
plugin.hover.sourcePath ? plugin.hover.sourcePath : "",
|
||||
);
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
if (!(file instanceof TFile)) {
|
||||
return;
|
||||
}
|
||||
if (file.extension !== "excalidraw") {
|
||||
return;
|
||||
}
|
||||
|
||||
const svgFileName = getIMGFilename(file.path, "svg");
|
||||
const svgFile = vault.getAbstractFileByPath(svgFileName);
|
||||
if (svgFile && svgFile instanceof TFile) {
|
||||
return;
|
||||
} //If auto export SVG or PNG is enabled it will be inserted at the top of the excalidraw file. No need to manually insert hover preview
|
||||
|
||||
const pngFileName = getIMGFilename(file.path, "png");
|
||||
const pngFile = vault.getAbstractFileByPath(pngFileName);
|
||||
if (pngFile && pngFile instanceof TFile) {
|
||||
return;
|
||||
} //If auto export SVG or PNG is enabled it will be inserted at the top of the excalidraw file. No need to manually insert hover preview
|
||||
|
||||
if (!plugin.hover.linkText) {
|
||||
return;
|
||||
}
|
||||
if (m.length != 1) {
|
||||
return;
|
||||
}
|
||||
if (m[0].addedNodes.length != 1) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
//@ts-ignore
|
||||
!m[0].addedNodes[0].classNames !=
|
||||
"popover hover-popover file-embed is-loaded"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const node = m[0].addedNodes[0];
|
||||
node.empty();
|
||||
|
||||
//this div will be on top of original DIV. By stopping the propagation of the click
|
||||
//I prevent the default Obsidian feature of openning the link in the native app
|
||||
const img = await getIMG({
|
||||
file,
|
||||
fname: file.path,
|
||||
fwidth: "300",
|
||||
fheight: null,
|
||||
style: "excalidraw-svg",
|
||||
});
|
||||
const div = createDiv("", async (el) => {
|
||||
el.appendChild(img);
|
||||
el.setAttribute("src", file.path);
|
||||
el.onClickEvent((ev) => {
|
||||
ev.stopImmediatePropagation();
|
||||
const src = el.getAttribute("src");
|
||||
if (src) {
|
||||
plugin.openDrawing(
|
||||
vault.getAbstractFileByPath(src) as TFile,
|
||||
linkClickModifierType(ev)
|
||||
);
|
||||
} //.ctrlKey||ev.metaKey);
|
||||
});
|
||||
});
|
||||
node.appendChild(div);
|
||||
});
|
||||
39
src/PenTypes.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
export interface StrokeOptions {
|
||||
thinning: number;
|
||||
smoothing: number;
|
||||
streamline: number;
|
||||
easing: string;
|
||||
simulatePressure?: boolean;
|
||||
start: {
|
||||
cap: boolean;
|
||||
taper: number | boolean;
|
||||
easing: string;
|
||||
};
|
||||
end: {
|
||||
cap: boolean;
|
||||
taper: number | boolean;
|
||||
easing: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PenOptions {
|
||||
highlighter: boolean;
|
||||
constantPressure: boolean;
|
||||
hasOutline: boolean;
|
||||
outlineWidth: number;
|
||||
options: StrokeOptions;
|
||||
}
|
||||
|
||||
export declare type ExtendedFillStyle = "dots"|"zigzag"|"zigzag-line"|"dashed"|"hachure"|"cross-hatch"|"solid"|"";
|
||||
export declare type PenType = "default" | "highlighter" | "finetip" | "fountain" | "marker" | "thick-thin" | "thin-thick-thin";
|
||||
|
||||
export interface PenStyle {
|
||||
type: PenType;
|
||||
freedrawOnly: boolean;
|
||||
strokeColor?: string;
|
||||
backgroundColor?: string;
|
||||
fillStyle: ExtendedFillStyle;
|
||||
strokeWidth: number;
|
||||
roughness: number;
|
||||
penOptions: PenOptions;
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import { getIMGFilename } from "./utils/FileUtils";
|
||||
import { splitFolderAndFilename } from "./utils/FileUtils";
|
||||
|
||||
export type ScriptIconMap = {
|
||||
[key: string]: { name: string; svgString: string };
|
||||
[key: string]: { name: string; group: string; svgString: string };
|
||||
};
|
||||
|
||||
export class ScriptEngine {
|
||||
@@ -147,7 +147,8 @@ export class ScriptEngine {
|
||||
this.scriptIconMap = {
|
||||
...this.scriptIconMap,
|
||||
};
|
||||
this.scriptIconMap[scriptPath] = { name, svgString };
|
||||
const splitname = splitFolderAndFilename(name)
|
||||
this.scriptIconMap[scriptPath] = { name:splitname.filename, group: splitname.folderpath === "/" ? "" : splitname.folderpath, svgString };
|
||||
this.updateToolPannels();
|
||||
}
|
||||
|
||||
@@ -169,7 +170,7 @@ export class ScriptEngine {
|
||||
(async()=>{
|
||||
const script = await app.vault.read(f);
|
||||
if(script) {
|
||||
this.executeScript(view, script, scriptName);
|
||||
this.executeScript(view, script, scriptName,f);
|
||||
}
|
||||
})()
|
||||
return true;
|
||||
@@ -205,7 +206,7 @@ export class ScriptEngine {
|
||||
delete app.commands.commands[commandId];
|
||||
}
|
||||
|
||||
async executeScript(view: ExcalidrawView, script: string, title: string) {
|
||||
async executeScript(view: ExcalidrawView, script: string, title: string, file: TFile) {
|
||||
if (!view || !script || !title) {
|
||||
return;
|
||||
}
|
||||
@@ -245,6 +246,7 @@ export class ScriptEngine {
|
||||
hint,
|
||||
instructions,
|
||||
),
|
||||
scriptFile: file
|
||||
});
|
||||
/*} catch (e) {
|
||||
new Notice(t("SCRIPT_EXECUTION_ERROR"), 4000);
|
||||
|
||||
@@ -1,10 +1,32 @@
|
||||
import { customAlphabet } from "nanoid";
|
||||
//This is only for backward compatibility because an early version of obsidian included an encoding to avoid fantom links from littering Obsidian graph view
|
||||
declare const PLUGIN_VERSION:string;
|
||||
export function JSON_parse(x: string): any {
|
||||
return JSON.parse(x.replaceAll("[", "["));
|
||||
}
|
||||
export const isDarwin = /Mac|iPod|iPhone|iPad/.test(window.navigator.platform);
|
||||
export const CTRL_OR_CMD = isDarwin ? "metaKey" : "ctrlKey";
|
||||
export const DEVICE: {
|
||||
isDesktop: boolean,
|
||||
isPhone: boolean,
|
||||
isTablet: boolean,
|
||||
isMobile: boolean,
|
||||
isLinux: boolean,
|
||||
isMacOS: boolean,
|
||||
isWindows: boolean,
|
||||
isIOS: boolean,
|
||||
isAndroid: boolean
|
||||
} = {
|
||||
isDesktop: !document.body.hasClass("is-tablet") && !document.body.hasClass("is-mobile"),
|
||||
isPhone: document.body.hasClass("is-phone"),
|
||||
isTablet: document.body.hasClass("is-tablet"),
|
||||
isMobile: document.body.hasClass("is-mobile"), //running Obsidian Mobile, need to also check isTablet
|
||||
isLinux: document.body.hasClass("mod-linux") && ! document.body.hasClass("is-android"),
|
||||
isMacOS: document.body.hasClass("mod-macos") && ! document.body.hasClass("is-ios"),
|
||||
isWindows: document.body.hasClass("mod-windows"),
|
||||
isIOS: document.body.hasClass("is-ios"),
|
||||
isAndroid: document.body.hasClass("is-android")
|
||||
};
|
||||
|
||||
export const nanoid = customAlphabet(
|
||||
"1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
8,
|
||||
@@ -12,6 +34,13 @@ export const nanoid = customAlphabet(
|
||||
export const KEYCODE = {
|
||||
ESC: 27,
|
||||
};
|
||||
export const ROUNDNESS = { //should at one point publish @zsviczian/excalidraw/types/constants
|
||||
LEGACY: 1,
|
||||
PROPORTIONAL_RADIUS: 2,
|
||||
ADAPTIVE_RADIUS: 3,
|
||||
} as const;
|
||||
export const GITHUB_RELEASES = "https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/";
|
||||
export const URLFETCHTIMEOUT = 1000;
|
||||
export const PLUGIN_ID = "obsidian-excalidraw-plugin";
|
||||
export const SCRIPT_INSTALL_CODEBLOCK = "excalidraw-script-install";
|
||||
export const SCRIPT_INSTALL_FOLDER = "Downloaded";
|
||||
@@ -41,16 +70,15 @@ export const FRONTMATTER_KEY_FONTCOLOR = "excalidraw-font-color";
|
||||
export const FRONTMATTER_KEY_BORDERCOLOR = "excalidraw-border-color";
|
||||
export const FRONTMATTER_KEY_MD_STYLE = "excalidraw-css";
|
||||
export const FRONTMATTER_KEY_AUTOEXPORT = "excalidraw-autoexport"
|
||||
export const LOCAL_PROTOCOL = "md://";
|
||||
export const VIEW_TYPE_EXCALIDRAW = "excalidraw";
|
||||
export const ICON_NAME = "excalidraw-icon";
|
||||
export const MAX_COLORS = 5;
|
||||
export const COLOR_FREQ = 6;
|
||||
export const RERENDER_EVENT = "excalidraw-embed-rerender";
|
||||
export const BLANK_DRAWING =
|
||||
'{"type":"excalidraw","version":2,"source":"https://excalidraw.com","elements":[],"appState":{"gridSize":null,"viewBackgroundColor":"#ffffff"}}';
|
||||
`{"type":"excalidraw","version":2,"source":"${GITHUB_RELEASES+PLUGIN_VERSION}","elements":[],"appState":{"gridSize":null,"viewBackgroundColor":"#ffffff"}}`;
|
||||
export const DARK_BLANK_DRAWING =
|
||||
'{"type":"excalidraw","version":2,"source":"https://excalidraw.com","elements":[],"appState":{"theme":"dark","gridSize":null,"viewBackgroundColor":"#ffffff"}}';
|
||||
`{"type":"excalidraw","version":2,"source":"${GITHUB_RELEASES+PLUGIN_VERSION}","elements":[],"appState":{"theme":"dark","gridSize":null,"viewBackgroundColor":"#ffffff"}}`;
|
||||
export const FRONTMATTER = [
|
||||
"---",
|
||||
"",
|
||||
@@ -210,10 +238,9 @@ COLOR_NAMES.set("white", "#ffffff");
|
||||
COLOR_NAMES.set("whitesmoke", "#f5f5f5");
|
||||
COLOR_NAMES.set("yellow", "#ffff00");
|
||||
COLOR_NAMES.set("yellowgreen", "#9acd32");
|
||||
export const DEFAULT_MD_EMBED_CSS = `.excalidraw-md-host{padding:0px 10px}.excalidraw-md-footer{height:5px}foreignObject{background-color:transparent}p{display:block;margin-block-start:1em;margin-block-end:1em;margin-inline-start:0px;margin-inline-end:0px;color:inherit}table,tr,th,td{color:inherit;border:1px solid;border-collapse:collapse;padding:3px}th{font-weight:bold;border-bottom:double;background-color:silver}.copy-code-button{display:none}code[class*=language-],pre[class*=language-]{color:#393a34;font-family:"Consolas","Bitstream Vera Sans Mono","Courier New",Courier,monospace;direction:ltr;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;font-size:.9em;line-height:1.2em;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre>code[class*=language-]{font-size:1em}pre[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,code[class*=language-] ::-moz-selection{background:#C1DEF1}pre[class*=language-]::selection,pre[class*=language-] ::selection,code[class*=language-]::selection,code[class*=language-] ::selection{background:#C1DEF1}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;background-color:#0000001a}:not(pre)>code[class*=language-]{padding:.2em;padding-top:1px;padding-bottom:1px;background:#f8f8f8;border:1px solid #dddddd}.token.comment,.token.prolog,.token.doctype,.token.cdata{color:green;font-style:italic}.token.namespace{opacity:.7}.token.string{color:#a31515}.token.punctuation,.token.operator{color:#393a34}.token.url,.token.symbol,.token.number,.token.boolean,.token.variable,.token.constant,.token.inserted{color:#36acaa}.token.atrule,.token.keyword,.token.attr-value,.language-autohotkey .token.selector,.language-json .token.boolean,.language-json .token.number,code[class*=language-css]{color:#00f}.token.function{color:#393a34}.token.deleted,.language-autohotkey .token.tag{color:#9a050f}.token.selector,.language-autohotkey .token.keyword{color:#00009f}.token.important{color:#e90}.token.important,.token.bold{font-weight:bold}.token.italic{font-style:italic}.token.class-name,.language-json .token.property{color:#2b91af}.token.tag,.token.selector{color:maroon}.token.attr-name,.token.property,.token.regex,.token.entity{color:red}.token.directive.tag .tag{background:#ffff00;color:#393a34}.line-numbers.line-numbers .line-numbers-rows{border-right-color:#a5a5a5}.line-numbers .line-numbers-rows>span:before{color:#2b91af}.line-highlight.line-highlight{background:rgba(193,222,241,.2);background:-webkit-linear-gradient(left,rgba(193,222,241,.2) 70%,rgba(221,222,241,0));background:linear-gradient(to right,rgba(193,222,241,.2) 70%,rgba(221,222,241,0))}blockquote{ font-style:italic;background-color:rgb(46,43,42,0.1);margin:0;margin-left:1em;border-radius:0 4px 4px 0;border:1px solid hsl(0,80%,32%);border-left-width:8px;border-top-width:0px;border-right-width:0px;border-bottom-width:0px;padding:10px 20px;margin-inline-start:30px;margin-inline-end:30px;}`;
|
||||
export const DEFAULT_MD_EMBED_CSS = `.snw-reference{display: none;}.excalidraw-md-host{padding:0px 10px}.excalidraw-md-footer{height:5px}foreignObject{background-color:transparent}p{display:block;margin-block-start:1em;margin-block-end:1em;margin-inline-start:0px;margin-inline-end:0px;color:inherit}table,tr,th,td{color:inherit;border:1px solid;border-collapse:collapse;padding:3px}th{font-weight:bold;border-bottom:double;background-color:silver}.copy-code-button{display:none}code[class*=language-],pre[class*=language-]{color:#393a34;font-family:"Consolas","Bitstream Vera Sans Mono","Courier New",Courier,monospace;direction:ltr;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;font-size:.9em;line-height:1.2em;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre>code[class*=language-]{font-size:1em}pre[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,code[class*=language-] ::-moz-selection{background:#C1DEF1}pre[class*=language-]::selection,pre[class*=language-] ::selection,code[class*=language-]::selection,code[class*=language-] ::selection{background:#C1DEF1}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;background-color:#0000001a}:not(pre)>code[class*=language-]{padding:.2em;padding-top:1px;padding-bottom:1px;background:#f8f8f8;border:1px solid #dddddd}.token.comment,.token.prolog,.token.doctype,.token.cdata{color:green;font-style:italic}.token.namespace{opacity:.7}.token.string{color:#a31515}.token.punctuation,.token.operator{color:#393a34}.token.url,.token.symbol,.token.number,.token.boolean,.token.variable,.token.constant,.token.inserted{color:#36acaa}.token.atrule,.token.keyword,.token.attr-value,.language-autohotkey .token.selector,.language-json .token.boolean,.language-json .token.number,code[class*=language-css]{color:#00f}.token.function{color:#393a34}.token.deleted,.language-autohotkey .token.tag{color:#9a050f}.token.selector,.language-autohotkey .token.keyword{color:#00009f}.token.important{color:#e90}.token.important,.token.bold{font-weight:bold}.token.italic{font-style:italic}.token.class-name,.language-json .token.property{color:#2b91af}.token.tag,.token.selector{color:maroon}.token.attr-name,.token.property,.token.regex,.token.entity{color:red}.token.directive.tag .tag{background:#ffff00;color:#393a34}.line-numbers.line-numbers .line-numbers-rows{border-right-color:#a5a5a5}.line-numbers .line-numbers-rows>span:before{color:#2b91af}.line-highlight.line-highlight{background:rgba(193,222,241,.2);background:-webkit-linear-gradient(left,rgba(193,222,241,.2) 70%,rgba(221,222,241,0));background:linear-gradient(to right,rgba(193,222,241,.2) 70%,rgba(221,222,241,0))}blockquote{ font-style:italic;background-color:rgb(46,43,42,0.1);margin:0;margin-left:1em;border-radius:0 4px 4px 0;border:1px solid hsl(0,80%,32%);border-left-width:8px;border-top-width:0px;border-right-width:0px;border-bottom-width:0px;padding:10px 20px;margin-inline-start:30px;margin-inline-end:30px;}`;
|
||||
export const SCRIPTENGINE_ICON = `<g transform="translate(-8,-8)"><path d="M24.318 37.983c-1.234-1.232-8.433-3.903-7.401-7.387 1.057-3.484 9.893-12.443 13.669-13.517 3.776-1.074 6.142 6.523 9.012 7.073 2.87.55 6.797-1.572 8.207-3.694 1.384-2.148-3.147-7.413.15-9.168 3.298-1.755 16.389-2.646 19.611-1.284 3.247 1.363-1.611 7.335-.151 9.483 1.46 2.148 6.067 3.746 8.836 3.38 2.769-.368 4.154-6.733 7.728-5.633 3.575 1.1 12.36 8.828 13.67 12.233 1.308 3.406-5.186 5.423-5.79 8.2-.58 2.75-.026 6.705 2.265 8.355 2.266 1.65 9.642-1.78 11.404 1.598 1.762 3.38 1.007 15.35-.806 18.651-1.787 3.353-7.753-.367-9.969 1.31-2.215 1.65-3.901 5.92-3.373 8.67.504 2.777 7.754 4.48 6.445 7.885C96.49 87.543 87.15 95.454 83.5 96.685c-3.65 1.231-4.96-4.741-7.577-5.16-2.593-.393-6.57.707-8.03 2.75-1.436 2.017 2.668 7.806-.63 9.483-3.323 1.676-15.759 2.226-19.157.655-3.373-1.598.554-7.964-1.108-10.138-1.687-2.174-6.394-3.431-9.012-2.907-2.643.55-3.273 7.282-6.747 6.103-3.499-1.126-12.788-9.535-14.172-13.019-1.36-3.484 5.437-5.108 5.966-7.858.529-2.777-.68-7.073-2.744-8.697-2.064-1.624-7.93 2.41-9.642-1.126-1.737-3.537-2.441-16.765-.654-20.118 1.787-3.3 9.062 1.598 11.429.183 2.366-1.44 2.316-7.282 2.769-8.749m.126-.104c-1.234-1.232-8.433-3.903-7.401-7.387 1.057-3.484 9.893-12.443 13.669-13.517 3.776-1.074 6.142 6.523 9.012 7.073 2.87.55 6.797-1.572 8.207-3.694 1.384-2.148-3.147-7.413.15-9.168 3.298-1.755 16.389-2.646 19.611-1.284 3.247 1.363-1.611 7.335-.151 9.483 1.46 2.148 6.067 3.746 8.836 3.38 2.769-.368 4.154-6.733 7.728-5.633 3.575 1.1 12.36 8.828 13.67 12.233 1.308 3.406-5.186 5.423-5.79 8.2-.58 2.75-.026 6.705 2.265 8.355 2.266 1.65 9.642-1.78 11.404 1.598 1.762 3.38 1.007 15.35-.806 18.651-1.787 3.353-7.753-.367-9.969 1.31-2.215 1.65-3.901 5.92-3.373 8.67.504 2.777 7.754 4.48 6.445 7.885C96.49 87.543 87.15 95.454 83.5 96.685c-3.65 1.231-4.96-4.741-7.577-5.16-2.593-.393-6.57.707-8.03 2.75-1.436 2.017 2.668 7.806-.63 9.483-3.323 1.676-15.759 2.226-19.157.655-3.373-1.598.554-7.964-1.108-10.138-1.687-2.174-6.394-3.431-9.012-2.907-2.643.55-3.273 7.282-6.747 6.103-3.499-1.126-12.788-9.535-14.172-13.019-1.36-3.484 5.437-5.108 5.966-7.858.529-2.777-.68-7.073-2.744-8.697-2.064-1.624-7.93 2.41-9.642-1.126-1.737-3.537-2.441-16.765-.654-20.118 1.787-3.3 9.062 1.598 11.429.183 2.366-1.44 2.316-7.282 2.769-8.749" fill="none" stroke-width="2" stroke-linecap="round" stroke="currentColor"/><path d="M81.235 56.502a23.3 23.3 0 0 1-1.46 8.068 20.785 20.785 0 0 1-1.762 3.72 24.068 24.068 0 0 1-5.337 6.26 22.575 22.575 0 0 1-3.449 2.358 23.726 23.726 0 0 1-7.803 2.803 24.719 24.719 0 0 1-8.333 0 24.102 24.102 0 0 1-4.028-1.074 23.71 23.71 0 0 1-3.776-1.729 23.259 23.259 0 0 1-6.369-5.265 23.775 23.775 0 0 1-2.416-3.353 24.935 24.935 0 0 1-1.762-3.72 23.765 23.765 0 0 1-1.083-3.981 23.454 23.454 0 0 1 0-8.173c.252-1.336.604-2.698 1.083-3.956a24.935 24.935 0 0 1 1.762-3.72 22.587 22.587 0 0 1 2.416-3.378c.881-1.048 1.888-2.017 2.946-2.908a24.38 24.38 0 0 1 3.423-2.357 23.71 23.71 0 0 1 3.776-1.73 21.74 21.74 0 0 1 4.028-1.047 23.437 23.437 0 0 1 8.333 0 24.282 24.282 0 0 1 7.803 2.777 26.198 26.198 0 0 1 3.45 2.357 24.62 24.62 0 0 1 5.336 6.287 20.785 20.785 0 0 1 1.762 3.72 21.32 21.32 0 0 1 1.083 3.955c.251 1.336.302 3.405.377 4.086.05.681.05-.68 0 0" fill="none" stroke-width="4" stroke-linecap="round" stroke="currentColor"/><path d="M69.404 56.633c-6.596-3.3-13.216-6.6-19.51-9.744m19.51 9.744c-6.747-3.379-13.493-6.758-19.51-9.744m0 0v19.489m0-19.49v19.49m0 0c4.355-2.148 8.71-4.322 19.51-9.745m-19.51 9.745c3.978-1.965 7.93-3.956 19.51-9.745m0 0h0m0 0h0" fill="currentColor" stroke-linecap="round" stroke="currentColor" stroke-width="4"/></g>`;
|
||||
export const DISK_ICON_NAME = "disk";
|
||||
export const DISK_ICON = `<path fill="none" stroke="currentColor" fill="#fff" d="M0 0h100v100H0z"/><path fill="none" stroke="currentColor" d="M20.832 4.168c21.824.145 43.645.289 74.68.5m-74.68-.5c17.09.113 34.176.227 74.68.5m0 0c.094 27.3.191 54.602.32 91.164m-.32-91.164c.113 32.633.23 65.27.32 91.164m0 0H4.168m91.664 0H4.168m0 0v-75m0 75v-75m0 0L20.832 4.168M4.168 20.832L20.832 4.168M20.832 4.168h58.336m-58.336 0h58.336m0 0v25m0-25v25m0 0H20.832m58.336 0H20.832m0 0v-25m0 25v-25" stroke-width="1.66668" /><path fill="none" stroke="currentColor" d="M29.168 4.168h16.664v16.664H29.168"/><path fill="none" stroke="currentColor" d="M29.168 4.168h16.664m-16.664 0h16.664m0 0v16.664m0-16.664v16.664m0 0H29.168m16.664 0H29.168m0 0V4.168m0 16.664V4.168M12.5 54.168h75m-75 0h75m0 0v41.664m0-41.664v41.664m0 0h-75m75 0h-75m0 0V54.168m0 41.664V54.168M20.832 62.5c20.11-.18 40.219-.36 55.68-.5m-55.68.5c14.656-.133 29.313-.262 55.68-.5M20.832 71.332c13.098-.117 26.2-.234 55.68-.5m-55.68.5l55.68-.5M21.117 79.582c20.645-.184 41.285-.371 55.68-.5m-55.68.5c18.153-.16 36.301-.324 55.68-.5" stroke-width="1.66668"/>`;
|
||||
export const DISK_ICON_NAME = "save";
|
||||
export const PNG_ICON_NAME = "save-png";
|
||||
export const PNG_ICON = `<defs><symbol overflow="visible" id="aa"><path fill="currentColor" stroke="currentColor" d="M6.578-10.984h8.188c2.03 0 3.64-.594 5.046-1.844 1.563-1.422 2.25-3.094 2.25-5.469 0-4.875-2.906-7.61-8.046-7.61H3.25V0h3.328zm0-2.907v-9.093h6.938c3.171 0 5.078 1.703 5.078 4.547 0 2.843-1.907 4.546-5.078 4.546zm0 0"></path></symbol><symbol overflow="visible" id="bb"><path fill="currentColor" stroke="currentColor" d="M23.094-25.906h-3.14V-4.72L6.327-25.906h-3.61V0H5.86v-21L19.344 0h3.75zm0 0"></path></symbol><symbol overflow="visible" id="cc"><path fill="currentColor" stroke="currentColor" d="M25.344-13.672h-10.86v2.906h7.938v.704c0 4.624-3.438 7.968-8.188 7.968-2.656 0-5.046-.969-6.578-2.625-1.718-1.86-2.765-4.953-2.765-8.14 0-6.36 3.656-10.563 9.156-10.563 3.969 0 6.828 2.031 7.547 5.375h3.39c-.922-5.265-4.922-8.281-10.906-8.281-3.172 0-5.75.812-7.781 2.484-3.047 2.485-4.719 6.5-4.719 11.157 0 7.968 4.89 13.5 11.938 13.5 3.53 0 6.328-1.313 8.906-4.11l.812 3.438h2.11zm0 0"></path></symbol></defs><path fill="none" stroke="currentColor" d="M-.003.003v59.999m0-60v60m0 0h220.006m-220.006 0h220.006m0 0v-60m0 60v-60" transform="matrix(.40833 0 0 .40574 4.083 68.975)" stroke-width="4"></path><use xlink:href="#aa" x="11.023" y="86.651"></use><use xlink:href="#bb" x="33.944" y="86.651"></use><use xlink:href="#cc" x="59.724" y="86.651"></use><path fill="currentColor" stroke="currentColor" d="M40.832 4.059h16.336v32.457h8.164L49 52.746l-16.332-16.23h8.164V4.059" fill-rule="evenodd"></path><path fill="currentColor" stroke="currentColor" d="M-.003.003h40.006m-40.006 0h40.006m0 0v79.995m0-79.995v79.995m0 0h19.994m-19.994 0h19.994m0 0C51.55 88.451 43.093 96.904 20 120m39.997-40.002A196001.962 196001.962 0 0120 120m0 0C8.406 108.41-3.18 96.817-19.997 79.998M20 120C9.43 109.43-1.142 98.858-19.997 79.998m0 0H-.003m-19.994 0H-.003m0 0V.003m0 79.995V.003m0 0h0m0 0h0" transform="matrix(.40833 0 0 .40574 40.833 4.057)" stroke-width="4"></path>`;
|
||||
export const SVG_ICON_NAME = "save-svg";
|
||||
|
||||
196
src/dialogs/ExportDialog.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/types";
|
||||
import { Modal, Setting, SliderComponent, TFile } from "obsidian";
|
||||
import { getEA } from "src";
|
||||
import { DEVICE } from "src/Constants";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { fragWithHTML, getExportPadding, getExportTheme, getPNGScale, getWithBackground } from "src/utils/Utils";
|
||||
|
||||
export class ExportDialog extends Modal {
|
||||
private ea: ExcalidrawAutomate;
|
||||
private api: ExcalidrawImperativeAPI;
|
||||
public padding: number;
|
||||
public scale: number;
|
||||
public theme: string;
|
||||
public transparent: boolean;
|
||||
public saveSettings: boolean;
|
||||
public dirty: boolean = false;
|
||||
private boundingBox: {
|
||||
topX: number;
|
||||
topY: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
public embedScene: boolean;
|
||||
public saveToVault: boolean;
|
||||
|
||||
constructor(
|
||||
private plugin: ExcalidrawPlugin,
|
||||
private view: ExcalidrawView,
|
||||
private file: TFile,
|
||||
) {
|
||||
super(app);
|
||||
this.ea = getEA(this.view);
|
||||
this.api = this.ea.getExcalidrawAPI() as ExcalidrawImperativeAPI;
|
||||
this.padding = getExportPadding(this.plugin,this.file);
|
||||
this.scale = getPNGScale(this.plugin,this.file)
|
||||
this.theme = getExportTheme(this.plugin, this.file, (this.api).getAppState().theme)
|
||||
this.boundingBox = this.ea.getBoundingBox(this.ea.getViewElements());
|
||||
this.embedScene = false;
|
||||
this.saveToVault = true;
|
||||
this.transparent = !getWithBackground(this.plugin, this.file);
|
||||
this.saveSettings = false;
|
||||
}
|
||||
|
||||
onOpen(): void {
|
||||
this.containerEl.classList.add("excalidraw-release");
|
||||
this.titleEl.setText(`Export Image`);
|
||||
}
|
||||
|
||||
async onClose() {
|
||||
this.dirty = this.saveSettings;
|
||||
}
|
||||
|
||||
async createForm() {
|
||||
let scaleSetting:Setting;
|
||||
let paddingSetting: Setting;
|
||||
|
||||
this.contentEl.createEl("h1",{text: "Image settings"});
|
||||
this.contentEl.createEl("p",{text: "Transparency only affects PNGs. Excalidraw files can only be exported outside the Vault. PNGs copied to clipboard may not include the scene."})
|
||||
|
||||
const size = ():DocumentFragment => {
|
||||
const width = Math.round(this.scale*this.boundingBox.width + this.padding*2);
|
||||
const height = Math.round(this.scale*this.boundingBox.height + this.padding*2);
|
||||
return fragWithHTML(`The lager the scale, the larger the image.<br>Scale: <b>${this.scale}</b><br>Image size: <b>${width}x${height}</b>`);
|
||||
}
|
||||
|
||||
const padding = ():DocumentFragment => {
|
||||
return fragWithHTML(`Current image padding is <b>${this.padding}</b>`);
|
||||
}
|
||||
|
||||
paddingSetting = new Setting(this.contentEl)
|
||||
.setName("Image padding")
|
||||
.setDesc(padding())
|
||||
.addSlider(slider => {
|
||||
slider
|
||||
.setLimits(0,50,1)
|
||||
.setValue(this.padding)
|
||||
.onChange(value => {
|
||||
this.padding = value;
|
||||
scaleSetting.setDesc(size());
|
||||
paddingSetting.setDesc(padding());
|
||||
})
|
||||
})
|
||||
|
||||
scaleSetting = new Setting(this.contentEl)
|
||||
.setName("PNG Scale")
|
||||
.setDesc(size())
|
||||
.addSlider(slider =>
|
||||
slider
|
||||
.setLimits(0.5,5,0.5)
|
||||
.setValue(this.scale)
|
||||
.onChange(value => {
|
||||
this.scale = value;
|
||||
scaleSetting.setDesc(size());
|
||||
})
|
||||
)
|
||||
|
||||
const themeMessage = () => `Export with ${this.theme} theme`;
|
||||
const themeSetting = new Setting(this.contentEl)
|
||||
.setName(themeMessage())
|
||||
.setDesc(fragWithHTML("<b>Toggle on:</b> Export with light theme<br><b>Toggle off:</b> Export with dark theme"))
|
||||
.addToggle(toggle =>
|
||||
toggle
|
||||
.setValue(this.theme === "dark" ? false : true)
|
||||
.onChange(value => {
|
||||
this.theme = value ? "light" : "dark";
|
||||
themeSetting.setName(themeMessage());
|
||||
})
|
||||
)
|
||||
|
||||
const transparencyMessage = () => `Export with ${this.transparent ? "transparent ":""}background`;
|
||||
const transparentSetting = new Setting(this.contentEl)
|
||||
.setName(transparencyMessage())
|
||||
.setDesc(fragWithHTML("<b>Toggle on:</b> Export with transparent background<br><b>Toggle off:</b> Export with background"))
|
||||
.addToggle(toggle =>
|
||||
toggle
|
||||
.setValue(this.transparent)
|
||||
.onChange(value => {
|
||||
this.transparent = value;
|
||||
transparentSetting.setName(transparencyMessage())
|
||||
})
|
||||
)
|
||||
|
||||
const saveSettingsMessage = () => this.saveSettings?"Save these settings as the preset for this image":"These are one-time settings"
|
||||
const saveSettingsSetting= new Setting(this.contentEl)
|
||||
.setName(saveSettingsMessage())
|
||||
.setDesc(fragWithHTML("Saving these settings as preset will override general export settings for this image.<br><b>Toggle on: </b>Save as preset for this image<br><b>Toggle off: </b>Don't save as preset"))
|
||||
.addToggle(toggle =>
|
||||
toggle
|
||||
.setValue(this.saveSettings)
|
||||
.onChange(value => {
|
||||
this.saveSettings = value;
|
||||
saveSettingsSetting.setName(saveSettingsMessage())
|
||||
})
|
||||
)
|
||||
|
||||
this.contentEl.createEl("h1",{text:"Export settings"});
|
||||
|
||||
const embedSceneMessage = () => this.embedScene?"Embed scene":"Do not embed scene";
|
||||
const embedSetting = new Setting(this.contentEl)
|
||||
.setName(embedSceneMessage())
|
||||
.setDesc(fragWithHTML("Embed the Excalidraw scene into the PNG or SVG image<br><b>Toggle on: </b>Embed scene<br><b>Toggle off: </b>Do not embed scene"))
|
||||
.addToggle(toggle =>
|
||||
toggle
|
||||
.setValue(this.embedScene)
|
||||
.onChange(value => {
|
||||
this.embedScene = value;
|
||||
embedSetting.setName(embedSceneMessage())
|
||||
})
|
||||
)
|
||||
|
||||
if(DEVICE.isDesktop) {
|
||||
const saveToMessage = () => this.saveToVault?"Save image to your Vault":"Export image outside your Vault";
|
||||
const saveToSetting = new Setting(this.contentEl)
|
||||
.setName(saveToMessage())
|
||||
.setDesc(fragWithHTML("<b>Toggle on: </b>Save image to your Vault in the same folder as this drawing<br><b>Toggle off: </b>Save image outside your Vault"))
|
||||
.addToggle(toggle =>
|
||||
toggle
|
||||
.setValue(this.saveToVault)
|
||||
.onChange(value => {
|
||||
this.saveToVault = value;
|
||||
saveToSetting.setName(saveToMessage())
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const div = this.contentEl.createDiv({cls: "excalidraw-prompt-buttons-div"});
|
||||
const bPNG = div.createEl("button", { text: "PNG to File", cls: "excalidraw-prompt-button"});
|
||||
bPNG.onclick = () => {
|
||||
this.saveToVault
|
||||
? this.view.savePNG()
|
||||
: this.view.exportPNG();
|
||||
this.close();
|
||||
};
|
||||
const bSVG = div.createEl("button", { text: "SVG to File", cls: "excalidraw-prompt-button" });
|
||||
bSVG.onclick = () => {
|
||||
this.saveToVault
|
||||
? this.view.saveSVG()
|
||||
: this.view.exportSVG();
|
||||
this.close();
|
||||
};
|
||||
const bExcalidraw = div.createEl("button", { text: "Excalidraw", cls: "excalidraw-prompt-button" });
|
||||
bExcalidraw.onclick = () => {
|
||||
this.view.exportExcalidraw();
|
||||
this.close();
|
||||
};
|
||||
if(DEVICE.isDesktop) {
|
||||
const bPNGClipboard = div.createEl("button", { text: "PNG to Clipboard", cls: "excalidraw-prompt-button" });
|
||||
bPNGClipboard.onclick = () => {
|
||||
this.view.exportPNGToClipboard();
|
||||
this.close();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { App, FuzzySuggestModal, TFile } from "obsidian";
|
||||
import { isALT, scaleToFullsizeModifier } from "src/utils/ModifierkeyHelper";
|
||||
import { fileURLToPath } from "url";
|
||||
import { IMAGE_TYPES, REG_LINKINDEX_INVALIDCHARS } from "../Constants";
|
||||
import { DEVICE, IMAGE_TYPES, REG_LINKINDEX_INVALIDCHARS } from "../Constants";
|
||||
import ExcalidrawView from "../ExcalidrawView";
|
||||
import { t } from "../lang/helpers";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
@@ -25,11 +26,15 @@ export class InsertImageDialog extends FuzzySuggestModal<TFile> {
|
||||
this.emptyStateText = t("NO_MATCH");
|
||||
this.inputEl.onkeyup = (e) => {
|
||||
//@ts-ignore
|
||||
if (e.key === "Enter" && e.altKey && this.chooser.values) {
|
||||
if (e.key === "Enter" && scaleToFullsizeModifier(e) && this.chooser.values) {
|
||||
this.onChooseItem(
|
||||
//@ts-ignore
|
||||
this.chooser.values[this.chooser.selectedItem].item,
|
||||
new KeyboardEvent("keypress",{altKey: true})
|
||||
new KeyboardEvent("keypress",{
|
||||
shiftKey: true,
|
||||
metaKey: !(DEVICE.isIOS || DEVICE.isMacOS),
|
||||
ctrlKey: (DEVICE.isIOS || DEVICE.isMacOS),
|
||||
})
|
||||
);
|
||||
this.close();
|
||||
}
|
||||
@@ -51,12 +56,11 @@ export class InsertImageDialog extends FuzzySuggestModal<TFile> {
|
||||
}
|
||||
|
||||
onChooseItem(item: TFile, event: KeyboardEvent): void {
|
||||
const ea = this.plugin.ea;
|
||||
ea.reset();
|
||||
ea.setView(this.view);
|
||||
const ea = this.plugin.ea.getAPI(this.view);
|
||||
ea.canvas.theme = this.view.excalidrawAPI.getAppState().theme;
|
||||
const scaleToFullsize = scaleToFullsizeModifier(event);
|
||||
(async () => {
|
||||
await ea.addImage(0, 0, item, !event.altKey);
|
||||
await ea.addImage(0, 0, item, !scaleToFullsize);
|
||||
ea.addElementsToView(true, false, true);
|
||||
})();
|
||||
}
|
||||
|
||||
@@ -11,12 +11,280 @@ Thank you & Enjoy!
|
||||
`;
|
||||
|
||||
export const RELEASE_NOTES: { [k: string]: string } = {
|
||||
Intro: `I want to help you keep up with all the updates. After installing each release, you'll be prompted with a summary of new features and fixes. You can disable these popup messages in plugin settings.
|
||||
Intro: `After each update you'll be prompted with the release notes. You can disable this in plugin settings.
|
||||
|
||||
I develop this plugin as a hobby, spending most of my free time doing this. If you'd like to contribute to the on-going work, I have a simple membership scheme with Bronze, Silver and Gold tiers. Many of you have already bought me a coffee. THANK YOU! It really means a lot to me! If you find this plugin valuable, please consider supporting me.
|
||||
I develop this plugin as a hobby, spending my free time doing this. If you find it valuable, then please say THANK YOU or...
|
||||
|
||||
<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.8.19": `
|
||||
## Fixed: Text wrapping issue in sticky notes
|
||||
|
||||
I fixed an issue where text would wrap differently and words would disappear during text editing in sticky notes. You can check out the details on [GitHub #6318](https://github.com/excalidraw/excalidraw/issues/6331).
|
||||
|
||||
I am aware of three additional issues related to container text editing that are still open. I apologize for any inconvenience caused by the recent change in how text size is calculated on Excalidraw.com, which has had a knock-on effect on Obsidian. I am actively working to address the following issues:
|
||||
|
||||
- Pinch zooming while editing text in a text container [GitHub #6331](https://github.com/excalidraw/excalidraw/issues/6331)
|
||||
- Container text jumps on edit on Android with on-screen keyboard [GitHub #6330](https://github.com/excalidraw/excalidraw/issues/6330)
|
||||
- Shadow text when editing text containers without a keyboard on iOS [GitHub #6329](https://github.com/excalidraw/excalidraw/issues/6329)
|
||||
|
||||
Thank you for your patience while I work on resolving these issues.
|
||||
`,
|
||||
"1.8.18": `
|
||||
## Fixed
|
||||
- Text scaling issue introduced in 1.8.17
|
||||
- [#1043](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1043): Error handling when ${String.fromCharCode(96)}onCanvasColorChangeHook${String.fromCharCode(96)} is executed. This is used in the [Dynamic Styling Script](https://youtu.be/LtR04fNTKTM).
|
||||
`,
|
||||
"1.8.17": `
|
||||
## New from Excalidraw.com
|
||||
- Improved text wrapping in the ellipse and diamond shapes [6172](https://github.com/excalidraw/excalidraw/pull/6172)
|
||||
|
||||
## New
|
||||
- Updated slideshow script
|
||||
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/mQ2eLk_0TV4" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## Fixed:
|
||||
- "Save to..." in the Stencil Library menu now works as expected [#1032](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1032)
|
||||
`,
|
||||
"1.8.16": `
|
||||
**!!! Modifier keys have changed, please review the table below !!!**
|
||||
[Click this to see the new shortcuts overview image](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/excalidraw-modifiers.png)
|
||||
|
||||
## Fixed
|
||||
- This version was extensively tested and developed on MacOS to remove usability issues.
|
||||
- New command palette action to create a new drawing in a new tab
|
||||
- Modifier keys to open links in the active window, splitting the current view to the right, in a new tab, or in a popout window now behave consistently both in Excalidraw and when clicking a drawing that is embedded in a markdown note.
|
||||
- Drag & Drop properly works from within Obsidian, from a web browser, and from the OS file explorer
|
||||
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/9HlipSIzRhc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
`,
|
||||
"1.8.14":`
|
||||
## Fixed
|
||||
- text element link gets deleted when the drawing is reloaded
|
||||
`,
|
||||
"1.8.13": `
|
||||
## Fixed
|
||||
- When changing a text element in markdown mode, the change seem to have showed up when switching back to Excalidraw mode, but then lost these changes when loading the file the next time.
|
||||
- Scrolling through a page that has embedded drawings on Obsidian Mobile accidently opens the drawing in Excalidraw when touching the image. Now you need to press and hold to open the image in Excalidraw. [#1003](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1003)
|
||||
- The scrollbar is no longer visible when presenting using the SlideShow script
|
||||
- Stroke properties could not be changed when custom pen settings had "Stroke & fill applies to: All shapes". It works now.
|
||||
|
||||
## QoL
|
||||
- Custom pens will remember the stroke changes until you press the pen preset button again.
|
||||
- This is a bit hard to explain, let me try... Essentially, when you use a custom pen, it will keep the changes you made to the pen (like changing the stroke width) until you press the pen-prereset button again. So, for example, if you're using a mind mapping custom pen and change its color, and then switch to a different tool like text, when you switch back to the freedraw tool using the Excalidraw tools panel, the pen will still have the same color you set earlier, but if you press the mind mapping pen-preset button, it will default back to your custom pen settings including your preset color.
|
||||
- Added new buttons to load current stroke color and background color in the pen settings dialog. Also added an edit box so you can configure any valid color string (including with transparency) for pen stroke and background colors. [#991](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/991)
|
||||
`,
|
||||
"1.8.11": `
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/rBarRfcSxNo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
# New
|
||||
- Support for referencing images from the internet in Excalidraw drawings, including YouTube thumbnail support. [#913](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/913)
|
||||
- Link to images on the internet without creating a copy in your Vault by holding down the CTRL key while dropping the link or image.
|
||||
- Automatic conversion of image URLs and YouTube links into image elements with original links added as a link on the element when pasting. Note, that if you only want to paste the plain text link (not the image), first double-click the canvas to start a new text element, then paste the link.
|
||||
- Two new options added to plugin settings:
|
||||
- Make mouse wheel zoom by default [#474](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/474)
|
||||
- Allow pinch zoom in pen mode [#828](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/828)
|
||||
- Update to the [Set Grid](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20Grid.svg) script now saves the grid setting for the current file.
|
||||
`,
|
||||
"1.8.10": `
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/wTtaXmRJ7wg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
# QoL improvements
|
||||
- You can structure icons in the Obsidian tools panel by moving scripts to folders
|
||||

|
||||
- I added useful actions to the hamburger menu in both tray-mode and normal-mode.
|
||||

|
||||
- I added a new Export Image dialog. You can access the new export screen from the hamburger-menu
|
||||

|
||||
- Links in help now point to Obsidian-Excalidraw relevant content.
|
||||
- I added a welcome screen
|
||||

|
||||
- I updated the alternative dark mode / dynamic styling [script](https://gist.github.com/zsviczian/c7223c5b4af30d5c88a0cae05300305c)
|
||||
`,
|
||||
"1.8.9":`
|
||||
# Minor QoL improvements
|
||||
- When you open a second drawing in the same Excalidraw view (i.e. by navigating a link) and make a change to this drawing, and then press UNDO, the entire drawing disappeared. Redo brought the image back, however, this behavior was frustrating. Not anymore...
|
||||
- On iPad
|
||||
- when you open the command palette, autozoom resized the drawing. If the Obsidian command palette or some other modal window is shown Excalidraw will not resize the view.
|
||||
- when you add a link to the drawing using the Command Palette, sometimes the link was added in a far corner of the drawing outside the current view area. This should be fixed now.`,
|
||||
"1.8.8":`
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/uZz5MgzWXiM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
# New
|
||||
- The plugin now includes support for [Perfect Freehand](https://perfect-freehand-example.vercel.app/) pen-options. I've also added a new [Alternative Pens](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Alternative%20Pens.md) script.
|
||||
- Embed scene in exported PNG and SVG images [#860](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/860). This means that the export will be a normal PNG or SVG image with the added functionality that if someone loads the image into excalidraw.com it will open as a normal excalidraw file.
|
||||
- I've added 2 new Command Palette actions (export PNG, export SVG with embedded scene).
|
||||
- If you SHIFT click ${String.fromCharCode(96)} Save as PNG (or SVG)${String.fromCharCode(96)} in the workspace-tab menu, Excalidraw will embed the scene in the export.
|
||||
- I updated the [Organic Line](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Organic%20Line.md) script. It has an improved thick-to-thin look and a new thin-to-thick-to-thin line type.
|
||||
|
||||
# Fixed
|
||||
- Intelligent image width setting [#955](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/955). Before this change, when the embedded image was small, the image would be extended to meet the image width setting in plugin settings. From now on, if the image is smaller than max-width, it will only extend to max-width. You can still set 100% width using custom CSS. See more on that [here](https://github.com/zsviczian/obsidian-excalidraw-plugin#embedded-images).
|
||||
|
||||
# New in ExcalidrawAutomate
|
||||
- I added the ${String.fromCharCode(96)} plaintext${String.fromCharCode(96)} parameter to ${String.fromCharCode(96)}ExcalidrawAutomate.create${String.fromCharCode(96)} . Using this, you can add some text below the frontmatter but above the ${String.fromCharCode(96)}# Text Elements${String.fromCharCode(96)} section. Use this for example to add metadata to your file. (e.g. I use this in my Daily Quote template to add a Dataview field for the ${String.fromCharCode(96)}Author::${String.fromCharCode(96)} and add the quote with a standard block reference, so I can easily reference it in other files. I also add the ${String.fromCharCode(96)}#quote${String.fromCharCode(96)} tag to the file using this.)
|
||||
- The script running in the ScriptEngine now also receives the ${String.fromCharCode(96)}TFile${String.fromCharCode(96)} object for the script itself. You can access this object during execution via the ${String.fromCharCode(96)}utils.scriptFile${String.fromCharCode(96)} variable.
|
||||
`,
|
||||
"1.8.7":`
|
||||
## New from Excalidraw.com
|
||||
- Support shrinking text containers to their original height when text is removed [#6025](https://github.com/excalidraw/excalidraw/pull/6025)
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://user-images.githubusercontent.com/14358394/209404092-579d54e9-7003-48ef-8b82-84be08ba6246.mp4" title="Demo" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## Fixed
|
||||
- removed the white background when editing arrow-label [#6033](https://github.com/excalidraw/excalidraw/pull/6033)
|
||||
- Minor style tweaks
|
||||
- for embedding Excalidraw into Obsidian Canvas. e.g. dragging no longer accidentally creates an image copy of the drawing, and
|
||||
- style tweaks on the Excalidraw canvas
|
||||
|
||||
## New
|
||||
- If you set a different text color and sticky note border color, now if you change the border color, the text color will not be changed.
|
||||
`,
|
||||
"1.8.6":`
|
||||
## New from Excalidraw.com:
|
||||
- Better default radius for rectangles [#5553](https://github.com/excalidraw/excalidraw/pull/5553). Existing drawings will look unchanged, this applies only to new rectangles.
|
||||

|
||||
> [!attention]- ExcalidrawAutomate technical details
|
||||
> - ${String.fromCharCode(96)}strokeSharpness${String.fromCharCode(96)} is now deprecated
|
||||
> - use roundness instead
|
||||
> - ${String.fromCharCode(96)}roundness === null${String.fromCharCode(96)} is legacy ${String.fromCharCode(96)}strokeSharpness = "sharp"${String.fromCharCode(96)}
|
||||
> - ${String.fromCharCode(96)}roundness = { type: RoundnessType; value?: number }${String.fromCharCode(96)}
|
||||
> - type: 1, LEGACY, type:2 PROPORTIONAL_RADIUS, type:3 ADAPTIVE_RADIUS: 3
|
||||
> - value:
|
||||
> - Radius represented as % of element's largest side (width/height).
|
||||
> DEFAULT_PROPORTIONAL_RADIUS = 0.25;
|
||||
> - Fixed radius for the ADAPTIVE_RADIUS algorithm. In pixels.
|
||||
> DEFAULT_ADAPTIVE_RADIUS = 32;
|
||||
|
||||
## New
|
||||
- For Obsidian 1.1.6 and above
|
||||
- Improved embedding into Obsidian Canvas
|
||||
- Improved embedding into Markdown documents
|
||||
- Added setting under ${String.fromCharCode(96)}Display/Default mode when opening Excalidraw${String.fromCharCode(96)} to always open the drawing in view mode on Mobile, but in normal mode on desktop. [#939](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/939)
|
||||
|
||||
## Fixed
|
||||
- Zoom reset tooltip appears twice [#942](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/942)
|
||||
- Hid export library from library menu as it does not work due to Obsidian limitations. Use the command palette export library instead.
|
||||
- Arrow with label did not get exported and embedded correctly [#941](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/941)
|
||||

|
||||
`,
|
||||
"1.8.4":`
|
||||
## New from Excalidraw.com
|
||||
- Labels on Arrows!!! [#5723](https://github.com/excalidraw/excalidraw/pull/5723)
|
||||
- To add a label press "Enter" or "Double click" on the arrow
|
||||
- Use "Cmd/Ctrl+double click" to enter the line editor
|
||||
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://user-images.githubusercontent.com/11256141/192515552-6b6ddc06-5de0-4931-abdd-6ac3a804656d.mp4" title="Demo" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## New
|
||||
- **Changed behavior**: In the Obsidian markdown editor clicking an Excalidraw image will not open the image (to avoid accidentally opening the image on a tablet). To open a drawing for editing in Excalidraw double click or long-tap on it. [#920](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/920)
|
||||
|
||||
## Fixed
|
||||
- Text stroke color is not honored when pasting a HEX color string to an Excalidraw canvas open in an Obsidian popout window [#921](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/921)
|
||||
- The new [multi-line >> multi-element paste behavior](https://github.com/excalidraw/excalidraw/pull/5786) introduced in the previous release did not work as expected in Obsidian. Now it does.
|
||||
`,
|
||||
"1.8.2":`
|
||||
Introducing the [Excalidraw Slideshow Script](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Slideshow.md) - available in the script store
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/HhRHFhWkmCk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## Fixed
|
||||
- Obsidian tools panel gets misplaced after switching Obsidian workspace tabs
|
||||
|
||||
## New in ExcalidrawAutomate
|
||||
- changed ${String.fromCharCode(96)}viewToggleFullScreen(forceViewMode: boolean = false): void${String.fromCharCode(96)}: the function will toggle view mode on when going to full screen and view mode off when terminating full screen.
|
||||
- new functions
|
||||
${String.fromCharCode(96, 96, 96)}typescript
|
||||
setViewModeEnabled(enabled: boolean):void;
|
||||
viewUpdateScene(
|
||||
scene: {
|
||||
elements?: ExcalidrawElement[];
|
||||
appState?: AppState;
|
||||
files?: BinaryFileData;
|
||||
commitToHistory?: boolean;
|
||||
},
|
||||
restore: boolean = false,
|
||||
):void;
|
||||
viewZoomToElements(
|
||||
selectElements: boolean,
|
||||
elements: ExcalidrawElement[]
|
||||
):void;
|
||||
${String.fromCharCode(96, 96, 96)}
|
||||
|
||||
`,
|
||||
"1.8.1": `
|
||||
## New and fixes from Excalidraw.com
|
||||
- New text paste behavior. Pasting multiline text will generate separate text elements unless you hold down the shift button while pasting [#5786](https://github.com/excalidraw/excalidraw/pull/5786)
|
||||
- line editor fixes [#5927](https://github.com/excalidraw/excalidraw/pull/5927)
|
||||
|
||||
## Fixed
|
||||
- The Command Palette "Insert link" action now inserts the new link at the top drawing layer, not at the bottom.
|
||||
- Updated, hopefully, better organized, Plugin Readme.
|
||||
|
||||
## New
|
||||
- Second attempt at moving to React 18. This upgrade is required to maintain alignment with the core Excalidraw product and to continue to benefit from Excalidraw.com enhancements.
|
||||
- Added options to Plugin Settings
|
||||
- to disable autozoom when loading a drawing for the first time [#907](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/907)
|
||||
- to modify autosave interval. You can now set an autosave interval for desktop and for mobile [#888](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/888)
|
||||
|
||||
## New in ExcalidrawAutomate
|
||||
- Published the obsidian_module on the ExcalidrawAutomate object. ${String.fromCharCode(96)}ExcalidrawAutomate.obsidian${String.fromCharCode(96)}. Publishing this object will give script developers increased flexibility and control over script automation.
|
||||
`,
|
||||
"1.8.0": `
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/7gu4ETx7zro" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## New
|
||||
- Optical Character Recognition (OCR). Introducing the MVP (minimum viable product) release of the integration of [Taskbone](https://taskbone.com) OCR into Excalidraw. See the new scan button on the Obsidian tools panel.
|
||||
- New and improved full-screen mode
|
||||
- Activate using the Obsidian tools panel, the Obsidian Command Palette, or the Alt+F11 shortcut
|
||||
- The ESC key no longer closes full-screen
|
||||
- Full-screen mode works properly on iOS as well
|
||||
- Improved Icon visibility on the Obsidian tools panel
|
||||
- Added 3 additional buttons to the tools panel
|
||||
- Force save
|
||||
- Open link (useful on Mobile devices). In the case of LaTeX equations, the button opens the equation properties.
|
||||
- Open the link in a new pane. In the case of embedded markdown documents, the button opens the embed properties.
|
||||
|
||||
## Fixed
|
||||
- The [deconstruct selected elements into a new drawing](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Deconstruct%20selected%20elements%20into%20new%20drawing.md) script now also correctly decomposes transcluded text elements.
|
||||
`,
|
||||
"1.7.30":`
|
||||
Fix:
|
||||
- Forcing the embedded image to always scale to 100% (a feature introduced in [1.7.26](https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/1.7.26)) scaled the embedded excalidraw drawings incorrectly on devices with a pixel ratio of 2 or 3 (e.g. iPads). This is now fixed, however, this fix might retrospectively impact drawings that use this feature. Sorry for that.
|
||||
`,
|
||||
"1.7.29":`
|
||||
- This is a big update that accommodates the **UI redesign** on Excalidraw.com [#5780](https://github.com/excalidraw/excalidraw/pull/5780). The change on the surface may seem superficial, however, I had to tweak a number of things to make it work in Obsidian. I hope I found everything that broke and fixed it, if not, I'll try to fix it quickly...
|
||||
- This update also comes with changes under the hood that **fix issues with Excalidraw Automate** - paving the way for further scripts, plus some smaller bug fixes.
|
||||
- I **reworked text wrapping**. In some cases, text wrapping in SVG exports looked different compared to how the text looked in Excalidraw. This should now be fixed.
|
||||
- If you are using the **Experimental Dynamic Styling** of the Excalidraw Toolbar, then I recommend updating your styling script following base on [this](https://gist.github.com/zsviczian/c7223c5b4af30d5c88a0cae05300305c)
|
||||
`,
|
||||
"1.7.27":`## New
|
||||
- Import SVG drawing as an Excalidraw object. [#679](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/679)
|
||||
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/vlC1-iBvIfo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## Fixed
|
||||
- Large drawings freeze on the iPad when opening the file. I implemented a workaround whereby Excalidraw will avoid zoom-to-fit drawings with over 1000 elements. [#863](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/863)
|
||||
- Reintroduced copy/paste to the context menu
|
||||
`,
|
||||
"1.7.26":`## Fixed
|
||||
- Transcluded block with a parent bullet does not embed sub-bullet [#853](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/853)
|
||||
- Transcluded text will now exclude ^block-references at end of lines
|
||||
@@ -29,637 +297,4 @@ I develop this plugin as a hobby, spending most of my free time doing this. If y
|
||||
## New - power user features
|
||||
- Force the embedded image to always scale to 100%. Note: this is a very niche feature with a very particular behavior that I built primarily for myself (even more so than other features in Excalidraw Obsidian - also built primarily for myself 😉)... This will reset your embedded image to 100% size every time you open the Excalidraw drawing, or in case you have embedded an Excalidraw drawing on your canvas inserted using this function, every time you update the embedded drawing, it will be scaled back to 100% size. This means that even if you resize the image on the drawing, it will reset to 100% the next time you open the file or you modify the original embedded object. This feature is useful when you decompose a drawing into separate Excalidraw files, but when combined onto a single canvas you want the individual pieces to maintain their actual sizes. I use this feature to construct Book-on-a-Page summaries from atomic drawings.
|
||||
- I added an action to the command palette to temporarily disable/enable Excalidraw autosave. When autosave is disabled, Excalidraw will still save your drawing when changing to another Obsidian window, but it will not save every 10 seconds. On a mobile device (but also on a desktop) this can lead to data loss if you terminate Obsidian abruptly (i.e. swipe the application away, or close Obsidian without first closing the drawing). Use this feature if you find Excalidraw laggy.`,
|
||||
"1.7.25":`## Fixed
|
||||
- Tool buttons did not "stick" the first time you clicked them.
|
||||
- Tray (in tray mode) was higher when the help button was visible. The tray in tablet mode was too large and the help button was missing.
|
||||
- ExcalidrawAutomate ${String.fromCharCode(96)}getCM(color:TInput): ColorMaster;${String.fromCharCode(96)} function will now properly convert valid [css color names](https://www.w3schools.com/colors/colors_names.asp) to ColorMaster objects.
|
||||
- The downloaded script icons in the Excalidraw-Obsidian menu were not always correct
|
||||
- The obsidian mobile navigation bar at the bottom overlapped with Excalidraw
|
||||
|
||||
## New
|
||||
- Created ExcalidrawAutomate hook for styling script when the canvas color changes. See sample [onCanvasColorChangeHook](https://gist.github.com/zsviczian/c7223c5b4af30d5c88a0cae05300305c) implementation following the link.
|
||||
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/LtR04fNTKTM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
${String.fromCharCode(96, 96, 96)}typescript
|
||||
/**
|
||||
* If set, this callback is triggered whenever the active canvas color changes
|
||||
*/
|
||||
onCanvasColorChangeHook: (
|
||||
ea: ExcalidrawAutomate,
|
||||
view: ExcalidrawView, //the Excalidraw view
|
||||
color: string,
|
||||
) => void = null;
|
||||
${String.fromCharCode(96, 96, 96)}
|
||||
`,
|
||||
"1.7.24":`
|
||||
# New and improved
|
||||
- **Updated Chinese translation**. Thanks, @tswwe!
|
||||
- **Improved update for TextElement links**: Until now, when you attached a link to a file to a TextElement using the "Create Link" command, this link did not get updated when the file was renamed or moved. Only links created as markdown links in the TextElement text were updated. Now both approaches work. Keep in mind however, that if you have a link in the TextElemenet text, it will override the link attached to the text element using the create link command. [#566](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/566)
|
||||
- **Transclusion filters markdown comments**: Text transclusion in a TextElement using the ${String.fromCharCode(96)}![[file]]${String.fromCharCode(96)} or ${String.fromCharCode(96)}![[file#section]]${String.fromCharCode(96)} format did not filter out markdown comments in the file placed ${String.fromCharCode(96)}%% inside a comment block %%${String.fromCharCode(96)}. Now they do.
|
||||
- **Remove leading '>' from trancluded quotes**: Added a new option in settings under **Links and Transclusion** to remove the leading ${String.fromCharCode(96)}> ${String.fromCharCode(96)} characters from quotes you transclude as a text element in your drawing.
|
||||

|
||||
- **Added support for ${String.fromCharCode(96)}webp${String.fromCharCode(96)}, ${String.fromCharCode(96)}bmp${String.fromCharCode(96)}, and ${String.fromCharCode(96)}ico${String.fromCharCode(96)} images**. This extends the already supported formats (${String.fromCharCode(96)}jpg${String.fromCharCode(96)}, ${String.fromCharCode(96)}gif${String.fromCharCode(96)}, ${String.fromCharCode(96)}png${String.fromCharCode(96)}, ${String.fromCharCode(96)}svg${String.fromCharCode(96)}).
|
||||
- **Added command palette action to reset images to original size**. Select a single image or embedded Excalidraw drawing on your canvas and choose ${String.fromCharCode(96)}Set selected image element size to 100% of original${String.fromCharCode(96)} from the command palette. This function is especially helpful when you combine atomic drawings on a single canvas, keeping each atomic piece in its original excalidraw file (i.e. the way I create [book on a page summaries](https://www.youtube.com/playlist?list=PL6mqgtMZ4NP1-mbCYc3T7mr-unmsIXpEG))
|
||||
- The ${String.fromCharCode(96)}async getOriginalImageSize(imageElement: ExcalidrawImageElement): Promise<{width: number; height: number}>${String.fromCharCode(96)} function is also avaiable via ExcalidrawAutomate. You may use this function to resize images to custom scales (e.g. 50% size, or to fit a certain bounding rectangle).
|
||||
|
||||
# Fixed
|
||||
- **Upgraded perfect freehand package to resolve unwanted dots on end of lines** [#5727](https://github.com/excalidraw/excalidraw/pull/5727)
|
||||
- **Pinch zoom in View mode opens images** resulting in a very annoying behavior [#837](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/837)
|
||||
- **Embedded files** such as transcluded markdown documents and images **did not honor the Obsidian "New Link Format" setting** (shortest path, relative path, absolute path). [#829](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/829)
|
||||
- **Fixed error with dataview queries involving Excalidraw files**: In case you created a task on an Excalidraw canvas (${String.fromCharCode(96)}docA.md${String.fromCharCode(96)}) by typing ${String.fromCharCode(96)}- [ ] Task [[owner]] #tag${String.fromCharCode(96)}, and then you created a Dataview tasklist in another document (${String.fromCharCode(96)}docB.md${String.fromCharCode(96)}) such that the query criteria matched the task in ${String.fromCharCode(96)}docA.md${String.fromCharCode(96)}, then the task from ${String.fromCharCode(96)}docA.md${String.fromCharCode(96)} only appeared as an empty line when viewing ${String.fromCharCode(96)}docB.md${String.fromCharCode(96)}. If you now embedded ${String.fromCharCode(96)}docB.md${String.fromCharCode(96)} into a third markdown document (${String.fromCharCode(96)}docC.md${String.fromCharCode(96)}), then instead of the contents of ${String.fromCharCode(96)}docB.md${String.fromCharCode(96)} Obsidian rendered ${String.fromCharCode(96)}docA.md${String.fromCharCode(96)}. [#835](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/835)
|
||||
`,
|
||||
"1.7.22":`
|
||||
# Fixed
|
||||
- Text size in sticky notes increased when opening the drawing and when editing a sticky note [#824](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/824)
|
||||
- ToDo rendering did not work properly when there were parsed links in the text
|
||||
- Horizontal text alignment in sticky notes did not honor text alignment setting when resizing text. The text was always aligned center even when text alignment was left or right. [#5720](https://github.com/excalidraw/excalidraw/issues/5720)
|
||||
`,
|
||||
"1.7.21":`
|
||||
# New from Excalidraw.com
|
||||
- Image-mirroring in export preview and in exported SVG [#5700](https://github.com/excalidraw/excalidraw/pull/5700), [#811](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/811), [#617](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/617)
|
||||
|
||||
# New
|
||||
- Ctrl+s will force-save your drawing and update all your transclusions
|
||||
- Added setting to parse ${String.fromCharCode(96)}- [ ] ${String.fromCharCode(96)} and ${String.fromCharCode(96)}- [x] ${String.fromCharCode(96)} todo items. Parsing is disabled by default. This feature can be found under "Links and Transclusions" in Plugin Settings. [#819](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/819)
|
||||
|
||||

|
||||
|
||||
<iframe src="https://user-images.githubusercontent.com/14358394/192151120-3c61c822-0352-4ba7-9900-b38078fb373c.mp4" title="Demo" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
- Added new scripts to the script library
|
||||
- [Rename Image](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Rename%20Image.md)
|
||||
- [Text Arch](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Text%20Arch.md)
|
||||
|
||||
<iframe src="https://user-images.githubusercontent.com/14358394/192151105-78c0115b-4e30-4296-b647-e3c05851a48f.mp4" title="Demo" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
# Fixed
|
||||
- Fixed toast message to display script name on press and hold on mobile and iPad.
|
||||
- Fixed save error when the embedded image file is not found (i.e. it was moved, renamed, or deleted)
|
||||
|
||||
`,
|
||||
"1.7.20":`
|
||||
# New from Excalidraw.com
|
||||
- support segment midpoints in line editor [#5641](https://github.com/excalidraw/excalidraw/pull/5641)
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://user-images.githubusercontent.com/11256141/187417807-3efeb673-6c96-4744-be0e-70119b0c6839.mp4" title="Demo" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
# Fixed
|
||||
- When editing a line or arrow and selecting a tool on the toolbar, the tool jumps back to the selection tool and you need to click again to select the tool [#5703](https://github.com/excalidraw/excalidraw/issues/5703)
|
||||
- Minor improvement of autosave, hopefully decreasing occasional lagging
|
||||
`,
|
||||
"1.7.19":`
|
||||
# QoL improvements
|
||||
- Reintroduced the help button. I also added the help button to the Tray (in Tray Mode) and moved help to the canvas action panel (in non-TrayMode) because in Obsidian 0.16.0 the status bar hides the help icon.
|
||||
- Resetting the canvas with the "Reset Canvas" button will now preserve your custom color palette.
|
||||
- I updated the [Set background color of unlclosed line object](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Set%20background%20color%20of%20unclosed%20line%20object%20by%20adding%20a%20shadow%20clone.md) script. The script will now add background color to open freedraw objects as well. You no longer need to convert freedraw objects to lines before setting the background color. Check the Script Engine library to download the update.
|
||||
|
||||
# New in Excalidraw Automate
|
||||
- I added the [ColorMaster](https://github.com/lbragile/ColorMaster#readme) library to ExcalidrawAutomate. You can get a CM object by calling ${String.fromCharCode(96)}ExcalidrawAutomate.getCM(<your color comes here>)${String.fromCharCode(96)}. Color master introduces many new ways to manipulate colors from script. I will publish scripts that make use of this new functionality including supporting videos on my YouTube channel in the coming days.
|
||||
`,
|
||||
"1.7.18":`
|
||||
## Critical fix
|
||||
- duplicating text elements, adding text elements from the library, and pasting excalidraw text elements results in a corrupted file!!`,
|
||||
"1.7.17":`
|
||||
## Fixed
|
||||
- Block transclusions sometimes got lost when switching between RAW mode and PREVIEW mode. [#769](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/769)
|
||||
|
||||
## New
|
||||
- Added feature to disable "new Excalidraw version" notification [#770](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/770)
|
||||
- Added option to export both light- and dark-themed images at the same time. If this is enabled Excalidraw will create two files "filename.dark.png" and "filename.light.png" (or .svg depending on your other settings). See practical use case here: [Aadam's Notes](https://notes.aadam.dev/SBYNtPHqsTW9Ck1Kuoxsu/)
|
||||
- Added custom export padding for PNG images. Use the frontmatter key ${String.fromCharCode(96)}excalidraw-export-padding${String.fromCharCode(96)} to set the padding at a file level, or set padding for all your files in plugin settings. The new feature replaces the old "SVG Padding" option and applies to both SVG and PNG exports.
|
||||
|
||||
## ExcalidrawAutomate
|
||||
- Added ${String.fromCharCode(96)}padding${String.fromCharCode(96)} to the createPNG function call.
|
||||
${String.fromCharCode(96, 96, 96)}typescript
|
||||
async createPNG(
|
||||
templatePath?: string,
|
||||
scale: number = 1,
|
||||
exportSettings?: ExportSettings,
|
||||
loader?: EmbeddedFilesLoader,
|
||||
theme?: string,
|
||||
padding?: number,
|
||||
)
|
||||
${String.fromCharCode(96, 96, 96)}
|
||||
`,
|
||||
"1.7.16":`
|
||||
## Fixed
|
||||
- Excalidraw canvas is empty after saving the drawing and re-opening it at a later time. If you accidentally paste Excalidraw elements from the clipboard as the contents of a text element, in certain situations this can corrupt the Excalidraw file and as a result, Excalidraw will load an empty-looking drawing the next time. Changing to markdown view, these files can be repaired, however, to avoid accidental data loss, I have prevented pasting of excalidraw clipboard contents as text elements. [#768](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/768)
|
||||
|
||||
## New
|
||||
- Add zoom % display in tray-mode [737](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/737)
|
||||
`,
|
||||
"1.7.15":`
|
||||
## Fixed
|
||||
- Canvas turns white when adding point for curved line [#760](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/760), [#738](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/738), [#5602](https://github.com/excalidraw/excalidraw/issues/5602)
|
||||
`,
|
||||
"1.7.14": `
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/yZQoJg2RCKI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## New
|
||||
- The ${String.fromCharCode(96)}Copy markdown link for selected element to clipboard${String.fromCharCode(96)} action in the Obsidian menu is now more intelligent. If multiple elements are selected it will copy the Element Reference for the largest element.
|
||||
- When referencing an element in a link pointing to an Excalidraw file using the elementId or the section header as the block reference e.g. ${String.fromCharCode(96)}[[file#^elementID]]${String.fromCharCode(96)}, you can now add the ${String.fromCharCode(96)}group=${String.fromCharCode(96)} prefix, e.g. ${String.fromCharCode(96)}[[file#^group=elementID]]${String.fromCharCode(96)} and the ${String.fromCharCode(96)}area=${String.fromCharCode(96)} prefix, e.g. ${String.fromCharCode(96)}[[file#area=Section heading]]${String.fromCharCode(96)}.
|
||||
- If the ${String.fromCharCode(96)}group=${String.fromCharCode(96)} prefix is found, Excalidraw will select the group of elements in the same group as the element referenced by the elementID or heading section.
|
||||
- If the ${String.fromCharCode(96)}area=${String.fromCharCode(96)} prefix is found, excalidraw will insert a cutout of the image around the referenced element.
|
||||
- The ${String.fromCharCode(96)}area=${String.fromCharCode(96)} selector is not supported when embedding Excalidraw as PNG into your markdown documents.
|
||||
- I added "Toggle left-handed mode" to the Command Palette. The action is only visible if tray-mode is enabled. It will move the tray from left to right and back. [749](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/749)
|
||||
|
||||
## Fixed
|
||||
- Zooming with CTRL+Wheel will no longer trigger hover preview.
|
||||
- When editing text in a text element CTRL+C will not launch the hover preview in case the mouse pointer is over the text element being edited. Hover preview will only show if the element is not in editing mode.
|
||||
- ExcalidrawAutomate did not reliably save changes. This caused issues for example in the "Add link to an existing file and open" script. [#747](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/747)
|
||||
- Create a new folder not working when clicking on a link in Erxcalidraw that points to a file that is in a folder that does not yet exist. [741](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/741)
|
||||
- Downgraded to React 17 due to various stability issues, including [#738](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/738) and [#747](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/747)
|
||||
|
||||
## New in Excalidraw Automate
|
||||
- I added two new Excalidraw Automate functions
|
||||
${String.fromCharCode(96, 96, 96)}typescript
|
||||
/**
|
||||
* Gets the groupId for the group that contains all the elements, or null if such a group does not exist
|
||||
* @param elements
|
||||
* @returns null or the groupId
|
||||
*/
|
||||
getCommonGroupForElements(elements: ExcalidrawElement[]): string;
|
||||
|
||||
/**
|
||||
* Gets all the elements from elements[] that share one or more groupIds with element.
|
||||
* @param element
|
||||
* @param elements - typically all the non-deleted elements in the scene
|
||||
* @returns
|
||||
*/
|
||||
getElementsInTheSameGroupWithElement(element: ExcalidrawElement, elements: ExcalidrawElement[]): ExcalidrawElement[];
|
||||
${String.fromCharCode(96, 96, 96)}`,
|
||||
"1.7.13": `
|
||||
## Fix from Excalidraw.com
|
||||
- Resize multiple elements from center ([#5560](https://github.com/excalidraw/excalidraw/pull/5560))
|
||||
|
||||
## Obsidian 0.16.0 compatibility (getting ready, because 0.16.0 will be available to insiders soon)
|
||||
- ${String.fromCharCode(96)}Install or update Excalidraw Scripts${String.fromCharCode(96)} was only available via the page header button. Because the page header is hidden by default, the install script action is now available through the pane menu and through the command palette as well.
|
||||
- ${String.fromCharCode(96)}Open selected text as link${String.fromCharCode(96)} page header button is now also available via the pane menu
|
||||
- ${String.fromCharCode(96)}Open in Adjacent Pane${String.fromCharCode(96)} and ${String.fromCharCode(96)}Open in Main Workspace${String.fromCharCode(96)} Excalidraw plugin settings is fixed
|
||||
`,
|
||||
"1.7.12": `
|
||||
## New from Excalidraw.com:
|
||||
- Showing a mid-point for lines and arrows. By touching the mid-point you can easily add an additional point to a two-point line. This is especially helpful when working on a tablet with touch input. ([#5534](https://github.com/excalidraw/excalidraw/pull/5534))
|
||||
- Lock angle when editing a line or an arrow with SHIFT pressed. Pressing SHIFT will restrict the edited point to snap to certain discrete angles. ([#5527](https://github.com/excalidraw/excalidraw/pull/5527))
|
||||
|
||||
## Fixed:
|
||||
- Clicking Obsidian search-results pointing to an element on the canvas works again ([#734](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/734))
|
||||
- The feature to allow resizing and rotation of lines and arrows consisting of 3 or more points by showing the bounding box when selected is back ([#5554](https://github.com/excalidraw/excalidraw/pull/5554))
|
||||
|
||||
## New
|
||||
- You can now use the following frontmatter key to allow/prevent automatic export of PNG/SVG images at a file level. This frontmatter will override export settings for the given file. ([#732](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/732)
|
||||
${String.fromCharCode(96)}excalidraw-autoexport: none|both|svg|png${String.fromCharCode(96)}
|
||||
`,
|
||||
"1.7.11": `
|
||||
## Fixed
|
||||
- Markdown files embed into the Excalidraw canvas crashed when the embedded markdown file included a nested Markdown embed with a block reference (i.e. the markdown document you are dropping into Excalidraw included a quote you referenced from another file using a ${String.fromCharCode(96)}[[other-file#^blockref]]${String.fromCharCode(96)} block or section reference.
|
||||
- Horizontal flipping of arrows and lines broke in 1.7.10. ([#726](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/726))
|
||||
`,
|
||||
"1.7.10": `
|
||||
## New from Excalidraw.com
|
||||
- Improved handling of arrows and lines. ([#5501](https://github.com/excalidraw/excalidraw/pull/5501))
|
||||
|
||||
## Fixed
|
||||
- When opening a document in view-mode or zen-mode the panel buttons no longer flash up for a moment before switching to the desired mode. ([#479](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/479))
|
||||
- The "blinding white screen" no longer flashes up while loading the scene if the scene is dark ([#241](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/241))
|
||||
|
||||
## Under the hood
|
||||
- Finalized migration to React 18 (no longer showing an error about React 17 compatibility mode in console log)
|
||||
`,
|
||||
"1.7.9": `
|
||||
## New features and fixes from Excalidraw.com:
|
||||
- The right-click context menu is now scrollable on smaller screens ([#4030](https://github.com/excalidraw/excalidraw/pull/4030), [#5520](https://github.com/excalidraw/excalidraw/pull/5520))
|
||||
- Holding down the shift key while rotating an object will rotate it at discrete angles. Rotation is continuous without the SHIFT key. ([#5500](https://github.com/excalidraw/excalidraw/pull/5500))
|
||||
- Improved cursor alignment when resizing an element proportionally (maintain aspect ratio) by holding SHIFT during resizing. ([#5513](https://github.com/excalidraw/excalidraw/pull/5515))
|
||||
- Improved freedraw performance during editing (now has proper canvas caching), and no more blurry freedraw shapes when exporting on a higher scale. ([#5481](https://github.com/excalidraw/excalidraw/pull/5481))
|
||||
- Sidebar stencil library now correctly scrolls vertically ([#5459](https://github.com/excalidraw/excalidraw/pull/5459))
|
||||
|
||||
## New in Obsidian:
|
||||
- Fullscreen mode on iPad. When there are multiple work panes open, clicking the fullscreen action in the Excalidraw Obsidian menu will hide the other work panes and make Excalidraw fullscreen.
|
||||
|
||||
## Fixes in Obsidian:
|
||||
- Drag&Drop an image from a web browser into Excalidraw ([#697](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/697))
|
||||
- On Obsidian Mobile 1.3.0, when the drawing included an embedded image, switching from markdown-view to Excalidraw-view caused the drawing to disappear (it had to be recovered from backup or synchronization history). ([#715](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/715))
|
||||
- When working on a mobile device (tablet and phone) and using two work panes (one for drawing and the other for editing a markdown document) if you switched focus from the drawing to the markdown document auto-zoom changed the zoom level of the drawing. ([#723](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/723)), ([#705](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/705))
|
||||
- Actions on the Command Palette to create a new drawing in a new pane or reusing an existing adjacent pane; on the main workspace or in the Hover Editor or Popout window, were not working well. See related settings in plugin settings under "Links and transclusions" ([#718](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/718))
|
||||
- There was a problem with links with section references when the header contained space characters ([#704](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/704))
|
||||
- I added additional controls to avoid the fantom warnings about a problem with saving the Excalidraw file. Hopefully, from now on, you'll see this error less frequently ([#701](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/701))
|
||||
`,
|
||||
"1.7.8": `
|
||||
# Optimized for Obsidian 0.15.5
|
||||
- I reworked how the plugin treats the "More options" menu because the old approach was interfering with Obsidian
|
||||
- Did thorough testing of handling of work panes on link click. There are two settings (open in the adjacent pane, and open in the main workspace), and three broad scenarios (Excalidraw in a work pane in the main Obsidian window, Excalidraw in a hover editor, and Excalidraw in an Obsidian popout window). All should work correctly now.
|
||||
`,
|
||||
"1.7.7": `
|
||||
# New
|
||||
- Optimized for Obsidian 0.15.4
|
||||
- On a desktop, you can now use the META key when clicking on a link and it will open the link in a new popout Window.
|
||||
- ([#685](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/685)) Markdown embeds will now display correctly in Excalidraw even if they include photos and recursive markdown embeds. Unfortunately due to the limitations of Safari the inversion of colors on iPads in dark mode will not work well.
|
||||
See an 18 second long demo video [here](https://user-images.githubusercontent.com/14358394/177213263-2a7ef1ca-0614-4190-8955-e830ca6b424b.mp4).
|
||||
|
||||
|
||||
# Fixed
|
||||
- ([#683](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/683)) Copy/Paste Markdown embeds to create another instance of the embed, thus you can reference different sections of the document in your drawing (something I broke in 1.7.6)
|
||||
- ([#684](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/684)) Transclusions incorrectly did not pick up subsections of a section. To understand this change, imagine for example the following document:
|
||||
${String.fromCharCode(96, 96, 96)}markdown
|
||||
# A
|
||||
abc
|
||||
# B
|
||||
xyz
|
||||
## b1
|
||||
123
|
||||
## b2
|
||||
456
|
||||
# C
|
||||
${String.fromCharCode(96, 96, 96)}
|
||||
When you transclude ${String.fromCharCode(96)}![[document#B]]${String.fromCharCode(96)} you expect the following result
|
||||
${String.fromCharCode(96, 96, 96)}markdown
|
||||
B
|
||||
xyz
|
||||
|
||||
b1
|
||||
123
|
||||
|
||||
b2
|
||||
456
|
||||
${String.fromCharCode(96, 96, 96)}
|
||||
Until this fix you only got
|
||||
${String.fromCharCode(96, 96, 96)}markdown
|
||||
B
|
||||
xyz
|
||||
${String.fromCharCode(96, 96, 96)}`,
|
||||
"1.7.6": `
|
||||
This release is the same as 1.7.5 except for two minor fixes
|
||||
- a fix for ExcaliBrain, becuase 1.7.5 broke ExcaliBrain.
|
||||
- I left out the release note from 1.7.5.
|
||||
|
||||
# New
|
||||
- Deployed sidebar for libraries panel from excalidraw.com ([#5274](https://github.com/excalidraw/excalidraw/pull/5274)). You can dock the library to the right side depending on the screen real estate available (i.e. does not work on mobiles).
|
||||
|
||||
# Fixed
|
||||
- When copying 2 identical images from one drawing to another, the second image got corrupted in the process ([#672]https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/672)).
|
||||
- When making a copy of an equation in a drawing and then without first closing/opening the file, immediately copying the new equation to another drawing, the equation did not get displayed until the file was closed and reopened.
|
||||
- Copying a markdown embed from one drawing to another, in the destination the markdown embed appeared without the section/block reference and without the width & height (i.e. these settings had to be done again)
|
||||
- Improved the parsing of section references in embeds. When you had ${String.fromCharCode(96)}&${String.fromCharCode(96)} in the section name in a markdown file, when embedding that markdown document into Excalidraw, the section reference did not work as expected ([#681 ](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/681)).
|
||||
- Improved the logic for autosave to better detect changes to the document, and to reduce too frequent export of ${String.fromCharCode(96)}.png${String.fromCharCode(96)} and/or ${String.fromCharCode(96)}.svg${String.fromCharCode(96)} files, when auto export is enabled in plugin settings.
|
||||
`,
|
||||
"1.7.5": `
|
||||
# New
|
||||
- Deployed sidebar for libraries panel from excalidraw.com ([#5274](https://github.com/excalidraw/excalidraw/pull/5274)). You can dock the library to the right side depending on the screen real estate available (i.e. does not work on mobiles).
|
||||
|
||||
# Fixed
|
||||
- When copying 2 identical images from one drawing to another, the second image got corrupted in the process ([#672]https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/672)).
|
||||
- When making a copy of an equation in a drawing and then without first closing/opening the file, immediately copying the new equation to another drawing, the equation did not get displayed until the file was closed and reopened.
|
||||
- Copying a markdown embed from one drawing to another, in the destination the markdown embed appeared without the section/block reference and without the width & height (i.e. these settings had to be done again)
|
||||
- Improved the parsing of section references in embeds. When you had ${String.fromCharCode(96)}&${String.fromCharCode(96)} in the section name in a markdown file, when embedding that markdown document into Excalidraw, the section reference did not work as expected ([#681 ](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/681)).
|
||||
- Improved the logic for autosave to better detect changes to the document, and to reduce too frequent export of ${String.fromCharCode(96)}.png${String.fromCharCode(96)} and/or ${String.fromCharCode(96)}.svg${String.fromCharCode(96)} files, when auto export is enabled in plugin settings.
|
||||
`,
|
||||
"1.7.4": `
|
||||
- Obsidian 0.15.3 support dragging and dropping work panes between Obsidian windows.
|
||||
- Addressed Obsidian changes affecting the more-options menu.
|
||||
- Addressed incompatibility with Obsidian Mobile 1.2.2.
|
||||
`,
|
||||
"1.7.3": `
|
||||
Obsidian 0.15.3 support for dragging and dropping work panes between Obsidian windows.
|
||||
`,
|
||||
"1.7.2": `
|
||||
Due to some of the changes to the code, I highly recommend restarting Obsidian after installing this update to Excalidraw.
|
||||
|
||||
# Fixed
|
||||
- Stability improvements
|
||||
- Opening links in new panes and creating new drawings from the file explorer works properly again
|
||||
|
||||
# New feature
|
||||
- Two new command palette actions:
|
||||
- Create a new drawing - IN A POPOUT WINDOW
|
||||
- Create a new drawing - IN A POPOUT WINDOW - and embed into active document
|
||||

|
||||
- Added setting to prefer opening the link in the popout window or in the main workspace.
|
||||

|
||||
`,
|
||||
"1.7.1": `
|
||||
Support for Obsidian 0.15.0 popout windows. While there are no new features (apart from the popout window support) under the hood there were some major changes required to make this happen.
|
||||
`,
|
||||
"1.7.0": `
|
||||
This is the first test version of Excalidraw Obsidian supporting Obsidian 0.15.0 popout windows. The current technical solution is not really sustainable, it's more of a working concept. I don't expect any real big issues with this version - on the contrary, this works much better with Obsidian 0.15.0 popout windows, but some of the features aren't working as expected in the Obsidian popouts yet. Also as a consequence of Obsidian 0.15.0 compatibility, multiple hover previews are no longer supported.
|
||||
`,
|
||||
"1.6.34": `
|
||||
With 0.15.1 Obsidian is implementing some exciting, but significant changes to how windows are managed. I need to make some heavy/invasive changes to Excalidraw to adapt. The next version of the Excalidraw Plugin will require Obsidian 0.15.1 or newer. If you are not signed up for Obsidian Insider Builds, you will need to wait few weeks until the new Obsidian version will be made public.
|
||||
|
||||
# Fixed
|
||||
- Error saving when the attachments folder exists but with a different letter case (i.e. ATTACHMENTS instead of attachments) [658](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/658). I added more error tolerance. As a general rule, however, I recommend treating file paths as case-sensitive as some platforms like iOS or LINUX have case-sensitive filenames, and synchronizing your Vault to these platforms will cause you headaches in the future.
|
||||
- Text detached from the container if you immediately clicked the text-align buttons on the properties pane while still editing the text in the container for the very first time. [#657](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/657).
|
||||
- Can't add text to the second container if the first container has text and the second container is centered around the first one. [#5300](https://github.com/excalidraw/excalidraw/issues/5300)
|
||||
`,
|
||||
"1.6.33": `
|
||||
# Fixed
|
||||
- Under some special circumstances when you embedded a drawing (guest) into another drawing (host), the host did not update when you modified the guest, until you closed Excalidraw completely and reopened the host. [#637](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/637)
|
||||
|
||||
# New
|
||||
- ExcalidrawAutomate ${String.fromCharCode(96)}addLabelToLine${String.fromCharCode(96)} adds a text label to a line or arrow. Currently this function only works with simple straight 2-point (start & end) lines.
|
||||
${String.fromCharCode(96, 96, 96)}typescript
|
||||
addLabelToLine(lineId: string, label: string): string
|
||||
${String.fromCharCode(96, 96, 96)}
|
||||
- ExcalidrawAutomate ${String.fromCharCode(96)}ConnectObjects${String.fromCharCode(96)} now returns the ID of the arrow that was created.`,
|
||||
"1.6.32": `
|
||||
## Fixed
|
||||
- Filenames of embedded images and markdown documents did not get updated if the drawing was open in a work-pane while you changed the filename of the embedded file (image or markdown document) [632](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/632).
|
||||
- When you created a new text element and immediately dragged it, sometimes autosave interrupted the drag action and Excalidraw dropped the element you were dragging [630](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/630)
|
||||
- In some edge cases when you had the drawing open on your desktop and you also opened the same image on your tablet, Sync seemed to work in the background but the changes did not appear on the desktop until you closed and opened the drawing again. [629](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/629)
|
||||
- LaTeX support: Excalidraw must download a javascript library from one of the hosting sites for MathJax tex2svg. It seems that some people do not have access to the URL recommended in the first place by [MathJax](https://docs.mathjax.org/en/latest/web/start.html). If LaTeX formulas do not render correctly in Excalidraw, try changing the source server under Compatibility Settings in Excalidraw Plugin Settings. [628](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/628)`,
|
||||
"1.6.31": `
|
||||
Minor update:
|
||||
|
||||
## Fixes
|
||||
- Color picker hotkeys were not working. They are working again [627](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/627)
|
||||
- I updated MathJax (LaTeX) to the newest (3.2.1) release.`,
|
||||
"1.6.30": `
|
||||
## Fixed
|
||||
- The load stencil library button stopped working after 1.6.29 due to an error in the core Excalidraw package. It is now fixed. [#625](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/625).
|
||||
- On iPad (probably other Obsidian mobile devices as well) after opening the command palette the positioning of the pointer was off. From now on, the pointer is automatically re-calibrated every 5 seconds.
|
||||
- I improved shared-vault collaboration sync. If the open file has not been saved for the last 5 minutes (i.e. you are not working on the drawing actively), and a newer remote version of the file is received via sync, then the remote file will simply overwrite the local file (i.e. the behavior of Excalidraw Obsidian prior to implementing Shared (Multiplayer) Vault Synchronization support in 1.6.29). This solution will support active collaboration when parties participating are actively editing the drawing, but also caters to the scenario when you open a drawing on one device (e.g. your desktop) and once you are finished editing you do not close the drawing, but simply put your PC to sleep... then later you edit the same drawing on your tablet. When you turn your desktop PC on the next time, the changes you've made on your tablet will be synchronized by Obsidian sync. In this case the changes from your tablet should be honored. If you have not edited the open drawing for more then 5 minutes (like in this scenario) there is no value in running the file comparison between the local version and the received one. This approach reduces the probability of running into sync conflicts.`,
|
||||
"1.6.29": `
|
||||
## New
|
||||
- I implemented sync support inspired by the new [Obsidian Multiplayer Sync](https://youtu.be/ZyCPhbd51eo) feature (available in insider build v0.14.10).
|
||||
- To manage expectations, this is not real-time collaboration like on Excalidraw.com. Synchronization is delayed by the frequency of the autosave timer (every 10 secs) and the speed of Obsidian sync. Also if a file has conflicting versions, Obsidian sync may delay the delivery of the changed file.
|
||||
- Even if you are not using multiplayer Obsidian Vaults, you may benefit from the improved synchronization, for example when using the freedraw tool on your tablet or phone, and in parallel editing the same drawing (e.g. typing text) on your desktop. I frequently do this in a mind-mapping scenario.
|
||||
- If the same Excalidraw sketch is open on multiple devices then Excalidraw will try to merge changes into the open drawing, thus parallel modifications on different devices are possible. If the same element is edited by multiple parties at the same time, then the foreign (received) version will be honored and the local changes lost.
|
||||
|
||||
## Fixed:
|
||||
- Default embed width setting stopped working. [#622](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/622)
|
||||
- The link tooltip gets stuck on screen after Excalidraw closes [#621](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/621)
|
||||
- Layout error when using the Workspaces core plugin. [#28](https://github.com/zsviczian/excalibrain/issues/28)`,
|
||||
"1.6.28": `
|
||||
## New
|
||||
- When dropping a link from a DataView query into Excalidraw the link will honor your "New link format" preferences in Obsidian. It will add the "shortest path when possible", if that is your setting. If the link includes a block or section reference, then the link will automatically include an alias, such that only the filename is displayed (shortest path possible allowing) [#610](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/610)
|
||||
- If Excalidraw is in a Hover Editor and you open a link in another pane by CTRL+SHIFT+Click then the new page will open in the main workspace, and not in a split pane in the hover editor.
|
||||
|
||||
## Fixed
|
||||
- New text elements get de-selected after auto-save [#609](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/609)
|
||||
- Update opacity of bound text when the opacity of the container is updated [#5142](https://github.com/excalidraw/excalidraw/pull/5142)
|
||||
- ExcalidrawAutomate: openFileInNewOrAdjacentLeaf() function. This also caused an error when clicking a link in Excalidraw in a hover window, when there were no leaves in the main workspace view.`,
|
||||
"1.6.27": `
|
||||
## New Features
|
||||
- While these new features are benefitial for all Excalidraw Automation projects, the current changes are mainly in support of the [ExcaliBrain](https://youtu.be/O2s-h5VKCas) integration. See detailed [Release Notes](https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/1.6.27) on GitHub.
|
||||
`,
|
||||
"1.6.26": `
|
||||
## Fixed
|
||||
- Dragging multiple files onto the canvas will now correctly [#589](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/589)
|
||||
- add multiple links
|
||||
- or if you hold the CTRL/(SHIFT on Mac) while dropping the files, then adding multiple images
|
||||
- Dropped images and links were not selectable with the selection tool until the file was saved. This is now fixed.
|
||||
- Display the linked block/section on link-hover instead of the full page. [#597](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/597)
|
||||
- Hover preview without CTRL/CMD works again. Requires configuration in plugin settings. [#595](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/595)
|
||||
- If you embed the same markdown document into a drawing multiple times, you can now display different sections of the document in each embedded object. [#601](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/601).
|
||||
- If you make a copy of an equation and edit this copy, the original equation will remain unchanged [#593](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/593)
|
||||
|
||||
## New Features
|
||||
- When you drag files from Dataview-results onto the canvas the obsidian:// urls will be converted into wiki links.[#599](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/599)
|
||||
- I added one more frontmatter key: ${String.fromCharCode(96)}excalidraw-linkbutton-opacity: ${String.fromCharCode(96)} This sets the opacity of the blue link-button in the top right corner of the element, overriding the respective setting in plugin settings. Valid values are numbers between 0 and 1, where 0 means the button is fully transparent.
|
||||
|
||||
## New Excalidraw Automate Features
|
||||
- As part of building the new [ExcaliBrain](https://youtu.be/O2s-h5VKCas) plugin, I've added a number of integration features. See the GitHub [Release Notes](https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/1.6.26) for details.
|
||||
`,
|
||||
"1.6.25": `
|
||||
## Fixed
|
||||
- Pinch-zoom in view mode was broken ([#5001](https://github.com/excalidraw/excalidraw/pull/5001))
|
||||
- The add image button on iPad was not working ([#5038](https://github.com/excalidraw/excalidraw/pull/5038) & [#584](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/584))
|
||||
|
||||
## New Features
|
||||
- If Excalidraw is open in a [hover-editor](https://github.com/nothingislost/obsidian-hover-editor) when opening a link in a new pane Excalidraw will now open the link in the main workspace and not by splitting the view inside the hover-editor.
|
||||
- Excalidraw ScriptEngine settings
|
||||
- Script Engine settings now render HTML descriptions
|
||||
- If the ${String.fromCharCode(96)}height${String.fromCharCode(96)} property of a text setting is set, the corresponding text input field will be rendered as a textArea with the specified height.
|
||||
`,
|
||||
"1.6.24": `
|
||||
## Fixed
|
||||
- Link fixes:
|
||||
- Shift+Click on an element link (i.e. a link attached to a rectangle, ellipse, etc) did not open the link in a new leaf.
|
||||
- Clicking a link and opening it in a new leaf will now make the new leaf active and focused after the click.
|
||||
- Pointer calibration:
|
||||
- Opening an Excalidraw drawing with the [hover-editor](https://github.com/nothingislost/obsidian-hover-editor) and dragging the editor to another location corrupted the calibration of the pointer in Excalidraw. Similarly, when rearranging workspace panes by dragging, Excalidraw lost pointer calibration.
|
||||
|
||||
## New Features
|
||||
### From Excalidraw.com
|
||||
- Element locking: The lock and unlock action is in the context menu.
|
||||
|
||||
### Plugin
|
||||
- Any element that has a link, ctrl/cmd+clicking anywhere on the object will trigger the link action. You no longer have to go to the link icon. ([#541](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/541#issuecomment-1075578365))
|
||||
`,
|
||||
"1.6.23": `
|
||||
## Fixed:
|
||||
- I have received some user feedback about cases where the text separated from the sticky note. This version comes with a cleanup algorithm that will try to automatically resolve these issues.
|
||||
- Autosave did not notice changes in a very obscure case, when you opened a drawing, resized an element, and without deselecting the element you immediately closed the drawing. ([565](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/565))
|
||||
- CTRL+Enter to create a task did not work in hover-editor when opened from Excalidraw. Now it does! Thanks @pjeby! ([567](https://github.com/zsviczian/obsidian-excalidraw-plugin/pull/567))
|
||||
|
||||
## New Features
|
||||
- If you have the [Obsidian-Latex](https://github.com/xldenis/obsidian-latex) plugin installed, from now Excalidraw will also process the ${String.fromCharCode(
|
||||
96,
|
||||
)}preambles.sty${String.fromCharCode(
|
||||
96,
|
||||
)} file. ( [563](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/563))
|
||||
- I added a new setting ${String.fromCharCode(
|
||||
96,
|
||||
)}Embed & Export >> If found, use the already exported image for preview${String.fromCharCode(
|
||||
96,
|
||||
)}. This setting works in conjunction with the ${String.fromCharCode(
|
||||
96,
|
||||
)}Auto-export SVG/PNG${String.fromCharCode(
|
||||
96,
|
||||
)} settings. If an exported image that matches the file name of the drawing is available, use that image instead of generating a preview image on the fly. This will result in faster previews especially when you have many embedded objects in the drawing, however, it may happen that your latest changes are not displayed and that the image will not automatically match your Obsidian theme in case you have changed the Obsidian theme since the export was created. This setting only applies to embedding images into markdown documents. For a number of reasons, the same approach cannot be used to expedite the loading of drawings with many embedded objects. See release notes for a [demo video](https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/1.6.22).
|
||||
`,
|
||||
"1.6.22": `
|
||||
## Fixed:
|
||||
- "Create a new drawing - IN THE CURRENT ACTIVE PANE - and embed into active document" did not work as intended when an Excalidraw pane was already open. [#559](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/559)
|
||||
- [Obsidian-hover-editor](https://github.com/nothingislost/obsidian-hover-editor) related improvements [#555](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/555):
|
||||
- hovering triggered many hover preview windows in quick succession, and in some cases raised dozens of errors in the Developer Console
|
||||
- hover-editors were not visible in Excalidraw fullscreen mode
|
||||
|
||||
## Minor new features:
|
||||
- Activating the eraser with key "e" will toggle the active tool and back. So for example if you are drawing a freedraw shape, you can press "e" to delete a few strokes, then press "e" again to continue drawing. On desktop PCs many styluses allow you to configure the pen button to trigger keypress "e".
|
||||
- New setting to enable penMode by default.
|
||||
- I increased the file size limit for images you paste into Excalidraw from 2MB to 20MB. You should however avoid very large images as they will impact the overall performance of the canvas. ([#557](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/557))
|
||||
`,
|
||||
"1.6.21": `
|
||||
Before I move on to implementing further features, I spent this week with further stabilizing and debugging the plugin. Hopefully this will result in a smoother, better experince for you all.
|
||||
|
||||
## Fixed
|
||||
- Links in drawings (e.g. text elements or embedded images) were sometimes not updating when the source file was moved or renamed in your Vault. The issue happend when you had the drawing and the linked file open in panes next to each other. This has led to broken links. ([#546](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/546))
|
||||
- To remove complexity and potential error, I have hidden the autosave settings. From now, autosave is now always enabled. Excalidraw will attempt to save your drawing every 10 seconds, or if you are actively engaged in drawing a shape at that very moment (e.g. you are busy with a freedraw line), then autosave will save the drawing at the earliest next opportunity. I imlemented further triggers to save the drawing when there are changes in the drawing and you click outside the drawing canvas. There was a rare error involving text elements, that when happened blocked saving of the file. This error is now properly handeled. Also from now, you will receive a warning message if for any reason save encountered problems.
|
||||
- If you have two heading sections in your drawing, e.g. ${String.fromCharCode(
|
||||
96,
|
||||
)}# Section abc${String.fromCharCode(96)} and ${String.fromCharCode(
|
||||
96,
|
||||
)}# Section abc def${String.fromCharCode(
|
||||
96,
|
||||
)}, then referencing ${String.fromCharCode(
|
||||
96,
|
||||
)}[[#Section abc]]${String.fromCharCode(
|
||||
96,
|
||||
)} in a link will highlight both text elements when clicking the link. These section references now work as expected. ([#530](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/530))`,
|
||||
"1.6.20": `
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/U2LkBRBk4LY" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## Fixed
|
||||
- ${String.fromCharCode(96)}ExcalidrawAutomate.create()${String.fromCharCode(
|
||||
96,
|
||||
)} threw an error [539](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/539)
|
||||
|
||||
## New Features
|
||||
### From excalidraw.com
|
||||
- Bind/unbind text to/from container [4935](https://github.com/excalidraw/excalidraw/pull/4935)
|
||||
|
||||
### Plugin
|
||||
Frontmatter tags to customize image export at a file level [519](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/519). If these keys are present they will override the default excalidraw embed and export settings.
|
||||
- ${String.fromCharCode(
|
||||
96,
|
||||
)}excalidraw-export-transparent: true${String.fromCharCode(96)}
|
||||
- true == Transparent / false == with background.
|
||||
- ${String.fromCharCode(96)}excalidraw-export-dark${String.fromCharCode(96)}
|
||||
- true == Dark mode / false == light mode.
|
||||
- ${String.fromCharCode(96)}excalidraw-export-svgpadding${String.fromCharCode(
|
||||
96,
|
||||
)}
|
||||
- This only affects export to SVG. Specify the export padding for the image
|
||||
- ${String.fromCharCode(96)}excalidraw-export-pngscale${String.fromCharCode(96)}
|
||||
- This only affects export to PNG. Specify the export scale for the image. The typical range is between 0.5 and 5, but you can experiment with other values as well.
|
||||
`,
|
||||
"1.6.19": `
|
||||
This is a minor update fixing left-handed mode on iOS, and deploying improvements to the new Excalidraw Eraser.
|
||||
`,
|
||||
"1.6.18": `
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/4N6efq1DtH0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## Fixed
|
||||
- Modifying properties of a text element in tray mode. [496](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/496)
|
||||
- Friendly page aliases with iframely should work more reliably now.
|
||||
- I further improved resilience of loading from a damaged Excalidraw.md file.
|
||||
|
||||
## New Features
|
||||
### From excalidraw.com
|
||||
- Added Eraser [4887](https://github.com/excalidraw/excalidraw/pull/4887)
|
||||
|
||||
### Plugin
|
||||
- New setting for default transcluded-text line-wrap length. This is the default value for "wrapAt" in ${String.fromCharCode(
|
||||
96,
|
||||
)}![[file#^block]]{wrapAt}${String.fromCharCode(
|
||||
96,
|
||||
)}. Wrapping text using this feature will insert linebreaks in the transcluded text. An alternative approach is to transclude text inside sticky notes, in which case Excalidraw will automatically take care of text wrapping depending on the sticky note's width. [228](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/228)
|
||||
- New command palette action to toggle fullscreen mode, so you can assign a hotkey.
|
||||
- I added basic support for left-handed users. Enable it in plugin settings under the "Display" section. Currently, only affects the position of the tray in tray-mode. [510](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/510)
|
||||
- More flexible filename settings. ⚠ Due to the change, current settings may behave slightly differently compared to before. ⚠ [470](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/470)
|
||||
`,
|
||||
"1.6.17": `
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/Etskjw7a5zo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## Fixed
|
||||
- Freedraw shape's background color was missing in the SVG export. [#443](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/443)
|
||||
- In rare cases, when you only changed the background color of the drawing or edited the dimensions of an embedded markdown document, or changed an existing LaTeX formula, and then moved to another document in the vault, these changes did not get saved. [#503](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/503)
|
||||
- I resolved an Excalidraw Automate glitch with word wrapping in containers. EA generated containers with fixed line breaks. The same error also affected the conversion of drawings from the "legacy" Excalidraw.com file format.
|
||||
- When you allow/disable autosave in settings, this change will immediately take effect for all open Excalidraw workspace leaves. Until now autosave was activated only after you closed and reopened the Excalidraw view. [#502](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/502)
|
||||
- When you create a text element containing a ${String.fromCharCode(
|
||||
96,
|
||||
96,
|
||||
96,
|
||||
)}[[markdown link]]${String.fromCharCode(
|
||||
96,
|
||||
96,
|
||||
96,
|
||||
)} in raw mode, the new link was parsed nonetheless, and sometimes the link disappeared, leaving only the parsed text without the actual link. Creating links in raw-mode now works correctly.
|
||||
|
||||
## New Features
|
||||
- The most recent 5 custom colors from the canvas are now added as color options to the element stroke and element background palette. [#4843](https://github.com/excalidraw/excalidraw/pull/4843)
|
||||
- Vertical text alignment for text in sticky notes [#4852](https://github.com/excalidraw/excalidraw/pull/4852)
|
||||
- Markdown embeds into Excalidraw now receive default styling, including that of tables, blockquotes, and code blocks. I also added a new setting and corresponding frontmatter-key to set the border-color for the embedded markdown document. You can override plugin settings at the document level by adding ${String.fromCharCode(
|
||||
96,
|
||||
96,
|
||||
96,
|
||||
)}excalidraw-border-color: steelblue${String.fromCharCode(
|
||||
96,
|
||||
96,
|
||||
96,
|
||||
)} to the markdown document you want to embed into your drawing. Valid values are css-color-name|#HEXcolor|any-other-html-standard-format.
|
||||
- In Obsidian search, when the text you were searching for is found in an Excalidraw document, clicking the link in search-results will open the drawing with the matching text element selected and zoomed.
|
||||
- Excalidraw now supports linking to text elements on the canvas and linking to non-text objects.
|
||||
1) You can reference text headings just the same as markdown headings in a document
|
||||
i.e. you have a text element that includes a valid markdown heading:
|
||||
${String.fromCharCode(96, 96, 96)}markdown
|
||||
# My Heading
|
||||
details...
|
||||
${String.fromCharCode(96, 96, 96)}
|
||||
or
|
||||
${String.fromCharCode(96, 96, 96)}markdown
|
||||
text element text
|
||||
# my reference
|
||||
${String.fromCharCode(96, 96, 96)}
|
||||
You can reference these like this respectively: ${String.fromCharCode(
|
||||
96,
|
||||
96,
|
||||
96,
|
||||
)}[[#My Heading|display alias]]${String.fromCharCode(
|
||||
96,
|
||||
96,
|
||||
96,
|
||||
)} and ${String.fromCharCode(
|
||||
96,
|
||||
96,
|
||||
96,
|
||||
)}[[#my reference|alias]]${String.fromCharCode(96, 96, 96)}
|
||||
|
||||

|
||||
|
||||
2) You can also reference element ids similar to block references
|
||||
- Links take this form ${String.fromCharCode(
|
||||
96,
|
||||
96,
|
||||
96,
|
||||
)}[[#^elementID|alias]]${String.fromCharCode(96, 96, 96)}
|
||||
- Linking is supported by a new action on the Obsidian Tools Panel
|
||||

|
||||
|
||||
[Release Notes on GitHub](https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/1.6.17)
|
||||
`,
|
||||
"1.6.16": `
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/gMIKXyhS-dM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
|
||||
## Fixed
|
||||
- CMD+Drag from the Obsidian File Manager does not work on Mac. You can now use SHIFT+Drag to embed an image or markdown document into a scene. ([#468](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/468))
|
||||
- Excalidraw Compressed JSON is now cut to smaller chunks (64 characters per paragraph, instead of the earlier 1024 characters). This should address search performance issues. ([#484](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/484))
|
||||
|
||||
## New Features
|
||||
- I added the Obsidian Tools Panel
|
||||
- Click the Obsidian button to access the panel.
|
||||
- The tools panel contains key plugin commands and user / downloaded Excalidraw scripts.
|
||||
- Drag the panel with the handle at the top. Single click on the top to collapse the panel.
|
||||
- On Mobile press and hold the drag handle before dragging, to avoid activating the Obsidian slide in menus.
|
||||
- On Mobile long touch individual buttons on the panel to access tooltips.
|
||||
- Reinstall Excalidraw scripts to get the icons.
|
||||
- If you hold down SHIFT while resizing a sticky note, the text size will scale instead of text wrapping. ([Excalidraw tweet](https://twitter.com/aakansha1216/status/1496116528890417155?s=20&t=taXjA6I9Nd0T-C0wYBsG5g))
|
||||
- SVG export now includes links ([#4791](https://github.com/excalidraw/excalidraw/pull/4791))
|
||||
- Added full screen mode for Obsidian Mobile
|
||||
- Release notes
|
||||
- disable popup in settings
|
||||
- access release notes via the command palette, or the button on the tools panel
|
||||
|
||||
[Release Notes on GitHub](https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/1.6.16)
|
||||
|
||||
[](https://ko-fi.com/zsolt)
|
||||
`,
|
||||
};
|
||||
|
||||
612
src/dialogs/PenSettingsModal.ts
Normal file
@@ -0,0 +1,612 @@
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/types";
|
||||
import { ColorComponent, Modal, Setting, SliderComponent, TextComponent, ToggleComponent } from "obsidian";
|
||||
import { COLOR_NAMES, VIEW_TYPE_EXCALIDRAW } from "src/Constants";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { setPen } from "src/menu/ObsidianMenu";
|
||||
import { ExtendedFillStyle, PenStyle, PenType } from "src/PenTypes";
|
||||
import { PENS } from "src/utils/Pens";
|
||||
import { fragWithHTML, getExportPadding, getExportTheme, getPNGScale, getWithBackground } from "src/utils/Utils";
|
||||
import { __values } from "tslib";
|
||||
|
||||
const EASINGFUNCTIONS: Record<string,string> = {
|
||||
linear: "linear",
|
||||
easeInQuad: "easeInQuad",
|
||||
easeOutQuad: "easeOutQuad",
|
||||
easeInOutQuad: "easeInOutQuad",
|
||||
easeInCubic: "easeInCubic",
|
||||
easeOutCubic: "easeOutCubic",
|
||||
easeInOutCubic: "easeInOutCubic",
|
||||
easeInQuart: "easeInQuart",
|
||||
easeOutQuart: "easeOutQuart",
|
||||
easeInOutQuart: "easeInOutQuart",
|
||||
easeInQuint: "easeInQuint",
|
||||
easeOutQuint: "easeOutQuint",
|
||||
easeInOutQuint: "easeInOutQuint",
|
||||
easeInSine: "easeInSine",
|
||||
easeOutSine: "easeOutSine",
|
||||
easeInOutSine: "easeInOutSine",
|
||||
easeInExpo: "easeInExpo",
|
||||
easeOutExpo: "easeOutExpo",
|
||||
easeInOutExpo: "easeInOutExpo",
|
||||
easeInCirc: "easeInCirc",
|
||||
easeOutCirc: "easeOutCirc",
|
||||
easeInOutCirc: "easeInOutCirc",
|
||||
easeInBack: "easeInBack",
|
||||
easeOutBack: "easeOutBack",
|
||||
easeInOutBack: "easeInOutBack",
|
||||
easeInElastic: "easeInElastic",
|
||||
easeOutElastic: "easeOutElastic",
|
||||
easeInOutElastic: "easeInOutElastic",
|
||||
easeInBounce: "easeInBounce",
|
||||
easeOutBounce: "easeOutBounce",
|
||||
easeInOutBounce: "easeInOutBounce",
|
||||
};
|
||||
|
||||
export class PenSettingsModal extends Modal {
|
||||
private api: ExcalidrawImperativeAPI;
|
||||
private dirty: boolean = false;
|
||||
|
||||
constructor(
|
||||
private plugin: ExcalidrawPlugin,
|
||||
private view: ExcalidrawView,
|
||||
private pen: number,
|
||||
) {
|
||||
super(app);
|
||||
this.api = view.excalidrawAPI;
|
||||
|
||||
}
|
||||
|
||||
onOpen(): void {
|
||||
this.containerEl.classList.add("excalidraw-release");
|
||||
this.titleEl.setText(`Pen Settings`);
|
||||
this.createForm();
|
||||
}
|
||||
|
||||
async onClose() {
|
||||
if(this.dirty) {
|
||||
app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> {
|
||||
if (v.view instanceof ExcalidrawView) v.view.updatePinnedCustomPens()
|
||||
})
|
||||
this.plugin.saveSettings();
|
||||
const pen = this.plugin.settings.customPens[this.pen]
|
||||
const api = this.view.excalidrawAPI;
|
||||
setPen(pen,api);
|
||||
api.setActiveTool({type:"freedraw"});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async createForm() {
|
||||
const hexColor = (color:string):[string,string] => {
|
||||
let opacity = "";
|
||||
if(COLOR_NAMES.has(color)) {
|
||||
return [COLOR_NAMES.get(color),opacity];
|
||||
}
|
||||
const style = new Option().style;
|
||||
style.color = color;
|
||||
if(!!style.color) {
|
||||
const digits = style.color.match(/^[^\d]*(\d*)[^\d]*(\d*)[^\d]*(\d*)[^\d]*([\d\.]*)?/);
|
||||
if(!digits) {
|
||||
return [null,opacity]
|
||||
}
|
||||
opacity = digits[4]
|
||||
? (Math.round(parseFloat(digits[4])*255)<<0).toString(16).padStart(2,"0")
|
||||
: "";
|
||||
return [`#${
|
||||
(parseInt(digits[1])<<0).toString(16).padStart(2,"0")}${
|
||||
(parseInt(digits[2])<<0).toString(16).padStart(2,"0")}${
|
||||
(parseInt(digits[3])<<0).toString(16).padStart(2,"0")}`,opacity]
|
||||
}
|
||||
return [null,opacity]
|
||||
}
|
||||
|
||||
const ps = this.plugin.settings.customPens[this.pen]
|
||||
const ce = this.contentEl;
|
||||
|
||||
ce.createEl("h1",{text: "Pen settings"});
|
||||
|
||||
new Setting(ce)
|
||||
.setName("Pen type")
|
||||
.setDesc("Select type of pen")
|
||||
.addDropdown(dropdown => {
|
||||
dropdown
|
||||
.addOption("default", "Excalidraw Default")
|
||||
.addOption("highlighter", "Highlighter")
|
||||
.addOption("finetip", "Fine tip pen")
|
||||
.addOption("fountain", "Fountain pen")
|
||||
.addOption("marker", "Marker with Outline")
|
||||
.addOption("thick-thin", "Mindmap Thick-Thin")
|
||||
.addOption("thin-thick-thin", "Mindmap Thin-Thick-Thin")
|
||||
.setValue(ps.type)
|
||||
.onChange((value:PenType) => {
|
||||
this.dirty = true;
|
||||
ps.type = value;
|
||||
})
|
||||
})
|
||||
.addButton(button =>
|
||||
button
|
||||
.setButtonText("Apply")
|
||||
.onClick(()=> {
|
||||
this.dirty = true;
|
||||
ps.strokeColor = PENS[ps.type].strokeColor;
|
||||
ps.backgroundColor = PENS[ps.type].backgroundColor;
|
||||
ps.fillStyle = PENS[ps.type].fillStyle;
|
||||
ps.strokeWidth = PENS[ps.type].strokeWidth;
|
||||
ps.roughness = PENS[ps.type].roughness;
|
||||
ps.penOptions = {...PENS[ps.type].penOptions};
|
||||
ce.empty();
|
||||
this.createForm();
|
||||
})
|
||||
)
|
||||
|
||||
let scopeSetting: Setting;
|
||||
|
||||
scopeSetting = new Setting(ce)
|
||||
.setName(fragWithHTML(ps.freedrawOnly?"Stroke & fill applies to: <b>Freedraw only</b>":"Stroke & fill applies to: <b>All shapes</b>"))
|
||||
.setDesc(fragWithHTML(`<b>"All shapes"</b> means that if for example, you select a blue pen with dashed fill and then switch to a different tool (e.g. to a line, a circle, an arrow - i.e. not the freedraw tool), those will all have the same blue line and dashed fill.<br><b>"Only applies to the freedraw line"</b> means that if for example you are writing black text, and you select a custom pen (e.g. a yellow highlighter), then after using the highlighter you switch to another tool, the previous settings (e.g. black stroke color) will apply to the new shape.`))
|
||||
.addToggle(toggle =>
|
||||
toggle
|
||||
.setValue(ps.freedrawOnly)
|
||||
.onChange(value => {
|
||||
this.dirty = true;
|
||||
scopeSetting.setName(fragWithHTML(value?"Stroke & fill applies to: <b>Freedraw only</b>":"Stroke & fill applies to: <b>All shapes</b>"))
|
||||
ps.freedrawOnly = value;
|
||||
})
|
||||
)
|
||||
|
||||
let scSetting: Setting;
|
||||
let sccpComponent: ColorComponent;
|
||||
let sctComponent: TextComponent;
|
||||
let strokeSetting: Setting;
|
||||
let [sHex, sOpacity] = hexColor(ps.strokeColor);
|
||||
let sChangeBounce:boolean = false;
|
||||
|
||||
strokeSetting = new Setting(ce)
|
||||
.setName(fragWithHTML(!Boolean(ps.strokeColor) ? "Stroke color: <b>Current</b>" : "Stroke color: <b>Preset color</b>"))
|
||||
.setDesc(fragWithHTML("Use <b>current</b> stroke color of the canvas, or set a specific <b>preset color</b> for the pen"))
|
||||
.addToggle(toggle =>
|
||||
toggle
|
||||
.setValue(!Boolean(ps.strokeColor))
|
||||
.onChange(value=> {
|
||||
this.dirty = true;
|
||||
scSetting.settingEl.style.display = value ? "none" : "";
|
||||
strokeSetting.setName(fragWithHTML(value ? "Stroke color: <b>Current</b>" : "Stroke color: <b>Preset color</b>"))
|
||||
if(value) {
|
||||
delete ps.strokeColor;
|
||||
} else {
|
||||
if(!sctComponent.getValue()) {
|
||||
[sHex,sOpacity] = hexColor("black");
|
||||
sccpComponent.setValue(sHex)
|
||||
sctComponent.setValue("black");
|
||||
}
|
||||
ps.strokeColor = sctComponent.getValue();
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
scSetting = new Setting(ce)
|
||||
.setName("Select stroke color")
|
||||
.addButton(button=>
|
||||
button
|
||||
.setButtonText("Use Canvas Current")
|
||||
.onClick(()=>{
|
||||
const st = this.api.getAppState();
|
||||
const color = st.resetCustomPen?.currentItemStrokeColor ?? st.currentItemStrokeColor;
|
||||
[sHex,sOpacity] = hexColor(color);
|
||||
ps.strokeColor = color;
|
||||
this.dirty = true;
|
||||
sctComponent.setValue(color);
|
||||
sChangeBounce = true;
|
||||
sccpComponent.setValue(sHex);
|
||||
})
|
||||
)
|
||||
.addText(text => {
|
||||
sctComponent = text;
|
||||
text
|
||||
.setValue(ps.strokeColor)
|
||||
.onChange(value=> {
|
||||
sChangeBounce = true;
|
||||
this.dirty = true;
|
||||
ps.strokeColor = value;
|
||||
[sHex,sOpacity] = hexColor(value);
|
||||
if(sHex) sccpComponent.setValue(sHex);
|
||||
})
|
||||
})
|
||||
.addColorPicker(colorpicker => {
|
||||
sccpComponent = colorpicker;
|
||||
colorpicker
|
||||
.setValue(sHex ?? "#000000")
|
||||
.onChange(value => {
|
||||
if(sChangeBounce) {
|
||||
sChangeBounce = false;
|
||||
return;
|
||||
}
|
||||
this.dirty = true;
|
||||
ps.strokeColor = value + sOpacity;
|
||||
sctComponent.setValue(value + sOpacity);
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
scSetting.settingEl.style.display = !Boolean(ps.strokeColor) ? "none" : "";
|
||||
|
||||
let bgSetting: Setting;
|
||||
let bgcSetting: Setting;
|
||||
let bgctSetting: Setting;
|
||||
let bgcpComponent: ColorComponent;
|
||||
let bgctComponent: TextComponent;
|
||||
let bgtComponent: ToggleComponent;
|
||||
let fsSetting: Setting;
|
||||
let [bgHex, bgOpacity] = hexColor(ps.backgroundColor);
|
||||
|
||||
bgSetting = new Setting(ce)
|
||||
.setName(fragWithHTML(!Boolean(ps.backgroundColor) ? "Background color: <b>Current</b>" : "Background color: <b>Preset color</b>"))
|
||||
.setDesc(fragWithHTML("Toggle to use the <b>current background color</b> of the canvas; or a <b>preset color</b>"))
|
||||
.addToggle(toggle =>
|
||||
toggle
|
||||
.setValue(!Boolean(ps.backgroundColor))
|
||||
.onChange(value=> {
|
||||
this.dirty = true;
|
||||
bgSetting.setName(fragWithHTML(value ? "Background color: <b>Current</b>" : "Background color: <b>Preset color</b>"))
|
||||
bgctSetting.settingEl.style.display = value ? "none" : "";
|
||||
bgcSetting.settingEl.style.display = (value || ps.backgroundColor==="transparent") ? "none" : "";
|
||||
if(value) {
|
||||
delete ps.backgroundColor;
|
||||
} else {
|
||||
if(!bgctComponent.getValue()) {
|
||||
[bgHex, bgOpacity] = hexColor("black");
|
||||
bgcpComponent.setValue(bgHex);
|
||||
bgctComponent.setValue("black");
|
||||
}
|
||||
bgtComponent.setValue(false);
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
bgctSetting = new Setting(ce)
|
||||
.setName(fragWithHTML(ps.backgroundColor==="transparent" ? "Background: <b>Transparent</b>" : "Color: <b>Preset color</b>"))
|
||||
.setDesc("Background has color or is transparent")
|
||||
.addToggle(toggle => {
|
||||
bgtComponent = toggle;
|
||||
toggle
|
||||
.setValue(ps.backgroundColor==="transparent")
|
||||
.onChange(value => {
|
||||
this.dirty = true;
|
||||
bgcSetting.settingEl.style.display = value ? "none" : "";
|
||||
fsSetting.settingEl.style.display = value ? "none" : "";
|
||||
bgctSetting.setName(fragWithHTML(value ? "Background: <b>Transparent</b>" : "Color: <b>Preset color</b>"))
|
||||
ps.backgroundColor = value ? "transparent" : bgcpComponent.getValue();
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
bgctSetting.settingEl.style.display = !Boolean(ps.backgroundColor) ? "none" : "";
|
||||
let bgChangeBounce:boolean = false;
|
||||
bgcSetting = new Setting(ce)
|
||||
.setName("Background color")
|
||||
.addButton(button=>
|
||||
button
|
||||
.setButtonText("Use Canvas Current")
|
||||
.onClick(()=>{
|
||||
const st = this.api.getAppState();
|
||||
const color = st.resetCustomPen?.currentItemBackgroundColor ?? st.currentItemBackgroundColor;
|
||||
[bgHex,bgOpacity] = hexColor(color);
|
||||
ps.backgroundColor = color;
|
||||
this.dirty = true;
|
||||
bgctComponent.setValue(color);
|
||||
bgChangeBounce = true;
|
||||
bgcpComponent.setValue(bgHex);
|
||||
})
|
||||
)
|
||||
.addText(text => {
|
||||
bgctComponent = text;
|
||||
text
|
||||
.setValue(ps.backgroundColor)
|
||||
.onChange(value=> {
|
||||
bgChangeBounce = true;
|
||||
this.dirty = true;
|
||||
ps.backgroundColor = value;
|
||||
[bgHex,bgOpacity] = hexColor(value);
|
||||
if(bgHex) bgcpComponent.setValue(bgHex);
|
||||
})
|
||||
})
|
||||
.addColorPicker(colorpicker => {
|
||||
bgcpComponent = colorpicker;
|
||||
colorpicker
|
||||
.setValue(bgHex ?? "#000000")
|
||||
.onChange(value => {
|
||||
if(bgChangeBounce) {
|
||||
bgChangeBounce = false;
|
||||
return;
|
||||
}
|
||||
this.dirty = true;
|
||||
ps.backgroundColor = value+bgOpacity;
|
||||
bgctComponent.setValue(value+bgOpacity)
|
||||
})
|
||||
})
|
||||
|
||||
bgcSetting.settingEl.style.display = (!Boolean(ps.backgroundColor) || ps.backgroundColor==="transparent") ? "none" : "";
|
||||
|
||||
fsSetting = new Setting(ce)
|
||||
.setName("Fill Style")
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOption("","Unset")
|
||||
.addOption("dots","Dots (⚠ VERY SLOW performance on large objects!)")
|
||||
.addOption("zigzag","Zigzag")
|
||||
.addOption("zigzag-line","Zigzag-line")
|
||||
.addOption("dashed","Dashed")
|
||||
.addOption("hachure","Hachure")
|
||||
.addOption("cross-hatch","Cross-hatch")
|
||||
.addOption("solid","Solid")
|
||||
.setValue(ps.fillStyle)
|
||||
.onChange((value: ExtendedFillStyle) => {
|
||||
this.dirty = true;
|
||||
ps.fillStyle = value;
|
||||
})
|
||||
)
|
||||
fsSetting.settingEl.style.display = (!Boolean(ps.backgroundColor) || ps.backgroundColor==="transparent") ? "none" : "";
|
||||
|
||||
let rSetting: Setting;
|
||||
rSetting = new Setting(ce)
|
||||
.setName(fragWithHTML(`Sloppiness: <b>${ps.roughness === null ? "Not Set" : (ps.roughness<=0.5 ? "Architect (" : (ps.roughness <= 1.5 ? "Artist (" : "Cartoonist ("))}${ps.roughness === null ? "":`${ps.roughness})`}</b>`))
|
||||
.setDesc("Line sloppiness of the shape fill pattern")
|
||||
.addSlider(slider =>
|
||||
slider
|
||||
.setLimits(-0.5,3,0.5)
|
||||
.setValue(ps.roughness === null ? -0.5 : ps.roughness)
|
||||
.onChange(value => {
|
||||
this.dirty = true;
|
||||
ps.roughness = value === -0.5 ? null : value;
|
||||
rSetting.setName(fragWithHTML(`Sloppiness: <b>${ps.roughness === null ? "Not Set" : (ps.roughness<=0.5 ? "Architect (" : (ps.roughness <= 1.5 ? "Artist (" : "Cartoonist ("))}${ps.roughness === null ? "":`${ps.roughness})`}</b>`));
|
||||
})
|
||||
)
|
||||
|
||||
let swSetting: Setting;
|
||||
|
||||
swSetting = new Setting(ce)
|
||||
.setName(fragWithHTML(`Stroke Width <b>${ps.strokeWidth === 0 ? "Not Set" : ps.strokeWidth}</b>`))
|
||||
.addSlider(slider =>
|
||||
slider
|
||||
.setLimits(0,5,0.5)
|
||||
.setValue(ps.strokeWidth)
|
||||
.onChange(value => {
|
||||
this.dirty = true;
|
||||
ps.strokeWidth = value;
|
||||
swSetting.setName(fragWithHTML(`Stroke Width <b>${ps.strokeWidth === 0 ? "Not Set" : ps.strokeWidth}</b>`));
|
||||
})
|
||||
)
|
||||
|
||||
new Setting(ce)
|
||||
.setName("Highlighter pen?")
|
||||
.addToggle(toggle =>
|
||||
toggle
|
||||
.setValue(ps.penOptions.highlighter)
|
||||
.onChange(value => {
|
||||
this.dirty = true;
|
||||
ps.penOptions.highlighter = value;
|
||||
})
|
||||
)
|
||||
|
||||
let spSetting: Setting;
|
||||
|
||||
new Setting(ce)
|
||||
.setName("Pressure sensitve pen?")
|
||||
.setDesc(fragWithHTML(`<b>toggle on</b>: pressure sensitive<br><b>toggle off</b>: constant pressure`))
|
||||
.addToggle(toggle =>
|
||||
toggle
|
||||
.setValue(!ps.penOptions.constantPressure)
|
||||
.onChange(value => {
|
||||
this.dirty = true;
|
||||
ps.penOptions.constantPressure = !value;
|
||||
spSetting.settingEl.style.display = ps.penOptions.constantPressure ? "none" : "";
|
||||
})
|
||||
)
|
||||
|
||||
if(ps.penOptions.hasOutline && ps.penOptions.outlineWidth === 0) {
|
||||
ps.penOptions.outlineWidth = 0.5;
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
if(!ps.penOptions.hasOutline && ps.penOptions.outlineWidth > 0) {
|
||||
ps.penOptions.outlineWidth = 0;
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
let owSetting: Setting;
|
||||
|
||||
owSetting = new Setting(ce)
|
||||
.setName(fragWithHTML(ps.penOptions.outlineWidth === 0 ? `No outline` : `Outline width <b>${ps.penOptions.outlineWidth}</b>`))
|
||||
.setDesc("If the stroke has an outline, this will mean the stroke color is the outline color, and the background color is the pen stroke's fill color. If the pen does not have an outline then the pen color is the stroke color. The Fill Style setting applies to the fill style of the enclosed shape, not of the line itself. The line can only have solid fill.")
|
||||
.addSlider(slider =>
|
||||
slider
|
||||
.setLimits(0,8,0.5)
|
||||
.setValue(ps.penOptions.outlineWidth)
|
||||
.onChange(value => {
|
||||
this.dirty = true;
|
||||
ps.penOptions.outlineWidth = value;
|
||||
ps.penOptions.hasOutline = value > 0;
|
||||
owSetting.setName(fragWithHTML(ps.penOptions.outlineWidth === 0 ? `No outline` : `Outline width <b>${ps.penOptions.outlineWidth}</b>`));
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
ce.createEl("h2",{text: "Perfect Freehand settings"});
|
||||
const p = ce.createEl("p");
|
||||
p.innerHTML = `Read the Perfect Freehand documentation following <a href="https://github.com/steveruizok/perfect-freehand#documentation" target="_blank">this link</a>.`;
|
||||
|
||||
let tSetting: Setting;
|
||||
tSetting = new Setting(ce)
|
||||
.setName(fragWithHTML(`Thinnning <b>${ps.penOptions.options.thinning}</b>`))
|
||||
.setDesc(fragWithHTML(`The effect of pressure on the stroke's size.<br>To create a stroke with a steady line, set the thinning option to 0.<br>To create a stroke that gets thinner with pressure instead of thicker, use a negative number for the thinning option.`))
|
||||
.addSlider(slider =>
|
||||
slider
|
||||
.setLimits(-1,1,0.05)
|
||||
.setValue(ps.penOptions.options.thinning)
|
||||
.onChange(value=> {
|
||||
this.dirty;
|
||||
tSetting.setName(fragWithHTML(`Thinnning <b>${value}</b>`));
|
||||
ps.penOptions.options.thinning = value;
|
||||
})
|
||||
)
|
||||
|
||||
let sSetting: Setting;
|
||||
sSetting = new Setting(ce)
|
||||
.setName(fragWithHTML(`Smoothing <b>${ps.penOptions.options.smoothing}</b>`))
|
||||
.setDesc(fragWithHTML(`How much to soften the stroke's edges.`))
|
||||
.addSlider(slider =>
|
||||
slider
|
||||
.setLimits(0,1,0.05)
|
||||
.setValue(ps.penOptions.options.smoothing)
|
||||
.onChange(value=> {
|
||||
this.dirty;
|
||||
sSetting.setName(fragWithHTML(`Smoothing <b>${value}</b>`));
|
||||
ps.penOptions.options.smoothing = value;
|
||||
})
|
||||
)
|
||||
|
||||
let slSetting: Setting;
|
||||
slSetting = new Setting(ce)
|
||||
.setName(fragWithHTML(`Streamline <b>${ps.penOptions.options.streamline}</b>`))
|
||||
.setDesc(fragWithHTML(` How much to streamline the stroke.`))
|
||||
.addSlider(slider =>
|
||||
slider
|
||||
.setLimits(0,1,0.05)
|
||||
.setValue(ps.penOptions.options.streamline)
|
||||
.onChange(value=> {
|
||||
this.dirty;
|
||||
slSetting.setName(fragWithHTML(`Streamline <b>${value}</b>`));
|
||||
ps.penOptions.options.streamline = value;
|
||||
})
|
||||
)
|
||||
|
||||
new Setting(ce)
|
||||
.setName("Easing function")
|
||||
.setDesc(fragWithHTML(`An easing function for the tapering effect. For more info <a href="https://easings.net/#" target="_blank">click here</a>`))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOptions(EASINGFUNCTIONS)
|
||||
.setValue(ps.penOptions.options.easing)
|
||||
.onChange(value => {
|
||||
this.dirty = true;
|
||||
ps.penOptions.options.easing = value;
|
||||
})
|
||||
)
|
||||
|
||||
spSetting = new Setting(ce)
|
||||
.setName("Simulate Pressure")
|
||||
.setDesc("Whether to simulate pressure based on velocity.")
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOption("true","Always")
|
||||
.addOption("false","Never")
|
||||
.addOption("","Yes for mouse, No for pen")
|
||||
.setValue(
|
||||
ps.penOptions.options.simulatePressure === true
|
||||
? "true"
|
||||
: (ps.penOptions.options.simulatePressure === false
|
||||
? "false"
|
||||
: "")
|
||||
)
|
||||
.onChange(value=>{
|
||||
this.dirty = true;
|
||||
switch(value) {
|
||||
case "true": ps.penOptions.options.simulatePressure = true; break;
|
||||
case "false": ps.penOptions.options.simulatePressure = false; break;
|
||||
default: delete ps.penOptions.options.simulatePressure;
|
||||
}
|
||||
})
|
||||
)
|
||||
spSetting.settingEl.style.display = ps.penOptions.constantPressure ? "none" : "";
|
||||
|
||||
ce.createEl("h3",{text: "Start"});
|
||||
ce.createEl("p",{text: "Tapering options for the start of the line."})
|
||||
|
||||
new Setting(ce)
|
||||
.setName("Cap Start")
|
||||
.setDesc("Whether to draw a cap")
|
||||
.addToggle(toggle=>
|
||||
toggle
|
||||
.setValue(ps.penOptions.options.start.cap)
|
||||
.onChange(value=> {
|
||||
this.dirty = true;
|
||||
ps.penOptions.options.start.cap = value;
|
||||
})
|
||||
)
|
||||
|
||||
let stSetting: Setting;
|
||||
stSetting = new Setting(ce)
|
||||
.setName(fragWithHTML(`Taper: <b>${ps.penOptions.options.start.taper === true ? "true" : ps.penOptions.options.start.taper}</b>`))
|
||||
.setDesc("The distance to taper. If set to true, the taper will be the total length of the stroke.")
|
||||
.addSlider(slider=>
|
||||
slider
|
||||
.setLimits(0,151,1)
|
||||
.setValue(typeof ps.penOptions.options.start.taper === "boolean" ? 151 : ps.penOptions.options.start.taper)
|
||||
.onChange(value => {
|
||||
this.dirty;
|
||||
ps.penOptions.options.start.taper = value === 151 ? true : value;
|
||||
stSetting.setName(fragWithHTML(`Taper: <b>${ps.penOptions.options.start.taper === true ? "true" : ps.penOptions.options.start.taper}</b>`));
|
||||
})
|
||||
)
|
||||
|
||||
new Setting(ce)
|
||||
.setName("Easing function")
|
||||
.setDesc(fragWithHTML(`An easing function for the tapering effect. For more info <a href="https://easings.net/#" target="_blank">click here</a>`))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOptions(EASINGFUNCTIONS)
|
||||
.setValue(ps.penOptions.options.start.easing)
|
||||
.onChange(value => {
|
||||
this.dirty = true;
|
||||
ps.penOptions.options.start.easing = value;
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
ce.createEl("h3",{text: "End"});
|
||||
ce.createEl("p",{text: "Tapering options for the end of the line."})
|
||||
|
||||
new Setting(ce)
|
||||
.setName("Cap End")
|
||||
.setDesc("Whether to draw a cap")
|
||||
.addToggle(toggle=>
|
||||
toggle
|
||||
.setValue(ps.penOptions.options.end.cap)
|
||||
.onChange(value=> {
|
||||
this.dirty = true;
|
||||
ps.penOptions.options.end.cap = value;
|
||||
})
|
||||
)
|
||||
|
||||
let etSetting: Setting;
|
||||
etSetting = new Setting(ce)
|
||||
.setName(fragWithHTML(`Taper: <b>${ps.penOptions.options.end.taper === true ? "true" : ps.penOptions.options.end.taper}</b>`))
|
||||
.setDesc("The distance to taper. If set to true, the taper will be the total length of the stroke.")
|
||||
.addSlider(slider=>
|
||||
slider
|
||||
.setLimits(0,151,1)
|
||||
.setValue(typeof ps.penOptions.options.end.taper === "boolean" ? 151 : ps.penOptions.options.end.taper)
|
||||
.onChange(value => {
|
||||
this.dirty;
|
||||
ps.penOptions.options.end.taper = value === 151 ? true : value;
|
||||
etSetting.setName(fragWithHTML(`Taper: <b>${ps.penOptions.options.end.taper === true ? "true" : ps.penOptions.options.end.taper}</b>`));
|
||||
})
|
||||
)
|
||||
|
||||
new Setting(ce)
|
||||
.setName("Easing function")
|
||||
.setDesc(fragWithHTML(`An easing function for the tapering effect. For more info <a href="https://easings.net/#" target="_blank">click here</a>`))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOptions(EASINGFUNCTIONS)
|
||||
.setValue(ps.penOptions.options.end.easing)
|
||||
.onChange(value => {
|
||||
this.dirty = true;
|
||||
ps.penOptions.options.end.easing = value;
|
||||
})
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,9 @@ import {
|
||||
import ExcalidrawView from "../ExcalidrawView";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { sleep } from "../utils/Utils";
|
||||
import { getNewOrAdjacentLeaf } from "../utils/ObsidianUtils";
|
||||
import { getLeaf, getNewOrAdjacentLeaf } from "../utils/ObsidianUtils";
|
||||
import { checkAndCreateFolder, splitFolderAndFilename } from "src/utils/FileUtils";
|
||||
import { KeyEvent, PaneTarget } from "src/utils/ModifierkeyHelper";
|
||||
|
||||
export class Prompt extends Modal {
|
||||
private promptEl: HTMLInputElement;
|
||||
@@ -370,8 +371,7 @@ export class NewFileActions extends Modal {
|
||||
constructor(
|
||||
private plugin: ExcalidrawPlugin,
|
||||
private path: string,
|
||||
private newPane: boolean,
|
||||
private newWindow: boolean,
|
||||
private keys: KeyEvent,
|
||||
private view: ExcalidrawView,
|
||||
) {
|
||||
super(plugin.app);
|
||||
@@ -387,14 +387,8 @@ export class NewFileActions extends Modal {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
const leaf = this.newWindow
|
||||
//@ts-ignore
|
||||
? app.workspace.openPopoutLeaf()
|
||||
: this.newPane
|
||||
? getNewOrAdjacentLeaf(this.plugin, this.view.leaf)
|
||||
: this.view.leaf;
|
||||
const leaf = getLeaf(this.plugin,this.view.leaf,this.keys)
|
||||
leaf.openFile(file, {active:true});
|
||||
//this.app.workspace.setActiveLeaf(leaf, true, true);
|
||||
}
|
||||
|
||||
createForm(): void {
|
||||
|
||||
@@ -35,7 +35,7 @@ export class ReleaseNotes extends Modal {
|
||||
const message = this.version
|
||||
? Object.keys(RELEASE_NOTES)
|
||||
.filter((key) => key === "Intro" || isVersionNewerThanOther(key,prevRelease))
|
||||
.map((key: string) => `# ${key}\n${RELEASE_NOTES[key]}`)
|
||||
.map((key: string) => `${key==="Intro" ? "" : `# ${key}\n`}${RELEASE_NOTES[key]}`)
|
||||
.slice(0, 10)
|
||||
.join("\n\n---\n")
|
||||
: FIRST_RUN;
|
||||
|
||||
@@ -73,9 +73,9 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "style.strokeSharpness",
|
||||
code: "[string]",
|
||||
desc: "'round' | 'sharp'",
|
||||
field: "style.roundness",
|
||||
code: "[null | { type: RoundnessType; value?: number };]",
|
||||
desc: "set to null for 'sharp', else the stroke will be 'round'<br>type: 1==LEGACY,<br>2==PROPORTIONAL RADIUS,<br>3==ADAPTIVE RADIUS, value: adaptive factor defaults to 32",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
@@ -139,7 +139,7 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "toCliboard",
|
||||
field: "toClipboard",
|
||||
code: "toClipboard(templatePath?: string): void;",
|
||||
desc: "Copies current elements using template to clipboard, ready to be pasted into an excalidraw canvas",
|
||||
after: "",
|
||||
@@ -224,8 +224,8 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
},
|
||||
{
|
||||
field: "addImage",
|
||||
code: "addImage(topX: number, topY: number, imageFile: TFile): Promise<string>;",
|
||||
desc: null,
|
||||
code: "addImage(topX: number, topY: number, imageFile: TFile, scale: boolean): Promise<string>;",
|
||||
desc: "set scale to false if you want to embed the image at 100% of its original size. Default is true which will insert a scaled image",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
@@ -486,6 +486,30 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
desc: "Converts a CSS color name to its HEX color equivalent. 'White' to #FFFFFF",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "obsidian",
|
||||
code: "obsidian",
|
||||
desc: "Access functions and objects available on the <a onclick='window.open(\"https://github.com/obsidianmd/obsidian-api/blob/master/obsidian.d.ts\")'>Obsidian Module</a>",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "setViewModeEnabled",
|
||||
code: "setViewModeEnabled(enabled: boolean): void;",
|
||||
desc: "Sets Excalidraw in the targetView to view-mode",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "viewUpdateScene",
|
||||
code: "viewUpdateScene(scene:{elements?:ExcalidrawElement[],appState?: AppState,files?: BinaryFileData,commitToHistory?: boolean,},restore:boolean=false):void",
|
||||
desc: "Calls the ExcalidrawAPI updateScene function for the targetView. When restore=true, excalidraw will try to correct errors in the scene such as setting default values to missing element properties.",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "viewZoomToElements",
|
||||
code: "viewZoomToElements(selectElements: boolean,elements: ExcalidrawElement[]):void",
|
||||
desc: "Zoom tarteView to fit elements provided as input. elements === [] will zoom to fit the entire scene. SelectElements toggles whether the elements should be in a selected state at the end of the operation.",
|
||||
after: "",
|
||||
},
|
||||
];
|
||||
|
||||
export const EXCALIDRAW_SCRIPTENGINE_INFO: SuggesterInfo[] = [
|
||||
@@ -504,6 +528,12 @@ export const EXCALIDRAW_SCRIPTENGINE_INFO: SuggesterInfo[] = [
|
||||
desc: "Opens a suggester. Displays the displayItems and returns the corresponding item from items[]\nYou need to await the result of suggester.\nIf the user cancels (ESC), suggester will return undefined\nHint and instructions are optional\n\n<code>interface Instruction {command: string;purpose: string;}</code>",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "scriptFile",
|
||||
code: "scriptFile: TFile",
|
||||
desc: "The TFile of the currently running script",
|
||||
after: "",
|
||||
},
|
||||
];
|
||||
|
||||
export const FRONTMATTER_KEYS_INFO: SuggesterInfo[] = [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import "obsidian";
|
||||
//import { ExcalidrawAutomate } from "./ExcalidrawAutomate";
|
||||
export {ExcalidrawAutomateInterface} from "./types";
|
||||
export type { ExcalidrawBindableElement, ExcalidrawElement, FileId, FillStyle, StrokeSharpness, StrokeStyle } from "@zsviczian/excalidraw/types/element/types";
|
||||
export type { ExcalidrawBindableElement, ExcalidrawElement, FileId, FillStyle, StrokeRoundness, StrokeStyle } from "@zsviczian/excalidraw/types/element/types";
|
||||
export type { Point } from "@zsviczian/excalidraw/types/types";
|
||||
export const getEA = (view?:any): any => {
|
||||
try {
|
||||
|
||||
@@ -1,48 +1,55 @@
|
||||
import {
|
||||
DEVICE,
|
||||
FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS,
|
||||
FRONTMATTER_KEY_CUSTOM_PREFIX,
|
||||
FRONTMATTER_KEY_CUSTOM_URL_PREFIX,
|
||||
} from "src/Constants";
|
||||
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/ModifierkeyHelper";
|
||||
|
||||
// English
|
||||
export default {
|
||||
// main.ts
|
||||
INSTALL_SCRIPT: "Install this script",
|
||||
UPDATE_SCRIPT: "An update is available - Click to install",
|
||||
INSTALL_SCRIPT: "Install the script",
|
||||
UPDATE_SCRIPT: "Update available - Click to install",
|
||||
CHECKING_SCRIPT:
|
||||
"Checking if a newer version is available - Click to reinstall now",
|
||||
"Checking for newer version - Click to reinstall",
|
||||
UNABLETOCHECK_SCRIPT:
|
||||
"Update check was unsuccessful - Click to reinstall now",
|
||||
"Update check failed - Click to reinstall",
|
||||
UPTODATE_SCRIPT:
|
||||
"Script is installed and up to date - Click to reinstall now",
|
||||
"Script is up to date - Click to reinstall",
|
||||
OPEN_AS_EXCALIDRAW: "Open as Excalidraw Drawing",
|
||||
TOGGLE_MODE: "Toggle between Excalidraw and Markdown mode",
|
||||
CONVERT_NOTE_TO_EXCALIDRAW: "Convert empty note to Excalidraw Drawing",
|
||||
CONVERT_EXCALIDRAW: "Convert *.excalidraw to *.md files",
|
||||
CREATE_NEW: "New Excalidraw drawing",
|
||||
CREATE_NEW: "Create new drawing",
|
||||
CONVERT_FILE_KEEP_EXT: "*.excalidraw => *.excalidraw.md",
|
||||
CONVERT_FILE_REPLACE_EXT: "*.excalidraw => *.md (Logseq compatibility)",
|
||||
DOWNLOAD_LIBRARY: "Export stencil library as an *.excalidrawlib file",
|
||||
OPEN_EXISTING_NEW_PANE: "Open an existing drawing - IN A NEW PANE",
|
||||
OPEN_EXISTING_NEW_PANE: "Open existing drawing - IN A NEW PANE",
|
||||
OPEN_EXISTING_ACTIVE_PANE:
|
||||
"Open an existing drawing - IN THE CURRENT ACTIVE PANE",
|
||||
TRANSCLUDE: "Transclude (embed) a drawing",
|
||||
TRANSCLUDE_MOST_RECENT: "Transclude (embed) the most recently edited drawing",
|
||||
"Open existing drawing - IN THE CURRENT ACTIVE PANE",
|
||||
TRANSCLUDE: "Embed a drawing",
|
||||
TRANSCLUDE_MOST_RECENT: "Embed the most recently edited drawing",
|
||||
TOGGLE_LEFTHANDED_MODE: "Toggle left-handed mode",
|
||||
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: "Create new drawing - IN AN ADJACENT WINDOW",
|
||||
NEW_IN_NEW_TAB: "Create new drawing - IN A NEW TAB",
|
||||
NEW_IN_ACTIVE_PANE: "Create new drawing - IN THE CURRENT ACTIVE WINDOW",
|
||||
NEW_IN_POPOUT_WINDOW: "Create new drawing - IN A POPOUT WINDOW",
|
||||
NEW_IN_NEW_PANE_EMBED:
|
||||
"Create a new drawing - IN A NEW PANE - and embed into active document",
|
||||
"Create new drawing - IN AN ADJACENT WINDOW - and embed into active document",
|
||||
NEW_IN_NEW_TAB_EMBED:
|
||||
"Create new drawing - IN A NEW TAB - 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 embed 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",
|
||||
DELETE_FILE: "Delete selected Image or Markdown file from Obsidian Vault",
|
||||
"Create new drawing - IN THE CURRENT ACTIVE WINDOW - and embed into active document",
|
||||
NEW_IN_POPOUT_WINDOW_EMBED: "Create new drawing - IN A POPOUT WINDOW - and embed into active document",
|
||||
EXPORT_SVG: "Save as SVG next to current file",
|
||||
EXPORT_PNG: "Save as PNG next to current file",
|
||||
EXPORT_SVG_WITH_SCENE: "Save as SVG with embedded Excalidraw Scene next to current file",
|
||||
EXPORT_PNG_WITH_SCENE: "Save as PNG with embedded Excalidraw Scene next to current file",
|
||||
TOGGLE_LOCK: "Toggle Text Element between edit RAW and PREVIEW",
|
||||
DELETE_FILE: "Delete selected image or Markdown file from Obsidian Vault",
|
||||
INSERT_LINK_TO_ELEMENT:
|
||||
"Copy markdown link for selected element to clipboard. CTRL/CMD+Click to copy group link. SHIFT+click to copy an area link.",
|
||||
`Copy markdown link for selected element to clipboard. ${labelCTRL()}+CLICK to copy 'group=' link. ${labelSHIFT()}+CLICK to copy an 'area=' link. ${labelALT()}+CLICK to watch a help video.`,
|
||||
INSERT_LINK_TO_ELEMENT_GROUP:
|
||||
"Copy 'group=' markdown link for selected element to clipboard.",
|
||||
INSERT_LINK_TO_ELEMENT_AREA:
|
||||
@@ -56,9 +63,10 @@ export default {
|
||||
IMPORT_SVG: "Import an SVG file as Excalidraw strokes (limited SVG support, TEXT currently not supported)",
|
||||
INSERT_MD: "Insert markdown file from vault",
|
||||
INSERT_LATEX:
|
||||
"Insert LaTeX formula (e.g. \\binom{n}{k} = \\frac{n!}{k!(n-k)!})",
|
||||
`Insert LaTeX formula (e.g. \\binom{n}{k} = \\frac{n!}{k!(n-k)!}). ${labelALT()}+CLICK to watch a help video.`,
|
||||
ENTER_LATEX: "Enter a valid LaTeX expression",
|
||||
READ_RELEASE_NOTES: "Read latest release notes",
|
||||
RUN_OCR: "OCR: Grab text from freedraw scribble and pictures to clipboard",
|
||||
TRAY_MODE: "Toggle property-panel tray-mode",
|
||||
SEARCH: "Search for text in drawing",
|
||||
RESET_IMG_TO_100: "Set selected image element size to 100% of original",
|
||||
@@ -68,18 +76,14 @@ export default {
|
||||
//ExcalidrawView.ts
|
||||
INSTALL_SCRIPT_BUTTON: "Install or update Excalidraw Scripts",
|
||||
OPEN_AS_MD: "Open as Markdown",
|
||||
SAVE_AS_PNG: "Save as PNG into Vault (CTRL/CMD+CLICK to export)",
|
||||
SAVE_AS_SVG: "Save as SVG into Vault (CTRL/CMD+CLICK to export)",
|
||||
SAVE_AS_PNG: `Save as PNG into Vault (${labelCTRL()}+CLICK to export; SHIFT to embed scene)`,
|
||||
SAVE_AS_SVG: `Save as SVG into Vault (${labelCTRL()}+CLICK to export; SHIFT to embed scene)`,
|
||||
OPEN_LINK: "Open selected text as link\n(SHIFT+CLICK to open in a new pane)",
|
||||
EXPORT_EXCALIDRAW: "Export to an .Excalidraw file",
|
||||
LINK_BUTTON_CLICK_NO_TEXT:
|
||||
"Select a an ImageElement, or select a TextElement that contains an internal or external link.\n" +
|
||||
"SHIFT CLICK this button to open the link in a new pane.\n" +
|
||||
"CTRL/CMD CLICK the Image or TextElement on the canvas has the same effect!",
|
||||
"Select a ImageElement, or select a TextElement that contains an internal or external link.\n",
|
||||
FILENAME_INVALID_CHARS:
|
||||
'File name cannot contain any of the following characters: * " \\ < > : | ? #',
|
||||
FILE_DOES_NOT_EXIST:
|
||||
"File does not exist. Hold down ALT (or ALT+SHIFT) and CLICK link button to create a new file.",
|
||||
FORCE_SAVE:
|
||||
"Save (will also update transclusions)",
|
||||
RAW: "Change to PREVIEW mode (only effects text-elements with links or transclusions)",
|
||||
@@ -97,7 +101,7 @@ export default {
|
||||
"<b>Toggle OFF:</b> Silent mode. You can still read release notes on <a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/releases'>GitHub</a>.",
|
||||
NEWVERSION_NOTIFICATION_NAME: "Plugin update notification",
|
||||
NEWVERSION_NOTIFICATION_DESC:
|
||||
"<b>Toggle ON:</b> Show a notification when a new version of the plugin is avaiable.<br>" +
|
||||
"<b>Toggle ON:</b> Show a notification when a new version of the plugin is available.<br>" +
|
||||
"<b>Toggle OFF:</b> Silent mode. You need to check for plugin updates in Community Plugins.",
|
||||
|
||||
FOLDER_NAME: "Excalidraw folder",
|
||||
@@ -108,11 +112,11 @@ export default {
|
||||
FOLDER_EMBED_DESC:
|
||||
"Define which folder to place the newly inserted drawing into " +
|
||||
"when using the command palette action: 'Create a new drawing and embed into active document'.<br>" +
|
||||
"<b>Toggle ON:</b> Use Excalidraw folder<br><b>Toggle OFF:</b> use the attachments folder defined in Obsidian settings.",
|
||||
"<b>Toggle ON:</b> Use Excalidraw folder<br><b>Toggle OFF:</b> Use the attachments folder defined in Obsidian settings.",
|
||||
TEMPLATE_NAME: "Excalidraw template file",
|
||||
TEMPLATE_DESC:
|
||||
"Full filepath to the Excalidraw template. " +
|
||||
"E.g.: If your template is in the default Excalidraw folder and it's name is " +
|
||||
"E.g.: If your template is in the default Excalidraw folder and its name is " +
|
||||
"Template.md, the setting would be: Excalidraw/Template.md (or just Excalidraw/Template - you may omit the .md file extension). " +
|
||||
"If you are using Excalidraw in compatibility mode, then your template must be a legacy Excalidraw file as well " +
|
||||
"such as Excalidraw/Template.excalidraw.",
|
||||
@@ -122,6 +126,7 @@ export default {
|
||||
"You can access your scripts from Excalidraw via the Obsidian Command Palette. Assign " +
|
||||
"hotkeys to your favorite scripts just like to any other Obsidian command. " +
|
||||
"The folder may not be the root folder of your Vault. ",
|
||||
SAVING_HEAD: "Saving",
|
||||
COMPRESS_NAME: "Compress Excalidraw JSON in Markdown",
|
||||
COMPRESS_DESC:
|
||||
"By enabling this feature Excalidraw will store the drawing JSON in a Base64 compressed " +
|
||||
@@ -133,15 +138,18 @@ export default {
|
||||
"once you switch back to Excalidraw view. " +
|
||||
"The setting only has effect 'point forward', meaning, existing drawings will not be effected by the setting " +
|
||||
"until you open them and save them.<br><b>Toggle ON:</b> Compress drawing JSON<br><b>Toggle OFF:</b> Leave drawing JSON uncompressed",
|
||||
AUTOSAVE_NAME: "Enable Autosave",
|
||||
AUTOSAVE_DESC:
|
||||
"Automatically save the active drawing, in case there are changes, every 15, 30 seconds, or 1, 2, 3, 4, or 5 minute. Save normally happens when you close Excalidraw or Obsidian, or move " +
|
||||
"focus to another pane. I created this feature with mobile " +
|
||||
"phones and tablets in mind, where 'swiping out Obsidian to close it' led to some data loss.",
|
||||
AUTOSAVE_INTERVAL_NAME: "Interval for autosave",
|
||||
AUTOSAVE_INTERVAL_DESC:
|
||||
"The time interval between saves. Autosave will skip if there are no changes in the drawing.",
|
||||
FILENAME_HEAD: "Filename",
|
||||
AUTOSAVE_INTERVAL_DESKTOP_NAME: "Interval for autosave on Desktop",
|
||||
AUTOSAVE_INTERVAL_DESKTOP_DESC:
|
||||
"The time interval between saves. Autosave will skip if there are no changes in the drawing. " +
|
||||
"Excalidraw will also save the file when closing a workspace tab or navigating within Obsidian, but away from the active Excalidraw tab (i.e. clicking on the Obsidian ribbon or checking backlinks, etc.). " +
|
||||
"Excalidraw will not be able to save your work when terminating Obsidian directly either by killing the Obsidian process, or clicking to close Obsidian altogether.",
|
||||
AUTOSAVE_INTERVAL_MOBILE_NAME: "Interval for autosave on Mobile",
|
||||
AUTOSAVE_INTERVAL_MOBILE_DESC:
|
||||
"I recommend a more frequent interval for Mobiles. " +
|
||||
"Excalidraw will also save the file when closing a workspace tab or navigating within Obsidian, but away from the active Excalidraw tab (i.e. tapping on the Obsidian ribbon or checking backlinks, etc.). " +
|
||||
"Excalidraw will not be able to save your work when terminating Obsidian directly (i.e. swiping it away). Also note, that when you switch apps on a Mobile device, sometimes Android and iOS closes " +
|
||||
"Obsidian in the background to save system resources. In such a case Excalidraw will not be able to save the latest changes.",
|
||||
FILENAME_HEAD: "Filename",
|
||||
FILENAME_DESC:
|
||||
"<p>Click this link for the <a href='https://momentjs.com/docs/#/displaying/format/'>" +
|
||||
"date and time format reference</a>.</p>",
|
||||
@@ -158,7 +166,7 @@ export default {
|
||||
FILENAME_POSTFIX_NAME:
|
||||
"Custom text after markdown Note's name when embedding",
|
||||
FILENAME_POSTFIX_DESC:
|
||||
"Effects filename only when embedding into a markdown document. This is text will be inserted after the note's name, but before the date.",
|
||||
"Effects filename only when embedding into a markdown document. This text will be inserted after the note's name, but before the date.",
|
||||
FILENAME_DATE_NAME: "Filename Date",
|
||||
FILENAME_DATE_DESC:
|
||||
"The last part of the filename. Leave empty if you do not want a date.",
|
||||
@@ -175,7 +183,7 @@ export default {
|
||||
MATCH_THEME_DESC:
|
||||
"If theme is dark, new drawing will be created in dark mode. This does not apply when you use a template for new drawings. " +
|
||||
"Also this will not effect when you open an existing drawing. Those will follow the theme of the template/drawing respectively." +
|
||||
"<br><b>Toggle ON:</b> Follow Obsidian Theme<br><b>Toggle OFF:</b>Follow theme defined in your template",
|
||||
"<br><b>Toggle ON:</b> Follow Obsidian Theme<br><b>Toggle OFF:</b> Follow theme defined in your template",
|
||||
MATCH_THEME_ALWAYS_NAME: "Existing drawings to match Obsidian theme",
|
||||
MATCH_THEME_ALWAYS_DESC:
|
||||
"If theme is dark, drawings will be opened in dark mode. If your theme is light, they will be opened in light mode. " +
|
||||
@@ -187,19 +195,32 @@ export default {
|
||||
DEFAULT_OPEN_MODE_NAME: "Default mode when opening Excalidraw",
|
||||
DEFAULT_OPEN_MODE_DESC:
|
||||
"Specifies the mode how Excalidraw opens: Normal, Zen, or View mode. You may also set this behavior on a file level by " +
|
||||
"adding the excalidraw-default-mode frontmatter key with a value of: normal,view, or zen to your document.",
|
||||
"adding the excalidraw-default-mode frontmatter key with a value of: normal, view, or zen to your document.",
|
||||
DEFAULT_PEN_MODE_NAME: "Pen mode",
|
||||
DEFAULT_PEN_MODE_DESC:
|
||||
"Should pen mode be automatically enabled when opening Excalidraw?",
|
||||
|
||||
DEFAULT_PINCHZOOM_NAME: "Allow pinch zoom in pen mode",
|
||||
DEFAULT_PINCHZOOM_DESC:
|
||||
"Pinch zoom in pen mode when using the freedraw tool is disabled by default to prevent unwanted accidental zooming with your palm.<br>" +
|
||||
"<b>Toggle on: </b>Enable pinch zoom in pen mode<br><b>Toggle off: </b>Disable pinch zoom in pen mode",
|
||||
|
||||
DEFAULT_WHEELZOOM_NAME: "Mouse wheel to zoom by default",
|
||||
DEFAULT_WHEELZOOM_DESC:
|
||||
`<b>Toggle on: </b>Mouse wheel to zoom; ${labelCTRL()} + mouse wheel to scroll</br><b>Toggle off: </b>${labelCTRL()} + mouse wheel to zoom; Mouse wheel to scroll`,
|
||||
|
||||
ZOOM_TO_FIT_NAME: "Zoom to fit on view resize",
|
||||
ZOOM_TO_FIT_DESC: "Zoom to fit drawing when the pane is resized" +
|
||||
"<br><b>Toggle ON:</b> Zoom to fit<br><b>Toggle OFF:</b> Auto zoom disabled",
|
||||
ZOOM_TO_FIT_ONOPEN_NAME: "Zoom to fit on file open",
|
||||
ZOOM_TO_FIT_ONOPEN_DESC: "Zoom to fit drawing when the drawing is first opened" +
|
||||
"<br><b>Toggle ON:</b> Zoom to fit<br><b>Toggle OFF:</b> Auto zoom disabled",
|
||||
ZOOM_TO_FIT_MAX_LEVEL_NAME: "Zoom to fit max ZOOM level",
|
||||
ZOOM_TO_FIT_MAX_LEVEL_DESC:
|
||||
"Set the maximum level to which zoom to fit will enlarge the drawing. Minimum is 0.5 (50%) and maximum is 10 (1000%).",
|
||||
LINKS_HEAD: "Links and transclusion",
|
||||
LINKS_DESC:
|
||||
"CTRL/CMD + CLICK on <code>[[Text Elements]]</code> to open them as links. " +
|
||||
`${labelCTRL()}+CLICK on <code>[[Text Elements]]</code> to open them as links. ` +
|
||||
"If the selected text has more than one <code>[[valid Obsidian links]]</code>, only the first will be opened. " +
|
||||
"If the text starts as a valid web link (i.e. <code>https://</code> or <code>http://</code>), then " +
|
||||
"the plugin will open it in a browser. " +
|
||||
@@ -207,13 +228,13 @@ 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 ${labelCTRL()}+${labelSHIFT()} 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 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. " +
|
||||
`When ${labelCTRL()}+${labelSHIFT()} 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: `${
|
||||
@@ -236,16 +257,16 @@ export default {
|
||||
TODO_DESC: "Icon to use for open TODO items",
|
||||
DONE_NAME: "Completed TODO icon",
|
||||
DONE_DESC: "Icon to use for completed TODO items",
|
||||
HOVERPREVIEW_NAME: "Hover preview without CTRL/CMD key",
|
||||
HOVERPREVIEW_NAME: `Hover preview without pressing the ${labelCTRL()} key`,
|
||||
HOVERPREVIEW_DESC:
|
||||
"<b>Toggle On</b>: In Exalidraw <u>view mode</u> the hover preview for [[wiki links]] will be shown immediately, without the need to hold the CTRL/CMD key. " +
|
||||
`<b>Toggle On</b>: In Exalidraw <u>view mode</u> the hover preview for [[wiki links]] will be shown immediately, without the need to hold the ${labelCTRL()} key. ` +
|
||||
"In Excalidraw <u>normal mode</u>, the preview will be shown immediately only when hovering the blue link icon in the top right of the element.<br> " +
|
||||
"<b>Toggle Off</b>: Hover preview is shown only when you hold the CTRL/CMD key while hovering the link.",
|
||||
`<b>Toggle Off</b>: Hover preview is shown only when you hold the ${labelCTRL()} key while hovering the link.`,
|
||||
LINKOPACITY_NAME: "Opacity of link icon",
|
||||
LINKOPACITY_DESC:
|
||||
"Opacity of the link indicator icon in the top right corner of an element. 1 is opaque, 0 is transparent.",
|
||||
LINK_CTRL_CLICK_NAME:
|
||||
"CTRL/CMD + CLICK on text with [[links]] or [](links) to open them",
|
||||
`${labelCTRL()}+CLICK on text with [[links]] or [](links) to open them`,
|
||||
LINK_CTRL_CLICK_DESC:
|
||||
"You can turn this feature off if it interferes with default Excalidraw features you want to use. If " +
|
||||
"this is turned off, only the link button in the title bar of the drawing pane will open links.",
|
||||
@@ -256,7 +277,7 @@ export default {
|
||||
"text (i.e. no overflow), or OFF to soft-wrap text (at the nearest whitespace).",
|
||||
TRANSCLUSION_DEFAULT_WRAP_NAME: "Transclusion word wrap default",
|
||||
TRANSCLUSION_DEFAULT_WRAP_DESC:
|
||||
"You can set manually set/override word wrapping length using the `![[page#^block]]{NUMBER}` format. " +
|
||||
"You can manually set/override word wrapping length using the `![[page#^block]]{NUMBER}` format. " +
|
||||
"Normally you will not want to set a default, because if you transclude text inside a sticky note, then Excalidraw will automatically take care of word wrapping. " +
|
||||
"Set this value to `0` if you do not want to set a default. ",
|
||||
PAGE_TRANSCLUSION_CHARCOUNT_NAME: "Page transclusion max char count",
|
||||
@@ -264,14 +285,14 @@ export default {
|
||||
"The maximum number of characters to display from the page when transcluding an entire page with the " +
|
||||
"![[markdown page]] format.",
|
||||
QUOTE_TRANSCLUSION_REMOVE_NAME: "Quote translusion: remove leading '> ' from each line",
|
||||
QUOTE_TRANSCLUSION_REMOVE_DESC: "Remove the leading '> ' from each line of the transclusion. This will improve readibility of quotes in text only transclusions<br>" +
|
||||
QUOTE_TRANSCLUSION_REMOVE_DESC: "Remove the leading '> ' from each line of the transclusion. This will improve readability of quotes in text only transclusions<br>" +
|
||||
"<b>Toggle ON:</b> Remove leading '> '<br><b>Toggle OFF:</b> Do not remove leading '> ' (note it will still be removed from the first row due to Obsidian API functionality)",
|
||||
GET_URL_TITLE_NAME: "Use iframely to resolve page title",
|
||||
GET_URL_TITLE_DESC:
|
||||
"Use the <code>http://iframely.server.crestify.com/iframely?url=</code> to get title of page when dropping a link into Excalidraw",
|
||||
MD_HEAD: "Markdown-embed settings",
|
||||
MD_HEAD_DESC:
|
||||
"You can transclude formatted markdown documents into drawings as images CTRL(Shift on Mac) drop from the file explorer or using " +
|
||||
`You can transclude formatted markdown documents into drawings as images ${labelSHIFT()} drop from the file explorer or using ` +
|
||||
"the command palette action.",
|
||||
MD_TRANSCLUDE_WIDTH_NAME: "Default width of a transcluded markdown document",
|
||||
MD_TRANSCLUDE_WIDTH_DESC:
|
||||
@@ -303,7 +324,7 @@ export default {
|
||||
MD_CSS_DESC:
|
||||
"The filename of the CSS to apply to markdown embeds. Provide the filename with extension (e.g. 'md-embed.css'). The css file may also be a plain " +
|
||||
"markdown file (e.g. 'md-embed-css.md'), just make sure the content is written using valid css syntax. " +
|
||||
"If you need to look at the HTML code you are applying the CSS to, then open Obsidian Developer Console (CTRL+SHIFT+i) and type in the following command: " +
|
||||
`If you need to look at the HTML code you are applying the CSS to, then open Obsidian Developer Console (${DEVICE.isIOS || DEVICE.isMacOS ? "CMD+OPT+i" : "CTRL+SHIFT+i"}) and type in the following command: ` +
|
||||
'"ExcalidrawAutomate.mostRecentMarkdownSVG". This will display the most recent SVG generated by Excalidraw. ' +
|
||||
"Setting the font-family in the css is has limitations. By default only your operating system's standard fonts are available (see README for details). " +
|
||||
"You can add one custom font beyond that using the setting above. " +
|
||||
@@ -320,7 +341,7 @@ export default {
|
||||
EMBED_PREVIEW_SVG_NAME: "Display SVG in markdown preview",
|
||||
EMBED_PREVIEW_SVG_DESC:
|
||||
"<b>Toggle ON</b>: Embed drawing as an <a href='https://en.wikipedia.org/wiki/Scalable_Vector_Graphics' target='_blank'>SVG</a> image into the markdown preview.<br>" +
|
||||
"<b>Toggle OFF</b>: Embedd drawing as a <a href='' target='_blank'>PNG</a> image. Note, that some of the <a href='https://www.youtube.com/watch?v=yZQoJg2RCKI&t=633s' target='_blank'>image block referencing features</a> do not work with PNG embeds.",
|
||||
"<b>Toggle OFF</b>: Embed drawing as a <a href='' target='_blank'>PNG</a> image. Note, that some of the <a href='https://www.youtube.com/watch?v=yZQoJg2RCKI&t=633s' target='_blank'>image block referencing features</a> do not work with PNG embeds.",
|
||||
PREVIEW_MATCH_OBSIDIAN_NAME: "Excalidraw preview to match Obsidian theme",
|
||||
PREVIEW_MATCH_OBSIDIAN_DESC:
|
||||
"Image preview in documents should match the Obsidian theme. If enabled, when Obsidian is in dark mode, Excalidraw images will render in dark mode. " +
|
||||
@@ -353,7 +374,7 @@ export default {
|
||||
EXPORT_THEME_DESC:
|
||||
"Export the image matching the dark/light theme of your drawing. If turned off, " +
|
||||
"drawings created in dark mode will appear as they would in light mode.",
|
||||
EXPORT_HEAD: "Export Settings",
|
||||
EXPORT_HEAD: "Auto-export Settings",
|
||||
EXPORT_SYNC_NAME:
|
||||
"Keep the .SVG and/or .PNG filenames in sync with the drawing file",
|
||||
EXPORT_SYNC_DESC:
|
||||
@@ -389,6 +410,10 @@ export default {
|
||||
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.",
|
||||
NONSTANDARD_HEAD: "Non-Excalidraw.com supported features",
|
||||
NONSTANDARD_DESC: "These features are not available on excalidraw.com. When exporting the drawing to Excalidraw.com these features will appear different.",
|
||||
CUSTOM_PEN_NAME: "Number of custom pens",
|
||||
CUSTOM_PEN_DESC: "You will see these pens next to the Obsidian Menu on the canvas. You can customize the pens on the canvas by long-pressing the pen button.",
|
||||
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.",
|
||||
@@ -418,10 +443,22 @@ export default {
|
||||
"Select a .ttf, .woff or .woff2 font file from your vault to use as the fourth font. " +
|
||||
"If no file is selected, Excalidraw will use the Virgil font by default.",
|
||||
SCRIPT_SETTINGS_HEAD: "Settings for installed Scripts",
|
||||
TASKBONE_HEAD: "Taskbone Optical Character Recogntion",
|
||||
TASKBONE_DESC: "This is an experimental integration of optical character recognition into Excalidraw. Please note, that taskbone is an independent external service not provided by Excalidraw, nor the Excalidraw-Obsidian plugin project. " +
|
||||
"The OCR service will grab legible text from freedraw lines and embedded pictures on your canvas and place the recognized text in the frontmatter of your drawing as well as onto clipboard. " +
|
||||
"Having the text in the frontmatter will enable you to search in Obsidian for the text contents of these. " +
|
||||
"Note, that the process of extracting the text from the image is not done locally, but via an online API. The taskbone service stores the image on its servers only as long as necessary for the text extraction. However, if this is a dealbreaker, then please don't use this feature.",
|
||||
TASKBONE_ENABLE_NAME: "Enable Taskbone",
|
||||
TASKBONE_ENABLE_DESC: "By enabling this service your agree to the Taskbone <a href='https://www.taskbone.com/legal/terms/' target='_blank'>Terms and Conditions</a> and the " +
|
||||
"<a href='https://www.taskbone.com/legal/privacy/' target='_blank'>Privacy Policy</a>.",
|
||||
TASKBONE_APIKEY_NAME: "Taskbone API Key",
|
||||
TASKBONE_APIKEY_DESC: "Taskbone offers a free service with a reasonable number of scans per month. If you want to use this feature more frequently, or you want to supoprt " +
|
||||
"the developer of Taskbone (as you can imagine, there is no such thing as 'free', providing this awesome OCR service costs some money to the developer of Taskbone), you can " +
|
||||
"purchase a paid API key from <a href='https://www.taskbone.com/' target='_blank'>taskbone.com</a>. In case you have purchased a key, simply overwrite this auto generated free-tier API-key with your paid key.",
|
||||
|
||||
//openDrawings.ts
|
||||
SELECT_FILE: "Select a file then press enter.",
|
||||
SELECT_FILE_WITH_OPTION_TO_SCALE: "Select a file then press ENTER, or ALT+ENTER to insert at 100% scale.",
|
||||
SELECT_FILE_WITH_OPTION_TO_SCALE: `Select a file then press ENTER, or ${labelSHIFT()}+${labelMETA()}+ENTER to insert at 100% scale.`,
|
||||
NO_MATCH: "No file matches your query.",
|
||||
SELECT_FILE_TO_LINK: "Select the file you want to insert the link for.",
|
||||
SELECT_DRAWING: "Select the image or drawing you want to insert",
|
||||
@@ -446,4 +483,6 @@ export default {
|
||||
GOTO_FULLSCREEN: "Goto fullscreen mode",
|
||||
EXIT_FULLSCREEN: "Exit fullscreen mode",
|
||||
TOGGLE_FULLSCREEN: "Toggle fullscreen mode",
|
||||
OPEN_LINK_CLICK: "Navigate to selected element link",
|
||||
OPEN_LINK_PROPS: "Open markdown-embed properties or open link in new window"
|
||||
};
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
import {
|
||||
DEVICE,
|
||||
FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS,
|
||||
FRONTMATTER_KEY_CUSTOM_PREFIX,
|
||||
FRONTMATTER_KEY_CUSTOM_URL_PREFIX,
|
||||
} from "src/Constants";
|
||||
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/ModifierkeyHelper";
|
||||
|
||||
// 简体中文
|
||||
export default {
|
||||
// main.ts
|
||||
INSTALL_SCRIPT: "安装此脚本",
|
||||
UPDATE_SCRIPT: "发现可用更新 - 点击安装",
|
||||
UPDATE_SCRIPT: "有可用更新 - 点击安装",
|
||||
CHECKING_SCRIPT:
|
||||
"检查脚本更新 - 点击重新安装",
|
||||
"检查更新中 - 点击重新安装",
|
||||
UNABLETOCHECK_SCRIPT:
|
||||
"检查更新失败 - 点击重新安装",
|
||||
UPTODATE_SCRIPT:
|
||||
"已安装最新脚本 - 点击重新安装",
|
||||
"脚本已是最新 - 点击重新安装",
|
||||
OPEN_AS_EXCALIDRAW: "打开为 Excalidraw 绘图",
|
||||
TOGGLE_MODE: "在 Excalidraw 和 Markdown 模式之间切换",
|
||||
CONVERT_NOTE_TO_EXCALIDRAW: "转换空白笔记为 Excalidraw 绘图",
|
||||
@@ -30,60 +32,66 @@ export default {
|
||||
TRANSCLUDE_MOST_RECENT: "嵌入最近编辑过的绘图(形如 ![[drawing]])到当前文档",
|
||||
TOGGLE_LEFTHANDED_MODE: "切换为左手模式",
|
||||
NEW_IN_NEW_PANE: "新建绘图 - 于新面板",
|
||||
NEW_IN_NEW_TAB: "新建绘图 - 于新页签",
|
||||
NEW_IN_ACTIVE_PANE: "新建绘图 - 于当前面板",
|
||||
NEW_IN_POPOUT_WINDOW: "新建绘图 - 于新窗口",
|
||||
NEW_IN_NEW_PANE_EMBED:
|
||||
"新建绘图 - 于新面板 - 并将其嵌入(形如 ![[drawing]])到当前文档",
|
||||
NEW_IN_NEW_TAB_EMBED:
|
||||
"新建绘图 - 于新页签 - 并将其嵌入(形如 ![[drawing]])到当前文档",
|
||||
NEW_IN_ACTIVE_PANE_EMBED:
|
||||
"新建绘图 - 于当前面板 - 并将其嵌入(形如 ![[drawing]])到当前文档",
|
||||
NEW_IN_POPOUT_WINDOW_EMBED: "新建绘图 - 于新窗口 - 并将其嵌入(形如 ![[drawing]])到当前文档",
|
||||
EXPORT_SVG: "导出 SVG 文件到当前目录",
|
||||
EXPORT_PNG: "导出 PNG 文件到当前目录",
|
||||
TOGGLE_LOCK: "切换文本元素为原文模式(RAW)/预览模式(PREVIEW)",
|
||||
EXPORT_SVG_WITH_SCENE: "导出 SVG 文件(包含 Scene)到当前目录",
|
||||
EXPORT_PNG_WITH_SCENE: "导出 PNG 文件(包含 Scene)到当前目录",
|
||||
TOGGLE_LOCK: "文本元素:原文模式(RAW)⟺ 预览模式(PREVIEW)",
|
||||
DELETE_FILE: "从库中删除所选图像(或 MD-Embed)的源文件",
|
||||
INSERT_LINK_TO_ELEMENT:
|
||||
"复制所选元素的内部链接。按住 CTRL/CMD 可复制元素所在分组的内部链接。按住 SHIFT 可复制元素周围区域的内部链接。",
|
||||
`复制所选元素的内部链接(形如 [[file#^elementID]])。\n按住 ${labelCTRL()} 可复制元素所在分组的内部链接(形如 [[file#^group=elementID]])。\n按住 ${labelSHIFT()} 可复制所选元素周围区域的内部链接(形如 [[file#^area=elementID]])。\n按住 ${labelALT()} 可观看视频演示。`,
|
||||
INSERT_LINK_TO_ELEMENT_GROUP:
|
||||
"复制所选元素所在分组的内部链接(形如 [[file#^group=elementID]])",
|
||||
INSERT_LINK_TO_ELEMENT_AREA:
|
||||
"复制所选元素周围区域的内部链接(形如 [[file#^area=elementID]])",
|
||||
INSERT_LINK_TO_ELEMENT_NORMAL:
|
||||
"复制所选元素的引用链接(形如 [[file#^elementID]])",
|
||||
"复制所选元素的内部链接(形如 [[file#^elementID]])",
|
||||
INSERT_LINK_TO_ELEMENT_ERROR: "未选择画布里的单个元素",
|
||||
INSERT_LINK_TO_ELEMENT_READY: "链接已生成并复制到剪贴板",
|
||||
INSERT_LINK: "插入文件的内部链接(形如 [[drawing]])到当前绘图",
|
||||
INSERT_IMAGE: "插入图像(以图像形式嵌入)到当前绘图",
|
||||
IMPORT_SVG: "插入 SVG 矢量图形到当前绘图(支持有限,尚不支持文本)",
|
||||
INSERT_MD: "插入 Markdown 文档(以图像形式嵌入)到当前绘图",
|
||||
INSERT_LATEX:
|
||||
"插入 LaTeX 公式到当前绘图",
|
||||
`插入 LaTeX 公式到当前绘图。按住 ${labelALT()} 可观看视频演示。`,
|
||||
ENTER_LATEX: "输入 LaTeX 表达式",
|
||||
READ_RELEASE_NOTES: "阅读本插件的最新发行版本说明",
|
||||
TRAY_MODE: "切换绘图工具属性页为面板模式(Panel)/托盘模式(Tray)",
|
||||
READ_RELEASE_NOTES: "阅读本插件的更新说明",
|
||||
RUN_OCR: "OCR 识别涂鸦和图片里的文本并复制到剪贴板",
|
||||
TRAY_MODE: "绘图工具属性页:面板模式 ⟺ 托盘模式",
|
||||
SEARCH: "搜索文本",
|
||||
RESET_IMG_TO_100: "重设图像元素的尺寸为 100%",
|
||||
TEMPORARY_DISABLE_AUTOSAVE: "临时禁用自动保存功能,直到 Obsidian 退出(勿点,除非你清楚自己在干什么)",
|
||||
TEMPORARY_ENABLE_AUTOSAVE: "恢复启用自动保存功能",
|
||||
|
||||
//ExcalidrawView.ts
|
||||
INSTALL_SCRIPT_BUTTON: "安装或更新 Excalidraw 自动化脚本",
|
||||
INSTALL_SCRIPT_BUTTON: "安装或更新 Excalidraw 脚本",
|
||||
OPEN_AS_MD: "打开为 Markdown 文件",
|
||||
SAVE_AS_PNG: "导出 PNG 到当前目录(按住 CTRL/CMD 设定导出路径)",
|
||||
SAVE_AS_SVG: "导出 SVG 到当前目录(按住 CTRL/CMD 设定导出路径)",
|
||||
SAVE_AS_PNG: `导出 PNG 到当前目录(按住 ${labelCTRL()} 设定导出路径;按住 SHIFT 在导出时包含 Scene)`,
|
||||
SAVE_AS_SVG: `导出 SVG 到当前目录(按住 ${labelCTRL()} 设定导出路径;按住 SHIFT 在导出时包含 Scene)`,
|
||||
OPEN_LINK: "打开所选元素里的链接 \n(按住 SHIFT 在新面板打开)",
|
||||
EXPORT_EXCALIDRAW: "导出为 .Excalidraw 文件",
|
||||
LINK_BUTTON_CLICK_NO_TEXT:
|
||||
"请选择一个含有链接的图形或文本元素。\n" +
|
||||
"按住 SHIFT 并点击此按钮可在新面板中打开链接。\n" +
|
||||
"您也可以直接在画布中按住 CTRL/CMD 并点击图形或文本元素来打开链接。",
|
||||
"请选择一个含有链接的图形或文本元素。",
|
||||
FILENAME_INVALID_CHARS:
|
||||
'文件名不能含有以下符号: * " \\ < > : | ? #',
|
||||
FILE_DOES_NOT_EXIST:
|
||||
"文件不存在。按住 ALT(或 ALT + SHIFT)并点击链接来创建新文件。",
|
||||
FORCE_SAVE:
|
||||
"立刻保存该绘图(并更新嵌入了该绘图的面板)。\n详见插件设置中的定期保存选项",
|
||||
"保存绘图(并更新嵌入了该绘图的面板)",
|
||||
RAW: "文本元素正以原文(RAW)模式显示链接。\n点击切换到预览(PREVIEW)模式",
|
||||
PARSED:
|
||||
"文本元素正以预览(PREVIEW)模式显示链接。\n点击切换到原文(RAW)模式",
|
||||
NOFILE: "Excalidraw(没有文件)",
|
||||
COMPATIBILITY_MODE:
|
||||
"*.excalidraw 文件以兼容模式打开。转换为新格式以获得完整的插件功能。",
|
||||
"*.excalidraw 文件正以兼容模式打开。需要转换为新格式才能使用插件的全部功能。",
|
||||
CONVERT_FILE: "转换为新格式",
|
||||
|
||||
//settings.ts
|
||||
@@ -112,12 +120,13 @@ export default {
|
||||
"Template.md,则此项应设为 Excalidraw/Template.md(也可省略 .md 扩展名,即 Excalidraw/Template)。<br>" +
|
||||
"如果您在兼容模式下使用 Excalidraw,那么您的模板文件也必须是旧的 *.excalidraw 格式," +
|
||||
"例如 Excalidraw/Template.excalidraw。",
|
||||
SCRIPT_FOLDER_NAME: "Excalidraw 自动化脚本的文件夹",
|
||||
SCRIPT_FOLDER_NAME: "Excalidraw 自动化脚本的文件夹(大小写敏感!)",
|
||||
SCRIPT_FOLDER_DESC:
|
||||
"此文件夹用于存放 Excalidraw 自动化脚本。" +
|
||||
"您可以在 Obsidian 命令面板中执行这些脚本," +
|
||||
"还可以为喜欢的脚本分配快捷键,就像为其他 Obsidian 命令分配快捷键一样。<br>" +
|
||||
"该项不能设为库的根目录。",
|
||||
SAVING_HEAD: "保存",
|
||||
COMPRESS_NAME: "压缩 Excalidraw JSON",
|
||||
COMPRESS_DESC:
|
||||
"Excalidraw 绘图文件默认将元素记录为 JSON 格式。开启此项,可将元素的 JSON 数据以 BASE64 编码" +
|
||||
@@ -129,15 +138,18 @@ export default {
|
||||
"而当您切换回 Excalidraw 模式时,数据就会被再次编码。<br>" +
|
||||
"开启此项后,对于之前已存在的未压缩的绘图文件," +
|
||||
"需要重新打开并保存它们才能生效。",
|
||||
AUTOSAVE_NAME: "定期保存",
|
||||
AUTOSAVE_DESC:
|
||||
"定期保存当前绘图。此功能专为移动设备设计 —— " +
|
||||
"在桌面端,当您关闭 Excalidraw 或 Obsidian,或者移动焦点到其他面板的时候,软件是会自动保存的;" +
|
||||
"但是在手机或平板上通过滑动手势退出 Obsidian 时,可能无法顺利触发自动保存。因此我添加了定期保存功能作为弥补。",
|
||||
AUTOSAVE_INTERVAL_NAME: "定期保存的时间间隔",
|
||||
AUTOSAVE_INTERVAL_DESC:
|
||||
"每隔多长时间执行一次保存。如果当前绘图没有发生改变,将不会触发保存。",
|
||||
FILENAME_HEAD: "文件名",
|
||||
AUTOSAVE_INTERVAL_DESKTOP_NAME: "桌面端定期保存时间间隔",
|
||||
AUTOSAVE_INTERVAL_DESKTOP_DESC:
|
||||
"每隔多长时间触发一次自动保存。但如果当前绘图没有发生改变,将不会触发自动保存。" +
|
||||
"当 Obsidian 应用内的焦点离开活动文档(如关闭工作空间、点击菜单栏、切换到其他页签或面板等)的时候,会触发自动保存。" +
|
||||
"直接退出 Obsidian 应用(不管是终结进程还是点关闭按钮)不会触发自动保存。",
|
||||
AUTOSAVE_INTERVAL_MOBILE_NAME: "移动端定期保存时间间隔",
|
||||
AUTOSAVE_INTERVAL_MOBILE_DESC:
|
||||
"建议在移动端设置更短的自动保存时间间隔。" +
|
||||
"当 Obsidian 应用内的焦点离开活动文档(如关闭工作空间、点击菜单栏、切换到其他页签或面板等)的时候,会触发自动保存。" +
|
||||
"直接退出 Obsidian 应用(在应用切换器中划掉)不会触发自动保存。此外,当您切换到其他应用时,有时候" +
|
||||
"系统会自动清理 Obsidian 后台以释放资源。这种情况下,Excalidraw 无法保存最新的变动。",
|
||||
FILENAME_HEAD: "文件名",
|
||||
FILENAME_DESC:
|
||||
"<p>点击阅读" +
|
||||
"<a href='https://momentjs.com/docs/#/displaying/format/'>日期和时间格式参考</a>。</p>",
|
||||
@@ -187,15 +199,28 @@ export default {
|
||||
DEFAULT_PEN_MODE_NAME: "触控笔模式(Pen mode)",
|
||||
DEFAULT_PEN_MODE_DESC:
|
||||
"打开绘图时,是否自动开启触控笔模式?",
|
||||
ZOOM_TO_FIT_NAME: "自动缩放以适应面板调整",
|
||||
ZOOM_TO_FIT_DESC: "调整面板大小时,自适应地缩放画布" +
|
||||
|
||||
DEFAULT_PINCHZOOM_NAME: "允许在触控笔模式下进行双指缩放",
|
||||
DEFAULT_PINCHZOOM_DESC:
|
||||
"在触控笔模式下使用自由画笔工具时,双指缩放可能造成干扰。<br>" +
|
||||
"<b>开启: </b>允许在触控笔模式下进行双指缩放<br><b>关闭: </b>禁止在触控笔模式下进行双指缩放",
|
||||
|
||||
DEFAULT_WHEELZOOM_NAME: "鼠标滚轮缩放页面",
|
||||
DEFAULT_WHEELZOOM_DESC:
|
||||
`<b>开启:</b>鼠标滚轮为缩放页面,${labelCTRL()}+鼠标滚轮为滚动页面</br><b>关闭:</b>鼠标滚轮为滚动页面,${labelCTRL()}+鼠标滚轮为缩放页面`,
|
||||
|
||||
ZOOM_TO_FIT_NAME: "调节面板尺寸后自动缩放页面",
|
||||
ZOOM_TO_FIT_DESC: "调节面板尺寸后,自适应地缩放页面" +
|
||||
"<br><b>开启:</b>自动缩放。<br><b>关闭:</b>禁用自动缩放。",
|
||||
ZOOM_TO_FIT_ONOPEN_NAME: "打开绘图时自动缩放页面",
|
||||
ZOOM_TO_FIT_ONOPEN_DESC: "打开绘图文件时,自适应地缩放页面" +
|
||||
"<br><b>开启:</b>自动缩放。<br><b>关闭:</b>禁用自动缩放。",
|
||||
ZOOM_TO_FIT_MAX_LEVEL_NAME: "自动缩放的最高级别",
|
||||
ZOOM_TO_FIT_MAX_LEVEL_DESC:
|
||||
"自动缩放画布时,允许放大的最高级别。该值不能低于 0.5(50%)且不能超过 10(1000%)。",
|
||||
LINKS_HEAD: "链接(Links) & 以文本形式嵌入到绘图中的文档(Transclusion)",
|
||||
LINKS_DESC:
|
||||
"按住 CTRL/CMD 并点击包含 <code>[[链接]]</code> 的文本元素可以打开其中的链接。<br>" +
|
||||
`按住 ${labelCTRL()} 并点击包含 <code>[[链接]]</code> 的文本元素可以打开其中的链接。` +
|
||||
"如果所选文本元素包含多个 <code>[[有效的内部链接]]</code> ,只会打开第一个链接;" +
|
||||
"如果所选文本元素包含有效的 URL 链接 (如 <code>https://</code> 或 <code>http://</code>)," +
|
||||
"插件会在浏览器中打开链接。<br>" +
|
||||
@@ -203,45 +228,45 @@ export default {
|
||||
"若您不愿绘图中的链接外观因此而变化,可使用 <code>[[内部链接|别名]]</code>。",
|
||||
ADJACENT_PANE_NAME: "在相邻面板中打开",
|
||||
ADJACENT_PANE_DESC:
|
||||
"按住 CTRL/CMD + SHIFT 并点击绘图里的内部链接时,插件默认会在新面板中打开该链接。<br>" +
|
||||
`按住 ${labelCTRL()}+${labelSHIFT()} 并点击绘图里的内部链接时,插件默认会在新面板中打开该链接。<br>` +
|
||||
"若开启此项,Excalidraw 会先尝试寻找已有的相邻面板(按照右侧、左侧、上方、下方的顺序)," +
|
||||
"并在其中打开该链接。如果找不到," +
|
||||
"再在新面板中打开。",
|
||||
MAINWORKSPACE_PANE_NAME: "在主工作区中打开",
|
||||
MAINWORKSPACE_PANE_DESC:
|
||||
"按住 CTRL/CMD + SHIFT 并点击绘图里的内部链接时,插件默认会在当前窗口的新面板中打开该链接。<br>" +
|
||||
`按住 ${labelCTRL()}+${labelSHIFT()} 并点击绘图里的内部链接时,插件默认会在当前窗口的新面板中打开该链接。<br>` +
|
||||
"若开启此项,Excalidraw 会在主工作区的面板中打开该链接。",
|
||||
LINK_BRACKETS_NAME: "在链接的两侧显示 [[中括号]]",
|
||||
LINK_BRACKETS_NAME: "在链接的两侧显示 <code>[[中括号]]</code>",
|
||||
LINK_BRACKETS_DESC: `${
|
||||
"文本元素处于预览模式时,在内部链接的两侧显示中括号。<br>" +
|
||||
"文本元素处于预览(PREVIEW)模式时,在内部链接的两侧显示中括号。<br>" +
|
||||
"您可为某个绘图单独设置此项,方法是在其 frontmatter 中添加形如 <code>"
|
||||
}${FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS}: true/false</code> 的键值对。`,
|
||||
LINK_PREFIX_NAME: "内部链接的前缀",
|
||||
LINK_PREFIX_DESC: `${
|
||||
"文本元素处于预览模式时,如果其中包含链接,则添加此前缀。<br>" +
|
||||
"文本元素处于预览(PREVIEW)模式时,如果其中包含链接,则添加此前缀。<br>" +
|
||||
"您可为某个绘图单独设置此项,方法是在其 frontmatter 中添加形如 <code>"
|
||||
}${FRONTMATTER_KEY_CUSTOM_PREFIX}: "📍 "</code> 的键值对。`,
|
||||
URL_PREFIX_NAME: "外部链接的前缀",
|
||||
URL_PREFIX_DESC: `${
|
||||
"文本元素处于预览模式时,如果其中包含外部链接,则添加此前缀。<br>" +
|
||||
"文本元素处于预览(PREVIEW)模式时,如果其中包含外部链接,则添加此前缀。<br>" +
|
||||
"您可为某个绘图单独设置此项,方法是在其 frontmatter 中添加形如 <code>"
|
||||
}${FRONTMATTER_KEY_CUSTOM_URL_PREFIX}: "🌐 "</code> 的键值对。`,
|
||||
PARSE_TODO_NAME: "解析任务列表(Todo)",
|
||||
PARSE_TODO_NAME: "待办任务(Todo)",
|
||||
PARSE_TODO_DESC: "将文本元素中的 <code>- [ ]</code> 和 <code>- [x]</code> 前缀显示为方框。",
|
||||
TODO_NAME: "未完成的 Todo 项目",
|
||||
TODO_DESC: "未完成的 Todo 项目的符号",
|
||||
DONE_NAME: "已完成的 Todo 项目",
|
||||
DONE_DESC: "已完成的 Todo 项目的符号",
|
||||
TODO_NAME: "未完成项目",
|
||||
TODO_DESC: "未完成的待办项目的符号",
|
||||
DONE_NAME: "已完成项目",
|
||||
DONE_DESC: "已完成的待办项目的符号",
|
||||
HOVERPREVIEW_NAME: "鼠标悬停预览内部链接",
|
||||
HOVERPREVIEW_DESC:
|
||||
"<b>开启:</b>在 Excalidraw <u>阅读模式(View)</u>下,鼠标悬停在 <code>[[内部链接]]</code> 上即可预览;" +
|
||||
`<b>开启:</b>在 Excalidraw <u>阅读模式(View)</u>下,鼠标悬停在 <code>[[内部链接]]</code> 上即可预览;` +
|
||||
"而在<u>普通模式(Normal)</u>下, 鼠标悬停在内部链接右上角的蓝色标识上即可预览。<br> " +
|
||||
"<b>关闭:</b>鼠标悬停在 <code>[[内部链接]]</code> 上,并且按住 CTRL/CMD 时进行预览。",
|
||||
`<b>关闭:</b>鼠标悬停在 <code>[[内部链接]]</code> 上,并且按住 ${labelCTRL()} 才能预览。`,
|
||||
LINKOPACITY_NAME: "链接标识的透明度",
|
||||
LINKOPACITY_DESC:
|
||||
"含有链接的元素,其右上角的链接标识的透明度。介于 0(全透明)到 1(不透明)之间。",
|
||||
LINK_CTRL_CLICK_NAME:
|
||||
"按住 CTRL/CMD 并点击含有 [[链接]] 或 [别名](链接) 的文本来打开链接",
|
||||
`按住 ${labelCTRL()} 并点击含有 [[链接]] 或 [别名](链接) 的文本来打开链接`,
|
||||
LINK_CTRL_CLICK_DESC:
|
||||
"如果此功能影响到您使用某些原版 Excalidraw 功能,可将其关闭。" +
|
||||
"关闭后,您只能通过绘图面板标题栏中的链接按钮来打开链接。",
|
||||
@@ -259,13 +284,16 @@ export default {
|
||||
PAGE_TRANSCLUSION_CHARCOUNT_DESC:
|
||||
"以 <code>![[内部链接]]</code> 或 <code></code> 的形式将文档以文本形式嵌入到绘图中时," +
|
||||
"该文档在绘图中可显示的最大字符数量。",
|
||||
QUOTE_TRANSCLUSION_REMOVE_NAME: "隐藏 Transclusion 行首的引用符号",
|
||||
QUOTE_TRANSCLUSION_REMOVE_DESC: "不显示 Transclusion 中每一行行首的 > 符号,以提高纯文本 Transclusion 的可读性。<br>" +
|
||||
"<b>开启:</b>隐藏 > 符号<br><b>关闭:</b>不隐藏 > 符号(注意,由于 Obsidian API 的原因,首行行首的 > 符号不会被隐藏)",
|
||||
GET_URL_TITLE_NAME: "使用 iframly 获取页面标题",
|
||||
GET_URL_TITLE_DESC:
|
||||
"拖放链接到 Excalidraw 时,使用 <code>http://iframely.server.crestify.com/iframely?url=</code> 来获取页面的标题。",
|
||||
MD_HEAD: "以图像形式嵌入到绘图中的 Markdown 文档(MD-Embed)",
|
||||
MD_HEAD_DESC:
|
||||
"您还可以将 Markdown 文档以图像形式(而非文本形式)嵌入到绘图中。" +
|
||||
"方法是按住 CTRL/CMD 并从文件管理器中把文档拖入绘图,或者执行“以图像形式嵌入”系列命令。",
|
||||
`方法是按住 ${labelCTRL()} 并从文件管理器中把文档拖入绘图,或者执行“以图像形式嵌入”系列命令。`,
|
||||
MD_TRANSCLUDE_WIDTH_NAME: "MD-Embed 的默认宽度",
|
||||
MD_TRANSCLUDE_WIDTH_DESC:
|
||||
"MD-Embed 的宽度。该选项会影响到折行,以及图像元素的宽度。<br>" +
|
||||
@@ -382,6 +410,10 @@ export default {
|
||||
MATHJAX_DESC: "如果您在绘图中使用 LaTeX,插件需要从服务器获取并加载一个 javascript 库。" +
|
||||
"如果您的网络无法访问某些库服务器,可以尝试通过此选项更换库服务器。"+
|
||||
"更改此选项后,您可能需要重启 Obsidian 来使其生效。",
|
||||
NONSTANDARD_HEAD: "非 Excalidraw.com 官方支持的特性",
|
||||
NONSTANDARD_DESC: "这些特性不受 Excalidraw.com 官方支持。当导出绘图到 Excalidraw.com 时,这些特性将会发生变化。",
|
||||
CUSTOM_PEN_NAME: "自定义画笔的数量",
|
||||
CUSTOM_PEN_DESC: "在画布上的 Obsidian 菜单旁边切换自定义画笔。长按画笔按钮可以修改其样式。",
|
||||
EXPERIMENTAL_HEAD: "实验性功能",
|
||||
EXPERIMENTAL_DESC:
|
||||
"以下部分设置不会立即生效,需要刷新文件资源管理器或者重启 Obsidian 才会生效。",
|
||||
@@ -411,9 +443,22 @@ export default {
|
||||
"选择库文件夹中的一个 .ttf, .woff 或 .woff2 字体文件作为本地字体文件。" +
|
||||
"若未选择文件,则使用默认的 Virgil 字体。",
|
||||
SCRIPT_SETTINGS_HEAD: "已安装脚本的设置",
|
||||
TASKBONE_HEAD: "Taskbone OCR(光学符号识别)",
|
||||
TASKBONE_DESC: "这是一个将 OCR 融入 Excalidraw 的实验性功能。请注意,Taskbone 是一项独立的外部服务,而不是由 Excalidraw 或 Obsidian-excalidraw-plugin 项目提供的。" +
|
||||
"OCR 能够对画布上用自由画笔工具写下的涂鸦或者嵌入的图像进行文本识别,并将识别出来的文本写入绘图文件的 frontmatter,同时复制到剪贴板。" +
|
||||
"之所以要写入 frontmatter 是为了便于您在 Obsidian 中能够搜索到这些文本。" +
|
||||
"注意,识别的过程不是在本地进行的,而是通过在线 API,图像会被上传到 taskbone 的服务器(仅用于识别目的)。如果您对此敏感,请不要使用这个功能。",
|
||||
TASKBONE_ENABLE_NAME: "启用 Taskbone",
|
||||
TASKBONE_ENABLE_DESC: "启用这个功能意味着你同意 Taskbone <a href='https://www.taskbone.com/legal/terms/' target='_blank'>条款及细则</a> 以及 " +
|
||||
"<a href='https://www.taskbone.com/legal/privacy/' target='_blank'>隐私政策</a>.",
|
||||
TASKBONE_APIKEY_NAME: "Taskbone API Key",
|
||||
TASKBONE_APIKEY_DESC: "Taskbone 的免费 API key 提供了一定数量的每月识别次数。如果您非常频繁地使用此功能,或者想要支持 " +
|
||||
"Taskbone 的开发者(您懂的,没有人能用爱发电,Taskbone 开发者也需要投入资金才能持续运行这项 OCR 服务)您可以" +
|
||||
"到 <a href='https://www.taskbone.com/' target='_blank'>taskbone.com</a> 购买一个商用 API key。购买后请将它填写到旁边这个文本框里,替换掉原本自动生成的免费 API key。",
|
||||
|
||||
//openDrawings.ts
|
||||
SELECT_FILE: "选择一个文件后按回车。",
|
||||
SELECT_FILE_WITH_OPTION_TO_SCALE: `选择一个文件后按回车,或者 ${labelSHIFT()}+${labelMETA()}+ENTER 以 100% 尺寸插入。`,
|
||||
NO_MATCH: "查询不到匹配的文件。",
|
||||
SELECT_FILE_TO_LINK: "选择要插入(链接)到当前绘图中的文件。",
|
||||
SELECT_DRAWING: "选择要插入(以图像形式嵌入)到当前绘图中的图像。",
|
||||
@@ -438,4 +483,6 @@ export default {
|
||||
GOTO_FULLSCREEN: "进入全屏模式",
|
||||
EXIT_FULLSCREEN: "退出全屏模式",
|
||||
TOGGLE_FULLSCREEN: "切换全屏模式",
|
||||
OPEN_LINK_CLICK: "打开所选的图形或文本元素里的链接",
|
||||
OPEN_LINK_PROPS: "编辑所选 MD-Embed 的内部链接,或者打开所选的图形或文本元素里的链接"
|
||||
};
|
||||
|
||||
206
src/main.ts
@@ -16,7 +16,8 @@ import {
|
||||
request,
|
||||
MetadataCache,
|
||||
FrontMatterCache,
|
||||
Command
|
||||
Command,
|
||||
requireApiVersion
|
||||
} from "obsidian";
|
||||
import {
|
||||
BLANK_DRAWING,
|
||||
@@ -25,8 +26,6 @@ import {
|
||||
ICON_NAME,
|
||||
SCRIPTENGINE_ICON,
|
||||
SCRIPTENGINE_ICON_NAME,
|
||||
DISK_ICON,
|
||||
DISK_ICON_NAME,
|
||||
PNG_ICON,
|
||||
PNG_ICON_NAME,
|
||||
SVG_ICON,
|
||||
@@ -37,12 +36,12 @@ import {
|
||||
JSON_parse,
|
||||
nanoid,
|
||||
DARK_BLANK_DRAWING,
|
||||
CTRL_OR_CMD,
|
||||
SCRIPT_INSTALL_CODEBLOCK,
|
||||
SCRIPT_INSTALL_FOLDER,
|
||||
VIRGIL_FONT,
|
||||
VIRGIL_DATAURL,
|
||||
EXPORT_TYPES,
|
||||
DEVICE,
|
||||
} from "./Constants";
|
||||
import ExcalidrawView, { TextMode, getTextMode } from "./ExcalidrawView";
|
||||
import {
|
||||
@@ -98,6 +97,7 @@ import {
|
||||
markdownPostProcessor,
|
||||
observer,
|
||||
} from "./MarkdownPostProcessor";
|
||||
|
||||
import { FieldSuggester } from "./dialogs/FieldSuggester";
|
||||
import { ReleaseNotes } from "./dialogs/ReleaseNotes";
|
||||
import { decompressFromBase64 } from "lz-string";
|
||||
@@ -105,6 +105,9 @@ import { Packages } from "./types";
|
||||
import * as React from "react";
|
||||
import { ScriptInstallPrompt } from "./dialogs/ScriptInstallPrompt";
|
||||
import { check } from "prettier";
|
||||
import Taskbone from "./ocr/Taskbone";
|
||||
import { hoverEvent_Legacy, initializeMarkdownPostProcessor_Legacy, markdownPostProcessor_Legacy, observer_Legacy } from "./MarkdownPostProcessor_Legacy";
|
||||
import { emulateCTRLClickForLinks, isCTRL, linkClickModifierType, PaneTarget } from "./utils/ModifierkeyHelper";
|
||||
|
||||
|
||||
declare module "obsidian" {
|
||||
@@ -133,6 +136,7 @@ declare const excalidrawLib: any;
|
||||
declare const PLUGIN_VERSION:string;
|
||||
|
||||
export default class ExcalidrawPlugin extends Plugin {
|
||||
public taskbone: Taskbone;
|
||||
private excalidrawFiles: Set<TFile> = new Set<TFile>();
|
||||
public excalidrawFileModes: { [file: string]: string } = {};
|
||||
private _loaded: boolean = false;
|
||||
@@ -157,7 +161,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; blockrefData: string }> =
|
||||
public filesMaster: Map<FileId, { isHyperlink: boolean; path: string; hasSVGwithBitmap: boolean; blockrefData: string }> =
|
||||
null; //fileId, path
|
||||
public equationsMaster: Map<FileId, string> = null; //fileId, formula
|
||||
public mathjax: any = null;
|
||||
@@ -168,23 +172,12 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
private packageMap: WeakMap<Window,Packages> = new WeakMap<Window,Packages>();
|
||||
public leafChangeTimeout: NodeJS.Timeout = null;
|
||||
private forceSaveCommand:Command;
|
||||
public device: {
|
||||
isDesktop: boolean,
|
||||
isPhone: boolean,
|
||||
isTablet: boolean,
|
||||
isMobile: boolean,
|
||||
isLinux: boolean,
|
||||
isMacOS: boolean,
|
||||
isWindows: boolean,
|
||||
isIOS: boolean,
|
||||
isAndroid: boolean
|
||||
};
|
||||
|
||||
constructor(app: App, manifest: PluginManifest) {
|
||||
super(app, manifest);
|
||||
this.filesMaster = new Map<
|
||||
FileId,
|
||||
{ path: string; hasSVGwithBitmap: boolean; blockrefData: string }
|
||||
{ isHyperlink: boolean; path: string; hasSVGwithBitmap: boolean; blockrefData: string }
|
||||
>();
|
||||
this.equationsMaster = new Map<FileId, string>();
|
||||
}
|
||||
@@ -208,21 +201,8 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
|
||||
async onload() {
|
||||
this.device = {
|
||||
isDesktop: !document.body.hasClass("is-tablet") && !document.body.hasClass("is-mobile"),
|
||||
isPhone: document.body.hasClass("is-phone"),
|
||||
isTablet: document.body.hasClass("is-tablet"),
|
||||
isMobile: document.body.hasClass("is-mobile"), //running Obsidian Mobile, need to also check isTablet
|
||||
isLinux: document.body.hasClass("mod-linux") && ! document.body.hasClass("is-android"),
|
||||
isMacOS: document.body.hasClass("mod-macos") && ! document.body.hasClass("is-ios"),
|
||||
isWindows: document.body.hasClass("mod-windows"),
|
||||
isIOS: document.body.hasClass("is-ios"),
|
||||
isAndroid: document.body.hasClass("is-android")
|
||||
}
|
||||
|
||||
addIcon(ICON_NAME, EXCALIDRAW_ICON);
|
||||
addIcon(SCRIPTENGINE_ICON_NAME, SCRIPTENGINE_ICON);
|
||||
addIcon(DISK_ICON_NAME, DISK_ICON);
|
||||
addIcon(PNG_ICON_NAME, PNG_ICON);
|
||||
addIcon(SVG_ICON_NAME, SVG_ICON);
|
||||
|
||||
@@ -239,7 +219,11 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
//Compatibility mode with .excalidraw files
|
||||
this.registerExtensions(["excalidraw"], VIEW_TYPE_EXCALIDRAW);
|
||||
|
||||
this.addMarkdownPostProcessor();
|
||||
if(requireApiVersion("1.1.6")) {
|
||||
this.addMarkdownPostProcessor();
|
||||
} else {
|
||||
this.addLegacyMarkdownPostProcessor();
|
||||
}
|
||||
this.registerInstallCodeblockProcessor();
|
||||
this.addThemeObserver();
|
||||
this.experimentalFileTypeDisplayToggle(this.settings.experimentalFileType);
|
||||
@@ -255,8 +239,8 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
// 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 obsidianJustInstalled = this.settings.previousRelease === "0.0.0"
|
||||
|
||||
if (isVersionNewerThanOther(PLUGIN_VERSION, this.settings.previousRelease)) {
|
||||
new ReleaseNotes(
|
||||
this.app,
|
||||
@@ -274,6 +258,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
this.scriptEngine = new ScriptEngine(self);
|
||||
});
|
||||
this.taskbone = new Taskbone(this);
|
||||
}
|
||||
|
||||
public initializeFourthFont() {
|
||||
@@ -395,23 +380,12 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (view) {
|
||||
(async()=>{
|
||||
if (view.semaphores.autosaving) {
|
||||
return;
|
||||
}
|
||||
view.semaphores.forceSaving = true;
|
||||
await view.save(false, true);
|
||||
view.plugin.triggerEmbedUpdates();
|
||||
view.loadSceneFiles();
|
||||
view.semaphores.forceSaving = false;
|
||||
new Notice("Save successful", 1000);
|
||||
})();
|
||||
view.forceSave();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private registerInstallCodeblockProcessor() {
|
||||
const codeblockProcessor = async (source: string, el: HTMLElement) => {
|
||||
//Button next to the "List of available scripts" at the top
|
||||
@@ -428,7 +402,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
link.style.paddingRight = "10px";
|
||||
button2 = link.parentElement.createEl("button", null, (b) => {
|
||||
b.setText(t("UPDATE_SCRIPT"));
|
||||
b.addClass("mod-cta");
|
||||
b.addClass("mod-muted");
|
||||
b.style.backgroundColor = "var(--interactive-success)";
|
||||
b.style.display = "none";
|
||||
});
|
||||
@@ -474,7 +448,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
break;
|
||||
}
|
||||
};
|
||||
button.addClass("mod-cta");
|
||||
button.addClass("mod-muted");
|
||||
let decodedURI = source;
|
||||
try {
|
||||
decodedURI = decodeURI(source);
|
||||
@@ -488,9 +462,10 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
const fname = decodedURI.substring(decodedURI.lastIndexOf("/") + 1);
|
||||
const folder = `${this.settings.scriptFolderPath}/${SCRIPT_INSTALL_FOLDER}`;
|
||||
const scriptPath = `${folder}/${fname}`;
|
||||
const downloaded = app.vault.getFiles().filter(f=>f.path.startsWith(folder) && f.name === fname).sort((a,b)=>a.path>b.path?1:-1);
|
||||
let scriptFile = downloaded[0];
|
||||
const scriptPath = scriptFile?.path ?? `${folder}/${fname}`;
|
||||
const svgPath = getIMGFilename(scriptPath, "svg");
|
||||
let scriptFile = this.app.vault.getAbstractFileByPath(scriptPath);
|
||||
let svgFile = this.app.vault.getAbstractFileByPath(svgPath);
|
||||
setButtonText(scriptFile ? "CHECKING" : "INSTALL");
|
||||
button.onclick = async () => {
|
||||
@@ -619,6 +594,18 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
this.observer.observe(document, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
private addLegacyMarkdownPostProcessor() {
|
||||
initializeMarkdownPostProcessor_Legacy(this);
|
||||
this.registerMarkdownPostProcessor(markdownPostProcessor_Legacy);
|
||||
|
||||
// internal-link quick preview
|
||||
this.registerEvent(this.app.workspace.on("hover-link", hoverEvent_Legacy));
|
||||
|
||||
//monitoring for div.popover.hover-popover.file-embed.is-loaded to be added to the DOM tree
|
||||
this.observer = observer_Legacy;
|
||||
this.observer.observe(document, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
private addThemeObserver() {
|
||||
this.themeObserver = new MutationObserver(async (m: MutationRecord[]) => {
|
||||
if (!this.settings.matchThemeTrigger) {
|
||||
@@ -720,8 +707,8 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
this.addRibbonIcon(ICON_NAME, t("CREATE_NEW"), async (e) => {
|
||||
this.createAndOpenDrawing(
|
||||
getDrawingFilename(this.settings),
|
||||
e[CTRL_OR_CMD]?"new-pane":"active-pane",
|
||||
); //.ctrlKey||e.metaKey);
|
||||
linkClickModifierType(emulateCTRLClickForLinks(e)),
|
||||
);
|
||||
});
|
||||
|
||||
const fileMenuHandlerCreateNew = (menu: Menu, file: TFile) => {
|
||||
@@ -729,7 +716,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
item
|
||||
.setTitle(t("CREATE_NEW"))
|
||||
.setIcon(ICON_NAME)
|
||||
.onClick(() => {
|
||||
.onClick((e) => {
|
||||
let folderpath = file.path;
|
||||
if (file instanceof TFile) {
|
||||
folderpath = normalizePath(
|
||||
@@ -738,7 +725,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
this.createAndOpenDrawing(
|
||||
getDrawingFilename(this.settings),
|
||||
"active-pane",
|
||||
linkClickModifierType(emulateCTRLClickForLinks(e)),
|
||||
folderpath,
|
||||
);
|
||||
});
|
||||
@@ -868,6 +855,14 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "excalidraw-autocreate-newtab",
|
||||
name: t("NEW_IN_NEW_TAB"),
|
||||
callback: () => {
|
||||
this.createAndOpenDrawing(getDrawingFilename(this.settings), "new-tab");
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "excalidraw-autocreate-on-current",
|
||||
name: t("NEW_IN_ACTIVE_PANE"),
|
||||
@@ -888,7 +883,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
});
|
||||
|
||||
const insertDrawingToDoc = async (
|
||||
location: "active-pane"|"new-pane"|"popout-window"
|
||||
location: PaneTarget
|
||||
) => {
|
||||
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (!activeView) {
|
||||
@@ -924,6 +919,18 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "excalidraw-autocreate-and-embed-new-tab",
|
||||
name: t("NEW_IN_NEW_TAB_EMBED"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
return Boolean(this.app.workspace.getActiveViewOfType(MarkdownView));
|
||||
}
|
||||
insertDrawingToDoc("new-tab");
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "excalidraw-autocreate-and-embed-on-current",
|
||||
name: t("NEW_IN_ACTIVE_PANE_EMBED"),
|
||||
@@ -966,6 +973,46 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "export-svg-scene",
|
||||
name: t("EXPORT_SVG_WITH_SCENE"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
return (
|
||||
Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView))
|
||||
);
|
||||
}
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (view) {
|
||||
view.saveSVG(undefined,true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "run-ocr",
|
||||
name: t("RUN_OCR"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (checking) {
|
||||
return (
|
||||
Boolean(view)
|
||||
);
|
||||
}
|
||||
if (view) {
|
||||
if(!this.settings.taskboneEnabled) {
|
||||
new Notice("Taskbone OCR is not enabled. Please go to plugins settings to enable it.",4000);
|
||||
return true;
|
||||
}
|
||||
this.taskbone.getTextForView(view, false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "search-text",
|
||||
name: t("SEARCH"),
|
||||
@@ -1024,6 +1071,24 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "export-png-scene",
|
||||
name: t("EXPORT_PNG_WITH_SCENE"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
return (
|
||||
Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView))
|
||||
);
|
||||
}
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (view) {
|
||||
view.savePNG(undefined, true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
this.forceSaveCommand = this.addCommand({
|
||||
id: "save",
|
||||
hotkeys: [{modifiers: ["Ctrl"], key:"s"}], //See also Poposcope
|
||||
@@ -1734,7 +1799,7 @@ 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
|
||||
if(previouslyActiveEV.semaphores.dirty) {
|
||||
if(previouslyActiveEV.semaphores.dirty && !previouslyActiveEV.semaphores.viewunload) {
|
||||
await previouslyActiveEV.save(true); //this will update transclusions in the drawing
|
||||
}
|
||||
}
|
||||
@@ -2053,7 +2118,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
if(opts.applyLefthandedMode) setLeftHandedMode(this.settings.isLeftHanded);
|
||||
if(opts.reEnableAutosave) this.settings.autosave = true;
|
||||
this.settings.autosaveInterval = app.isMobile?10000:15000; //more frequent on mobile because Obsidian may be killed on context switching
|
||||
this.settings.autosaveInterval = app.isMobile
|
||||
? this.settings.autosaveIntervalMobile
|
||||
: this.settings.autosaveIntervalDesktop;
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
@@ -2086,8 +2153,8 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
e.initEvent(RERENDER_EVENT, true, false);
|
||||
ownerDocument
|
||||
.querySelectorAll(
|
||||
`div[class^='excalidraw-svg']${
|
||||
filepath ? `[src='${filepath.replaceAll("'", "\\'")}']` : ""
|
||||
`img[class^='excalidraw-svg']${
|
||||
filepath ? `[fileSource='${filepath.replaceAll("'", "\\'")}']` : ""
|
||||
}`,
|
||||
)
|
||||
.forEach((el) => el.dispatchEvent(e));
|
||||
@@ -2096,16 +2163,21 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
public openDrawing(
|
||||
drawingFile: TFile,
|
||||
location: "active-pane"|"new-pane"|"popout-window",
|
||||
location: PaneTarget,
|
||||
active: boolean = false,
|
||||
subpath?: string
|
||||
) {
|
||||
if(location === "md-properties") {
|
||||
location = "new-tab";
|
||||
}
|
||||
let leaf: WorkspaceLeaf;
|
||||
if(location === "popout-window") {
|
||||
//@ts-ignore
|
||||
leaf = app.workspace.openPopoutLeaf();
|
||||
}
|
||||
else {
|
||||
if(location === "new-tab") {
|
||||
leaf = app.workspace.getLeaf('tab');
|
||||
}
|
||||
if(!leaf) {
|
||||
leaf = this.app.workspace.getLeaf(false);
|
||||
if ((leaf.view.getViewType() !== 'empty') && (location === "new-pane")) {
|
||||
leaf = getNewOrAdjacentLeaf(this, leaf)
|
||||
@@ -2117,12 +2189,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
!subpath || subpath === ""
|
||||
? {active}
|
||||
: { active, eState: { subpath } }
|
||||
);
|
||||
|
||||
/* leaf.setViewState({
|
||||
type: VIEW_TYPE_EXCALIDRAW,
|
||||
state: { file: drawingFile.path, eState: {subpath}},
|
||||
});*/
|
||||
)
|
||||
}
|
||||
|
||||
public async getBlankDrawing(): Promise<string> {
|
||||
@@ -2223,7 +2290,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
public async createAndOpenDrawing(
|
||||
filename: string,
|
||||
location: "active-pane"|"new-pane"|"popout-window",
|
||||
location: PaneTarget,
|
||||
foldername?: string,
|
||||
initData?: string,
|
||||
): Promise<string> {
|
||||
@@ -2259,7 +2326,8 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
|
||||
public isExcalidrawFile(f: TFile) {
|
||||
if (f.extension == "excalidraw") {
|
||||
if(!f) return false;
|
||||
if (f.extension === "excalidraw") {
|
||||
return true;
|
||||
}
|
||||
const fileCache = f ? this.app.metadataCache.getFileCache(f) : null;
|
||||
|
||||
@@ -3,7 +3,8 @@ import ExcalidrawView from "../ExcalidrawView";
|
||||
|
||||
type ButtonProps = {
|
||||
title: string;
|
||||
action: Function;
|
||||
action: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||
longpress?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||
icon: JSX.Element;
|
||||
view: ExcalidrawView;
|
||||
};
|
||||
@@ -14,6 +15,7 @@ type ButtonState = {
|
||||
|
||||
export class ActionButton extends React.Component<ButtonProps, ButtonState> {
|
||||
toastMessageTimeout: number = 0;
|
||||
longpressTimeout: number = 0;
|
||||
|
||||
constructor(props: ButtonProps) {
|
||||
super(props);
|
||||
@@ -26,9 +28,9 @@ export class ActionButton extends React.Component<ButtonProps, ButtonState> {
|
||||
return (
|
||||
<button
|
||||
style={{
|
||||
width: "fit-content",
|
||||
padding: "2px",
|
||||
margin: "4px",
|
||||
//width: "fit-content",
|
||||
//padding: "2px",
|
||||
//margin: "4px",
|
||||
}}
|
||||
className="ToolIcon_type_button ToolIcon_size_small ToolIcon_type_button--show ToolIcon"
|
||||
title={this.props.title}
|
||||
@@ -36,15 +38,32 @@ export class ActionButton extends React.Component<ButtonProps, ButtonState> {
|
||||
if (this.toastMessageTimeout) {
|
||||
window.clearTimeout(this.toastMessageTimeout);
|
||||
this.toastMessageTimeout = 0;
|
||||
this.props.action(event); //don't invoke the action on long press
|
||||
}
|
||||
if (this.longpressTimeout) {
|
||||
window.clearTimeout(this.longpressTimeout);
|
||||
this.longpressTimeout = 0;
|
||||
}
|
||||
this.props.action(event);
|
||||
}}
|
||||
onPointerDown={() => {
|
||||
onPointerDown={(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
this.toastMessageTimeout = window.setTimeout(
|
||||
() =>
|
||||
this.props.view.excalidrawAPI?.setToast({message:this.props.title}),
|
||||
300,
|
||||
() => {
|
||||
this.props.view.excalidrawAPI?.setToast({message:this.props.title, duration: 3000, closable: true});
|
||||
this.toastMessageTimeout = 0;
|
||||
},
|
||||
400,
|
||||
);
|
||||
this.longpressTimeout = window.setTimeout(
|
||||
() => {
|
||||
if(this.props.longpress) {
|
||||
this.props.longpress(event);
|
||||
} else {
|
||||
this.props.view.excalidrawAPI?.setToast({message:"Cannot pin this action", duration: 3000, closable: true});
|
||||
}
|
||||
this.longpressTimeout = 0;
|
||||
},
|
||||
1500
|
||||
)
|
||||
}}
|
||||
>
|
||||
<div className="ToolIcon__icon" aria-hidden="true">
|
||||
|
||||
21
src/menu/MenuLinks.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { AppState } from "@zsviczian/excalidraw/types/types";
|
||||
import clsx from "clsx";
|
||||
import * as React from "react";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
|
||||
|
||||
export class MenuLinks {
|
||||
plugin: ExcalidrawPlugin;
|
||||
ref: React.MutableRefObject<any>;
|
||||
|
||||
constructor(plugin: ExcalidrawPlugin, ref: React.MutableRefObject<any>) {
|
||||
this.plugin = plugin;
|
||||
this.ref = ref;
|
||||
}
|
||||
|
||||
render = (isMobile: boolean, appState: AppState) => {
|
||||
return (
|
||||
<div>Hello</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,125 +1,251 @@
|
||||
import { AppState } from "@zsviczian/excalidraw/types/types";
|
||||
import { AppState, ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/types";
|
||||
import clsx from "clsx";
|
||||
import { TFile } from "obsidian";
|
||||
import * as React from "react";
|
||||
import { VIEW_TYPE_EXCALIDRAW } from "src/Constants";
|
||||
import { PenSettingsModal } from "src/dialogs/PenSettingsModal";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import { PenStyle } from "src/PenTypes";
|
||||
import { PENS } from "src/utils/Pens";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { ICONS, penIcon, stringToSVG } from "./ActionIcons";
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
|
||||
export const setPen = (pen: PenStyle, api: any) => {
|
||||
const st = api.getAppState();
|
||||
api.updateScene({
|
||||
appState: {
|
||||
currentStrokeOptions: pen.penOptions,
|
||||
...(!pen.strokeWidth || (pen.strokeWidth === 0)) ? null : {currentItemStrokeWidth: pen.strokeWidth},
|
||||
...pen.backgroundColor ? {currentItemBackgroundColor: pen.backgroundColor} : null,
|
||||
...pen.strokeColor ? {currentItemStrokeColor: pen.strokeColor} : null,
|
||||
...pen.fillStyle === "" ? null : {currentItemFillStyle: pen.fillStyle},
|
||||
...pen.roughness ? null : {currentItemRoughness: pen.roughness},
|
||||
...pen.freedrawOnly && !st.resetCustomPen //switching from custom pen to next custom pen
|
||||
? {
|
||||
resetCustomPen: {
|
||||
currentItemStrokeWidth: st.currentItemStrokeWidth,
|
||||
currentItemBackgroundColor: st.currentItemBackgroundColor,
|
||||
currentItemStrokeColor: st.currentItemStrokeColor,
|
||||
currentItemFillStyle: st.currentItemFillStyle,
|
||||
currentItemRoughness: st.currentItemRoughness,
|
||||
}}
|
||||
: null,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const resetStrokeOptions = (resetCustomPen:any, api: ExcalidrawImperativeAPI, clearCurrentStrokeOptions: boolean) => {
|
||||
api.updateScene({
|
||||
appState: {
|
||||
...resetCustomPen ? {
|
||||
currentItemStrokeWidth: resetCustomPen.currentItemStrokeWidth,
|
||||
currentItemBackgroundColor: resetCustomPen.currentItemBackgroundColor,
|
||||
currentItemStrokeColor: resetCustomPen.currentItemStrokeColor,
|
||||
currentItemFillStyle: resetCustomPen.currentItemFillStyle,
|
||||
currentItemRoughness: resetCustomPen.currentItemRoughness,
|
||||
}: null,
|
||||
resetCustomPen: null,
|
||||
...clearCurrentStrokeOptions ? {currentStrokeOptions: null} : null,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export class ObsidianMenu {
|
||||
plugin: ExcalidrawPlugin;
|
||||
toolsRef: React.MutableRefObject<any>;
|
||||
private clickTimestamp:number[];
|
||||
private activePen: PenStyle;
|
||||
constructor(
|
||||
private plugin: ExcalidrawPlugin,
|
||||
private toolsRef: React.MutableRefObject<any>,
|
||||
private view: ExcalidrawView
|
||||
) {
|
||||
this.clickTimestamp = Array.from({length: Object.keys(PENS).length}, () => 0);
|
||||
}
|
||||
|
||||
constructor(plugin: ExcalidrawPlugin, toolsRef: React.MutableRefObject<any>) {
|
||||
this.plugin = plugin;
|
||||
this.toolsRef = toolsRef;
|
||||
renderCustomPens = (isMobile: boolean, appState: AppState) => {
|
||||
return(
|
||||
appState.customPens?.map((key,index)=>{
|
||||
const pen = this.plugin.settings.customPens[index]
|
||||
//Reset stroke setting when changing to a different tool
|
||||
if(
|
||||
appState.resetCustomPen &&
|
||||
appState.activeTool.type !== "freedraw" &&
|
||||
appState.currentStrokeOptions === pen.penOptions
|
||||
) {
|
||||
setTimeout(()=> resetStrokeOptions(appState.resetCustomPen, this.view.excalidrawAPI, false))
|
||||
}
|
||||
//if Pen settings are loaded, select custom pen when activating the freedraw element
|
||||
if (
|
||||
!appState.resetCustomPen &&
|
||||
appState.activeTool.type === "freedraw" &&
|
||||
appState.currentStrokeOptions === pen.penOptions &&
|
||||
pen.freedrawOnly
|
||||
) {
|
||||
setTimeout(()=>setPen(this.activePen,this.view.excalidrawAPI));
|
||||
}
|
||||
|
||||
if(
|
||||
this.activePen &&
|
||||
appState.resetCustomPen &&
|
||||
appState.activeTool.type === "freedraw" &&
|
||||
appState.currentStrokeOptions === pen.penOptions &&
|
||||
pen.freedrawOnly
|
||||
) {
|
||||
this.activePen.strokeWidth = appState.currentItemStrokeWidth;
|
||||
this.activePen.backgroundColor = appState.currentItemBackgroundColor;
|
||||
this.activePen.strokeColor = appState.currentItemStrokeColor;
|
||||
this.activePen.fillStyle = appState.currentItemFillStyle;
|
||||
this.activePen.roughness = appState.currentItemRoughness;
|
||||
}
|
||||
|
||||
return (
|
||||
<label
|
||||
key={index}
|
||||
className={clsx(
|
||||
"ToolIcon ToolIcon_type_floating",
|
||||
"ToolIcon_size_medium",
|
||||
{
|
||||
"is-mobile": isMobile,
|
||||
},
|
||||
)}
|
||||
onClick={() => {
|
||||
const now = Date.now();
|
||||
const dblClick = now-this.clickTimestamp[index] < 500;
|
||||
//open pen settings on double click
|
||||
if(dblClick) {
|
||||
const penSettings = new PenSettingsModal(this.plugin,this.view,index);
|
||||
(async () => {
|
||||
await this.plugin.loadSettings();
|
||||
penSettings.open();
|
||||
})();
|
||||
return;
|
||||
}
|
||||
this.clickTimestamp[index] = now;
|
||||
|
||||
const api = this.view.excalidrawAPI;
|
||||
const st = api.getAppState();
|
||||
|
||||
//single second click to reset freedraw to default
|
||||
if(st.currentStrokeOptions === pen.penOptions && st.activeTool.type === "freedraw") {
|
||||
resetStrokeOptions(st.resetCustomPen, api, true);
|
||||
return;
|
||||
}
|
||||
|
||||
//apply pen settings to canvas
|
||||
this.activePen = {...pen};
|
||||
setPen(pen,api);
|
||||
api.setActiveTool({type:"freedraw"});
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="ToolIcon__icon"
|
||||
aria-label={pen.type}
|
||||
style={{
|
||||
...appState.activeTool.type === "freedraw" && appState.currentStrokeOptions === pen.penOptions
|
||||
? {background: "var(--color-primary)"}
|
||||
: {}
|
||||
}}
|
||||
>
|
||||
{penIcon(pen)}
|
||||
</div>
|
||||
</label>
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
renderPinnedScriptButtons = (isMobile: boolean, appState: AppState) => {
|
||||
return (
|
||||
appState?.pinnedScripts?.map((key,index)=>{ //pinned scripts
|
||||
const scriptProp = this.plugin.scriptEngine.scriptIconMap[key];
|
||||
const name = scriptProp?.name ?? "";
|
||||
const icon = scriptProp?.svgString
|
||||
? stringToSVG(scriptProp.svgString)
|
||||
: ICONS.cog;
|
||||
let longpressTimout = 0;
|
||||
return (
|
||||
<label
|
||||
key = {index}
|
||||
className={clsx(
|
||||
"ToolIcon ToolIcon_type_floating",
|
||||
"ToolIcon_size_medium",
|
||||
{
|
||||
"is-mobile": isMobile,
|
||||
},
|
||||
)}
|
||||
onClick={() => {
|
||||
if(longpressTimout) {
|
||||
window.clearTimeout(longpressTimout);
|
||||
longpressTimout = 0;
|
||||
(async ()=>{
|
||||
const f = app.vault.getAbstractFileByPath(key);
|
||||
if (f && f instanceof TFile) {
|
||||
this.plugin.scriptEngine.executeScript(
|
||||
this.view,
|
||||
await app.vault.read(f),
|
||||
this.plugin.scriptEngine.getScriptName(f),
|
||||
f
|
||||
);
|
||||
}
|
||||
})()
|
||||
}
|
||||
}}
|
||||
onPointerDown={()=>{
|
||||
longpressTimout = window.setTimeout(
|
||||
() => {
|
||||
longpressTimout = 0;
|
||||
(async () =>{
|
||||
await this.plugin.loadSettings();
|
||||
const index = this.plugin.settings.pinnedScripts.indexOf(key)
|
||||
if(index > -1) {
|
||||
this.plugin.settings.pinnedScripts.splice(index,1);
|
||||
this.view.excalidrawAPI?.setToast({message:`Pin removed: ${name}`, duration: 3000, closable: true});
|
||||
}
|
||||
await this.plugin.saveSettings();
|
||||
app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> {
|
||||
if (v.view instanceof ExcalidrawView) v.view.updatePinnedScripts()
|
||||
})
|
||||
})()
|
||||
},
|
||||
1500
|
||||
)
|
||||
}}
|
||||
>
|
||||
<div className="ToolIcon__icon" aria-label={name}>
|
||||
{icon}
|
||||
</div>
|
||||
</label>
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
renderButton = (isMobile: boolean, appState: AppState) => {
|
||||
return (
|
||||
<label
|
||||
className={clsx(
|
||||
"ToolIcon ToolIcon_type_floating",
|
||||
"ToolIcon_size_medium",
|
||||
{
|
||||
"is-mobile": isMobile,
|
||||
},
|
||||
)}
|
||||
onClick={() => {
|
||||
this.toolsRef.current.setTheme(appState.theme);
|
||||
this.toolsRef.current.toggleVisibility(
|
||||
appState.zenModeEnabled || isMobile,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<div className="ToolIcon__icon" aria-hidden="true">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 166 267"
|
||||
>
|
||||
<path fill="transparent" d="M0 0h165.742v267.245H0z" />
|
||||
<g fillRule="evenodd">
|
||||
<path
|
||||
fill="#bd7efc"
|
||||
strokeWidth="0"
|
||||
d="M55.5 96.49 39.92 57.05 111.28 10l4.58 36.54L55.5 95.65"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#410380"
|
||||
strokeWidth=".5"
|
||||
d="M55.5 96.49c-5.79-14.66-11.59-29.33-15.58-39.44M55.5 96.49c-3.79-9.59-7.58-19.18-15.58-39.44m0 0C60.13 43.72 80.34 30.4 111.28 10M39.92 57.05C60.82 43.27 81.73 29.49 111.28 10m0 0c.97 7.72 1.94 15.45 4.58 36.54M111.28 10c1.14 9.12 2.29 18.24 4.58 36.54m0 0C95.41 63.18 74.96 79.82 55.5 95.65m60.36-49.11C102.78 57.18 89.71 67.82 55.5 95.65m0 0v.84m0-.84v.84"
|
||||
/>
|
||||
</g>
|
||||
<g fillRule="evenodd">
|
||||
<path
|
||||
fill="#e2c4ff"
|
||||
strokeWidth="0"
|
||||
d="m111.234 10.06 44.51 42.07-40.66-5.08-3.85-36.99"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#410380"
|
||||
strokeWidth=".5"
|
||||
d="M111.234 10.06c11.83 11.18 23.65 22.36 44.51 42.07m-44.51-42.07 44.51 42.07m0 0c-13.07-1.63-26.13-3.27-40.66-5.08m40.66 5.08c-11.33-1.41-22.67-2.83-40.66-5.08m0 0c-1.17-11.29-2.35-22.58-3.85-36.99m3.85 36.99c-1.47-14.17-2.95-28.33-3.85-36.99m0 0s0 0 0 0m0 0s0 0 0 0"
|
||||
/>
|
||||
</g>
|
||||
<g fillRule="evenodd">
|
||||
<path
|
||||
fill="#2f005e"
|
||||
strokeWidth="0"
|
||||
d="m10 127.778 45.77-32.99-15.57-38.08-30.2 71.07"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#410380"
|
||||
strokeWidth=".5"
|
||||
d="M10 127.778c16.85-12.14 33.7-24.29 45.77-32.99M10 127.778c16.59-11.95 33.17-23.91 45.77-32.99m0 0c-6.14-15.02-12.29-30.05-15.57-38.08m15.57 38.08c-4.08-9.98-8.16-19.96-15.57-38.08m0 0c-11.16 26.27-22.33 52.54-30.2 71.07m30.2-71.07c-10.12 23.81-20.23 47.61-30.2 71.07m0 0s0 0 0 0m0 0s0 0 0 0"
|
||||
/>
|
||||
</g>
|
||||
<g fillRule="evenodd">
|
||||
<path
|
||||
fill="#410380"
|
||||
strokeWidth="0"
|
||||
d="m40.208 235.61 15.76-140.4-45.92 32.92 30.16 107.48"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#410380"
|
||||
strokeWidth=".5"
|
||||
d="M40.208 235.61c3.7-33.01 7.41-66.02 15.76-140.4m-15.76 140.4c3.38-30.16 6.77-60.32 15.76-140.4m0 0c-10.83 7.76-21.66 15.53-45.92 32.92m45.92-32.92c-11.69 8.38-23.37 16.75-45.92 32.92m0 0c6.84 24.4 13.69 48.8 30.16 107.48m-30.16-107.48c6.67 23.77 13.33 47.53 30.16 107.48m0 0s0 0 0 0m0 0s0 0 0 0"
|
||||
/>
|
||||
</g>
|
||||
<g fillRule="evenodd">
|
||||
<path
|
||||
fill="#943feb"
|
||||
strokeWidth="0"
|
||||
d="m111.234 240.434-12.47 16.67-42.36-161.87 58.81-48.3 40.46 5.25-44.44 188.25"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#410380"
|
||||
strokeWidth=".5"
|
||||
d="M111.234 240.434c-3.79 5.06-7.57 10.12-12.47 16.67m12.47-16.67c-4.43 5.93-8.87 11.85-12.47 16.67m0 0c-16.8-64.17-33.59-128.35-42.36-161.87m42.36 161.87c-9.74-37.2-19.47-74.41-42.36-161.87m0 0c15.03-12.35 30.07-24.7 58.81-48.3m-58.81 48.3c22.49-18.47 44.97-36.94 58.81-48.3m0 0c9.48 1.23 18.95 2.46 40.46 5.25m-40.46-5.25c13.01 1.69 26.02 3.38 40.46 5.25m0 0c-10.95 46.41-21.91 92.82-44.44 188.25m44.44-188.25c-12.2 51.71-24.41 103.42-44.44 188.25m0 0s0 0 0 0m0 0s0 0 0 0"
|
||||
/>
|
||||
</g>
|
||||
<g fillRule="evenodd">
|
||||
<path
|
||||
fill="#6212b3"
|
||||
strokeWidth="0"
|
||||
d="m40.379 235.667 15.9-140.21 42.43 161.79-58.33-21.58"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#410380"
|
||||
strokeWidth=".5"
|
||||
d="M40.379 235.667c4.83-42.62 9.67-85.25 15.9-140.21m-15.9 140.21c5.84-51.52 11.69-103.03 15.9-140.21m0 0c10.98 41.87 21.96 83.74 42.43 161.79m-42.43-161.79c13.28 50.63 26.56 101.25 42.43 161.79m0 0c-11.8-4.37-23.6-8.74-58.33-21.58m58.33 21.58c-21.73-8.04-43.47-16.08-58.33-21.58m0 0s0 0 0 0m0 0s0 0 0 0"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</label>
|
||||
<>
|
||||
<label
|
||||
className={clsx(
|
||||
"ToolIcon ToolIcon_type_floating",
|
||||
"ToolIcon_size_medium",
|
||||
{
|
||||
"is-mobile": isMobile,
|
||||
},
|
||||
)}
|
||||
onClick={() => {
|
||||
this.toolsRef.current.setTheme(appState.theme);
|
||||
this.toolsRef.current.toggleVisibility(
|
||||
appState.zenModeEnabled || isMobile,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<div className="ToolIcon__icon" aria-hidden="true">
|
||||
{ICONS.obsidian}
|
||||
</div>
|
||||
</label>
|
||||
{this.renderCustomPens(isMobile,appState)}
|
||||
{this.renderPinnedScriptButtons(isMobile,appState)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,14 +2,17 @@ import clsx from "clsx";
|
||||
import { Notice, TFile } from "obsidian";
|
||||
import * as React from "react";
|
||||
import { ActionButton } from "./ActionButton";
|
||||
import { ICONS, stringToSVG } from "./ActionIcons";
|
||||
import { SCRIPT_INSTALL_FOLDER, CTRL_OR_CMD } from "../Constants";
|
||||
import { ICONS, saveIcon, stringToSVG } from "./ActionIcons";
|
||||
import { DEVICE, SCRIPT_INSTALL_FOLDER, VIEW_TYPE_EXCALIDRAW } from "../Constants";
|
||||
import { insertLaTeXToView, search } from "../ExcalidrawAutomate";
|
||||
import ExcalidrawView, { TextMode } from "../ExcalidrawView";
|
||||
import { t } from "../lang/helpers";
|
||||
import { ReleaseNotes } from "../dialogs/ReleaseNotes";
|
||||
import { ScriptIconMap } from "../Scripts";
|
||||
import { getIMGFilename } from "../utils/FileUtils";
|
||||
import { ScriptInstallPrompt } from "src/dialogs/ScriptInstallPrompt";
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/types";
|
||||
import { isALT, isCTRL, isSHIFT, mdPropModifier } from "src/utils/ModifierkeyHelper";
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
const dark = '<svg style="stroke:#ced4da;#212529;color:#ced4da;fill:#ced4da" ';
|
||||
@@ -28,6 +31,7 @@ export type PanelState = {
|
||||
theme: "dark" | "light";
|
||||
excalidrawViewMode: boolean;
|
||||
minimized: boolean;
|
||||
isDirty: boolean;
|
||||
isFullscreen: boolean;
|
||||
isPreviewMode: boolean;
|
||||
scriptIconMap: ScriptIconMap;
|
||||
@@ -59,6 +63,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
theme: "dark",
|
||||
excalidrawViewMode: false,
|
||||
minimized: false,
|
||||
isDirty: false,
|
||||
isFullscreen: false,
|
||||
isPreviewMode: true,
|
||||
scriptIconMap: {},
|
||||
@@ -87,6 +92,14 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
});
|
||||
}
|
||||
|
||||
setDirty(isDirty: boolean) {
|
||||
this.setState(()=> {
|
||||
return {
|
||||
isDirty,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
setExcalidrawViewMode(isViewModeEnabled: boolean) {
|
||||
this.setState(() => {
|
||||
return {
|
||||
@@ -257,7 +270,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
className="Island App-menu__left scrollbar"
|
||||
style={{
|
||||
maxHeight: "350px",
|
||||
backgroundColor: "transparent",
|
||||
width: "initial",
|
||||
//@ts-ignore
|
||||
"--padding": 2,
|
||||
display: this.state.minimized ? "none" : "block",
|
||||
@@ -267,13 +280,13 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
<fieldset>
|
||||
<legend>Utility actions</legend>
|
||||
<div className="buttonList buttonListIcon">
|
||||
<ActionButton
|
||||
key={"search"}
|
||||
title={t("SEARCH")}
|
||||
<ActionButton
|
||||
key={"scriptEngine"}
|
||||
title={t("INSTALL_SCRIPT_BUTTON")}
|
||||
action={() => {
|
||||
search(this.props.view);
|
||||
new ScriptInstallPrompt(this.props.view.plugin).open();
|
||||
}}
|
||||
icon={ICONS.search}
|
||||
icon={ICONS.scriptEngine}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
@@ -348,6 +361,67 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"search"}
|
||||
title={t("SEARCH")}
|
||||
action={() => {
|
||||
search(this.props.view);
|
||||
}}
|
||||
icon={ICONS.search}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"ocr"}
|
||||
title={t("RUN_OCR")}
|
||||
action={(e:React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
if(!this.props.view.plugin.settings.taskboneEnabled) {
|
||||
new Notice("Taskbone OCR is not enabled. Please go to plugins settings to enable it.",4000);
|
||||
return;
|
||||
}
|
||||
this.props.view.plugin.taskbone.getTextForView(this.props.view, isCTRL(e));
|
||||
}}
|
||||
icon={ICONS.ocr}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"openLink"}
|
||||
title={t("OPEN_LINK_CLICK")}
|
||||
action={(e) => {
|
||||
const event = new MouseEvent("click", {
|
||||
ctrlKey: e.ctrlKey || !(DEVICE.isIOS || DEVICE.isMacOS),
|
||||
metaKey: e.metaKey || (DEVICE.isIOS || DEVICE.isMacOS),
|
||||
shiftKey: e.shiftKey,
|
||||
altKey: e.altKey,
|
||||
});
|
||||
this.props.view.handleLinkClick(event);
|
||||
}}
|
||||
icon={ICONS.openLink}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"openLinkProperties"}
|
||||
title={t("OPEN_LINK_PROPS")}
|
||||
action={() => {
|
||||
const event = new MouseEvent("click", {
|
||||
ctrlKey: true,
|
||||
metaKey: true,
|
||||
shiftKey: false,
|
||||
altKey: false,
|
||||
});
|
||||
this.props.view.handleLinkClick(event);
|
||||
}}
|
||||
icon={ICONS.openLinkProperties}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"save"}
|
||||
title={t("FORCE_SAVE")}
|
||||
action={() => {
|
||||
this.props.view.forceSave();
|
||||
}}
|
||||
icon={saveIcon(this.state.isDirty)}
|
||||
view={this.props.view}
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
@@ -442,7 +516,11 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
<ActionButton
|
||||
key={"latex"}
|
||||
title={t("INSERT_LATEX")}
|
||||
action={() => {
|
||||
action={(e) => {
|
||||
if(isALT(e)) {
|
||||
this.props.view.openExternalLink("https://youtu.be/r08wk-58DPk");
|
||||
return;
|
||||
}
|
||||
this.props.centerPointer();
|
||||
insertLaTeXToView(this.props.view);
|
||||
}}
|
||||
@@ -466,8 +544,12 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
key={"link-to-element"}
|
||||
title={t("INSERT_LINK_TO_ELEMENT")}
|
||||
action={(e:React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
if(isALT(e)) {
|
||||
this.props.view.openExternalLink("https://youtu.be/yZQoJg2RCKI");
|
||||
return;
|
||||
}
|
||||
this.props.view.copyLinkToSelectedElementToClipboard(
|
||||
e[CTRL_OR_CMD] ? "group=" : (e.shiftKey ? "area=" : "")
|
||||
isCTRL(e) ? "group=" : (isSHIFT(e) ? "area=" : "")
|
||||
);
|
||||
}}
|
||||
icon={ICONS.copyElementLink}
|
||||
@@ -512,47 +594,72 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
return "";
|
||||
}
|
||||
|
||||
const groups = new Set<string>();
|
||||
|
||||
Object.keys(this.state.scriptIconMap)
|
||||
.filter((k) => filterCondition(k))
|
||||
.forEach(k => groups.add(this.state.scriptIconMap[k].group))
|
||||
|
||||
const scriptlist = Array.from(groups).sort((a,b)=>a>b?1:-1);
|
||||
scriptlist.push(scriptlist.shift());
|
||||
return (
|
||||
<fieldset>
|
||||
<legend>{isDownloaded ? "Downloaded" : "User"} Scripts</legend>
|
||||
<div className="buttonList buttonListIcon">
|
||||
{Object.keys(this.state.scriptIconMap)
|
||||
.filter((k) => filterCondition(k))
|
||||
.sort()
|
||||
.map((key: string) => (
|
||||
<ActionButton
|
||||
key={key}
|
||||
title={
|
||||
isDownloaded
|
||||
? this.state.scriptIconMap[key].name.replace(
|
||||
`${SCRIPT_INSTALL_FOLDER}/`,
|
||||
"",
|
||||
)
|
||||
: this.state.scriptIconMap[key].name
|
||||
}
|
||||
action={async () => {
|
||||
const f =
|
||||
this.props.view.app.vault.getAbstractFileByPath(key);
|
||||
if (f && f instanceof TFile) {
|
||||
this.props.view.plugin.scriptEngine.executeScript(
|
||||
this.props.view,
|
||||
await this.props.view.plugin.app.vault.read(f),
|
||||
this.props.view.plugin.scriptEngine.getScriptName(f)
|
||||
);
|
||||
<>
|
||||
{scriptlist.map(group => (
|
||||
<fieldset>
|
||||
<legend>{isDownloaded ? group : (group === "" ? "User" : "User/"+group)}</legend>
|
||||
<div className="buttonList buttonListIcon">
|
||||
{Object.entries(this.state.scriptIconMap)
|
||||
.filter(([k,v])=>v.group === group)
|
||||
.sort()
|
||||
.map(([key,value])=>(
|
||||
<ActionButton
|
||||
key={key}
|
||||
title={value.name}
|
||||
action={async () => {
|
||||
const view = this.props.view;
|
||||
const plugin = view.plugin;
|
||||
const f = app.vault.getAbstractFileByPath(key);
|
||||
if (f && f instanceof TFile) {
|
||||
plugin.scriptEngine.executeScript(
|
||||
view,
|
||||
await app.vault.read(f),
|
||||
plugin.scriptEngine.getScriptName(f),
|
||||
f
|
||||
);
|
||||
}
|
||||
}}
|
||||
longpress={async () => {
|
||||
const view = this.props.view;
|
||||
const api = view.excalidrawAPI as ExcalidrawImperativeAPI;
|
||||
const plugin = view.plugin;
|
||||
await plugin.loadSettings();
|
||||
const index = plugin.settings.pinnedScripts.indexOf(key)
|
||||
if(index > -1) {
|
||||
plugin.settings.pinnedScripts.splice(index,1);
|
||||
api?.setToast({message:`Pin removed: ${value.name}`, duration: 3000, closable: true});
|
||||
} else {
|
||||
plugin.settings.pinnedScripts.push(key);
|
||||
api?.setToast({message:`Pinned: ${value.name}`, duration: 3000, closable: true})
|
||||
}
|
||||
await plugin.saveSettings();
|
||||
app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> {
|
||||
if (v.view instanceof ExcalidrawView) v.view.updatePinnedScripts()
|
||||
})
|
||||
}}
|
||||
icon={
|
||||
value.svgString
|
||||
? stringToSVG(value.svgString)
|
||||
: (
|
||||
ICONS.cog
|
||||
)
|
||||
}
|
||||
}}
|
||||
icon={
|
||||
this.state.scriptIconMap[key].svgString
|
||||
? stringToSVG(this.state.scriptIconMap[key].svgString)
|
||||
: (
|
||||
ICONS.cog
|
||||
)
|
||||
}
|
||||
view={this.props.view}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</fieldset>
|
||||
view={this.props.view}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</fieldset>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
147
src/ocr/Taskbone.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { createPNG, ExcalidrawAutomate } from "../ExcalidrawAutomate";
|
||||
import {Notice, requestUrl} from "obsidian"
|
||||
import ExcalidrawPlugin from "../main"
|
||||
import {log} from "../utils/Utils"
|
||||
import ExcalidrawView, { ExportSettings } from "../ExcalidrawView"
|
||||
import FrontmatterEditor from "src/utils/Frontmatter";
|
||||
import { ExcalidrawElement, ExcalidrawImageElement } from "@zsviczian/excalidraw/types/element/types";
|
||||
import { EmbeddedFilesLoader } from "src/EmbeddedFileLoader";
|
||||
|
||||
const TASKBONE_URL = "https://api.taskbone.com/"; //"https://excalidraw-preview.onrender.com/";
|
||||
const TASKBONE_OCR_FN = "execute?id=60f394af-85f6-40bc-9613-5d26dc283cbb";
|
||||
|
||||
export default class Taskbone {
|
||||
get apiKey() {
|
||||
return this.plugin.settings.taskboneAPIkey;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private plugin: ExcalidrawPlugin
|
||||
) {
|
||||
}
|
||||
|
||||
public async initialize(save:boolean = true):Promise<string> {
|
||||
if(this.plugin.settings.taskboneAPIkey !== "") return;
|
||||
const response = await requestUrl({
|
||||
url: `${TASKBONE_URL}users/excalidraw-obsidian/identities`,
|
||||
method: "post",
|
||||
contentType: "application/json",
|
||||
throw: false
|
||||
});
|
||||
if(!response) return;
|
||||
const apiKey = response.json?.apiKey;
|
||||
if(apiKey && typeof apiKey === "string") {
|
||||
if(save) await this.plugin.loadSettings();
|
||||
this.plugin.settings.taskboneAPIkey = apiKey;
|
||||
if(save) await this.plugin.saveSettings();
|
||||
}
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
public async getTextForView(view: ExcalidrawView, forceReScan: boolean) {
|
||||
await view.forceSave(true);
|
||||
const viewElements = view.excalidrawAPI.getSceneElements().filter((el:ExcalidrawElement) =>
|
||||
el.type==="freedraw" ||
|
||||
( el.type==="image" &&
|
||||
!this.plugin.isExcalidrawFile(view.excalidrawData.getFile(el.fileId)?.file)
|
||||
));
|
||||
if(viewElements.length === 0) {
|
||||
new Notice ("Aborting OCR because there are no image or freedraw elements on the canvas.",4000);
|
||||
return;
|
||||
}
|
||||
const fe = new FrontmatterEditor(view.data);
|
||||
if(fe.hasKey("taskbone-ocr") && !forceReScan) {
|
||||
new Notice ("The drawing has already been processed, you will find the result in the frontmatter in markdown view mode. If you ran the command from the Obsidian Panel in Excalidraw then you can CTRL(CMD)+click the command to force the rescaning.",4000)
|
||||
return;
|
||||
}
|
||||
const bb = this.plugin.ea.getBoundingBox(viewElements);
|
||||
const size = (bb.width*bb.height);
|
||||
const minRatio = Math.sqrt(360000/size);
|
||||
const maxRatio = Math.sqrt(size/16000000);
|
||||
const scale = minRatio > 1
|
||||
? minRatio
|
||||
: (
|
||||
maxRatio > 1
|
||||
? 1/maxRatio
|
||||
: 1
|
||||
);
|
||||
|
||||
const loader = new EmbeddedFilesLoader(
|
||||
this.plugin,
|
||||
false,
|
||||
);
|
||||
|
||||
const exportSettings: ExportSettings = {
|
||||
withBackground: true,
|
||||
withTheme: true,
|
||||
};
|
||||
|
||||
const img =
|
||||
await createPNG(
|
||||
view.file.path + "#^taskbone",
|
||||
scale,
|
||||
exportSettings,
|
||||
loader,
|
||||
"light",
|
||||
null,
|
||||
null,
|
||||
[],
|
||||
this.plugin,
|
||||
0
|
||||
);
|
||||
|
||||
const text = await this.getTextForImage(img);
|
||||
if(text) {
|
||||
fe.setKey("taskbone-ocr",text);
|
||||
view.data = fe.data;
|
||||
view.save(false);
|
||||
window.navigator.clipboard.writeText(text);
|
||||
new Notice("I placed the recognized in the drawing's frontmatter and onto the system clipboard.");
|
||||
}
|
||||
}
|
||||
|
||||
private async getTextForImage(image: Blob):Promise<string> {
|
||||
const url = TASKBONE_URL+TASKBONE_OCR_FN;
|
||||
if(this.apiKey === "") {
|
||||
await this.initialize();
|
||||
}
|
||||
const base64Image = await this.blobToBase64(image);
|
||||
const input = {
|
||||
records: [{
|
||||
image: base64Image
|
||||
}]
|
||||
};
|
||||
|
||||
const apiResponse = await requestUrl ({
|
||||
url: url,
|
||||
method: "post",
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify(input),
|
||||
headers: {
|
||||
authorization: `Bearer ${this.apiKey}`
|
||||
},
|
||||
throw: false
|
||||
});
|
||||
const content = apiResponse?.json;
|
||||
|
||||
if(!content || apiResponse.status !== 200) {
|
||||
new Notice("Something went wrong while processing your request. Please check developer console for more information");
|
||||
log(apiResponse);
|
||||
return;
|
||||
}
|
||||
return content.records[0].text;
|
||||
}
|
||||
|
||||
|
||||
private async blobToBase64(blob: Blob): Promise<string> {
|
||||
const arrayBuffer = await blob.arrayBuffer()
|
||||
const bytes = new Uint8Array(arrayBuffer)
|
||||
var binary = '';
|
||||
var len = bytes.byteLength;
|
||||
for (var i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return btoa(binary);
|
||||
}
|
||||
}
|
||||
|
||||
277
src/settings.ts
@@ -7,14 +7,16 @@ import {
|
||||
TextComponent,
|
||||
TFile,
|
||||
} from "obsidian";
|
||||
import { VIEW_TYPE_EXCALIDRAW } from "./Constants";
|
||||
import { GITHUB_RELEASES, VIEW_TYPE_EXCALIDRAW } from "./Constants";
|
||||
import ExcalidrawView from "./ExcalidrawView";
|
||||
import { t } from "./lang/helpers";
|
||||
import type ExcalidrawPlugin from "./main";
|
||||
import { PenStyle } from "./PenTypes";
|
||||
import {
|
||||
getDrawingFilename,
|
||||
getEmbedFilename,
|
||||
} from "./utils/FileUtils";
|
||||
import { PENS } from "./utils/Pens";
|
||||
import {
|
||||
fragWithHTML,
|
||||
setLeftHandedMode,
|
||||
@@ -28,6 +30,8 @@ export interface ExcalidrawSettings {
|
||||
compress: boolean;
|
||||
autosave: boolean;
|
||||
autosaveInterval: number;
|
||||
autosaveIntervalDesktop: number;
|
||||
autosaveIntervalMobile: number;
|
||||
drawingFilenamePrefix: string;
|
||||
drawingEmbedPrefixWithFilename: boolean;
|
||||
drawingFilnameEmbedPostfix: string;
|
||||
@@ -43,6 +47,9 @@ export interface ExcalidrawSettings {
|
||||
matchThemeTrigger: boolean;
|
||||
defaultMode: string;
|
||||
defaultPenMode: "never" | "mobile" | "always";
|
||||
allowPinchZoom: boolean;
|
||||
allowWheelZoom: boolean;
|
||||
zoomToFitOnOpen: boolean;
|
||||
zoomToFitOnResize: boolean;
|
||||
zoomToFitMaxLevel: number;
|
||||
openInAdjacentPane: boolean;
|
||||
@@ -110,8 +117,15 @@ export interface ExcalidrawSettings {
|
||||
showReleaseNotes: boolean;
|
||||
showNewVersionNotification: boolean;
|
||||
mathjaxSourceURL: string;
|
||||
taskboneEnabled: boolean;
|
||||
taskboneAPIkey: string;
|
||||
pinnedScripts: string[];
|
||||
customPens: PenStyle[];
|
||||
numberOfCustomPens: number;
|
||||
}
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
|
||||
export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
folder: "Excalidraw",
|
||||
embedUseExcalidrawFolder: false,
|
||||
@@ -120,6 +134,8 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
compress: false,
|
||||
autosave: true,
|
||||
autosaveInterval: 15000,
|
||||
autosaveIntervalDesktop: 15000,
|
||||
autosaveIntervalMobile: 10000,
|
||||
drawingFilenamePrefix: "Drawing ",
|
||||
drawingEmbedPrefixWithFilename: true,
|
||||
drawingFilnameEmbedPostfix: " ",
|
||||
@@ -135,6 +151,9 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
matchThemeTrigger: false,
|
||||
defaultMode: "normal",
|
||||
defaultPenMode: "never",
|
||||
allowPinchZoom: false,
|
||||
allowWheelZoom: false,
|
||||
zoomToFitOnOpen: true,
|
||||
zoomToFitOnResize: true,
|
||||
zoomToFitMaxLevel: 2,
|
||||
linkPrefix: "📍",
|
||||
@@ -178,7 +197,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
library2: {
|
||||
type: "excalidrawlib",
|
||||
version: 2,
|
||||
source: "https://excalidraw.com",
|
||||
source: GITHUB_RELEASES+PLUGIN_VERSION,
|
||||
libraryItems: [],
|
||||
},
|
||||
//patchCommentBlock: true,
|
||||
@@ -192,17 +211,34 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
mdBorderColor: "Black",
|
||||
mdCSS: "",
|
||||
scriptEngineSettings: {},
|
||||
defaultTrayMode: false,
|
||||
previousRelease: "1.6.13",
|
||||
defaultTrayMode: true,
|
||||
previousRelease: "0.0.0",
|
||||
showReleaseNotes: true,
|
||||
showNewVersionNotification: true,
|
||||
mathjaxSourceURL: "https://cdn.jsdelivr.net/npm/mathjax@3.2.1/es5/tex-svg.js"
|
||||
mathjaxSourceURL: "https://cdn.jsdelivr.net/npm/mathjax@3.2.1/es5/tex-svg.js",
|
||||
taskboneEnabled: false,
|
||||
taskboneAPIkey: "",
|
||||
pinnedScripts: [],
|
||||
customPens: [
|
||||
{...PENS["default"]},
|
||||
{...PENS["highlighter"]},
|
||||
{...PENS["finetip"]},
|
||||
{...PENS["fountain"]},
|
||||
{...PENS["marker"]},
|
||||
{...PENS["thick-thin"]},
|
||||
{...PENS["thin-thick-thin"]},
|
||||
{...PENS["default"]},
|
||||
{...PENS["default"]},
|
||||
{...PENS["default"]}
|
||||
],
|
||||
numberOfCustomPens: 0,
|
||||
};
|
||||
|
||||
export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
plugin: ExcalidrawPlugin;
|
||||
private requestEmbedUpdate: boolean = false;
|
||||
private requestReloadDrawings: boolean = false;
|
||||
private requestUpdatePinnedPens: boolean = false;
|
||||
private reloadMathJax: boolean = false;
|
||||
//private applyDebounceTimer: number = 0;
|
||||
|
||||
@@ -228,6 +264,11 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
this.plugin.settings.scriptFolderPath = "Excalidraw/Scripts";
|
||||
}
|
||||
this.plugin.saveSettings();
|
||||
if (this.requestUpdatePinnedPens) {
|
||||
app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> {
|
||||
if (v.view instanceof ExcalidrawView) v.view.updatePinnedCustomPens()
|
||||
})
|
||||
}
|
||||
if (this.requestReloadDrawings) {
|
||||
const exs =
|
||||
app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
@@ -254,6 +295,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
this.requestEmbedUpdate = false;
|
||||
this.requestReloadDrawings = false;
|
||||
const { containerEl } = this;
|
||||
containerEl.addClass("excalidraw-settings");
|
||||
this.containerEl.empty();
|
||||
|
||||
const coffeeDiv = containerEl.createDiv("coffee");
|
||||
@@ -343,6 +385,8 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
this.containerEl.createEl("h1", { text: t("SAVING_HEAD") });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(t("COMPRESS_NAME"))
|
||||
.setDesc(fragWithHTML(t("COMPRESS_DESC")))
|
||||
@@ -353,7 +397,45 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
this.plugin.settings.compress = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(t("AUTOSAVE_INTERVAL_DESKTOP_NAME"))
|
||||
.setDesc(fragWithHTML(t("AUTOSAVE_INTERVAL_DESKTOP_DESC")))
|
||||
.addDropdown((dropdown) =>
|
||||
dropdown
|
||||
.addOption("15000", "Frequent (every 15 seconds)")
|
||||
.addOption("60000", "Moderate (every 60 seconds)")
|
||||
.addOption("300000", "Rare (every 5 minutes)")
|
||||
.addOption("900000", "Practically never (every 15 minutes)")
|
||||
.setValue(this.plugin.settings.autosaveIntervalDesktop.toString())
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.autosaveIntervalDesktop = parseInt(value);
|
||||
this.plugin.settings.autosaveInterval = app.isMobile
|
||||
? this.plugin.settings.autosaveIntervalMobile
|
||||
: this.plugin.settings.autosaveIntervalDesktop;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(t("AUTOSAVE_INTERVAL_MOBILE_NAME"))
|
||||
.setDesc(fragWithHTML(t("AUTOSAVE_INTERVAL_MOBILE_DESC")))
|
||||
.addDropdown((dropdown) =>
|
||||
dropdown
|
||||
.addOption("10000", "Frequent (every 10 seconds)")
|
||||
.addOption("30000", "Moderate (every 30 seconds)")
|
||||
.addOption("60000", "Rare (every 1 minute)")
|
||||
.addOption("300000", "Practically never (every 5 minutes)")
|
||||
.setValue(this.plugin.settings.autosaveIntervalMobile.toString())
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.autosaveIntervalMobile = parseInt(value);
|
||||
this.plugin.settings.autosaveInterval = app.isMobile
|
||||
? this.plugin.settings.autosaveIntervalMobile
|
||||
: this.plugin.settings.autosaveIntervalDesktop;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
this.containerEl.createEl("h1", { text: t("FILENAME_HEAD") });
|
||||
containerEl.createDiv("", (el) => {
|
||||
@@ -515,9 +597,10 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
.setDesc(fragWithHTML(t("DEFAULT_OPEN_MODE_DESC")))
|
||||
.addDropdown((dropdown) =>
|
||||
dropdown
|
||||
.addOption("normal", "Normal Mode")
|
||||
.addOption("zen", "Zen Mode")
|
||||
.addOption("view", "View Mode")
|
||||
.addOption("normal", "Always in normal-mode")
|
||||
.addOption("zen", "Always in zen-mode")
|
||||
.addOption("view", "Always in view-mode")
|
||||
.addOption("view-mobile", "Usually normal, but view-mode on Phone")
|
||||
.setValue(this.plugin.settings.defaultMode)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.defaultMode = value;
|
||||
@@ -540,6 +623,48 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(t("DEFAULT_PINCHZOOM_NAME"))
|
||||
.setDesc(fragWithHTML(t("DEFAULT_PINCHZOOM_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.allowPinchZoom)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.allowPinchZoom = value;
|
||||
app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> {
|
||||
if (v.view instanceof ExcalidrawView) v.view.updatePinchZoom()
|
||||
})
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(t("DEFAULT_WHEELZOOM_NAME"))
|
||||
.setDesc(fragWithHTML(t("DEFAULT_WHEELZOOM_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.allowWheelZoom)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.allowWheelZoom = value;
|
||||
app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> {
|
||||
if (v.view instanceof ExcalidrawView) v.view.updateWheelZoom()
|
||||
})
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(t("ZOOM_TO_FIT_ONOPEN_NAME"))
|
||||
.setDesc(fragWithHTML(t("ZOOM_TO_FIT_ONOPEN_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.zoomToFitOnOpen)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.zoomToFitOnOpen = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(t("ZOOM_TO_FIT_NAME"))
|
||||
.setDesc(fragWithHTML(t("ZOOM_TO_FIT_DESC")))
|
||||
@@ -1228,6 +1353,70 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
this.containerEl.createEl("h1", { text: t("NONSTANDARD_HEAD") });
|
||||
this.containerEl.createEl("p", { text: t("NONSTANDARD_DESC") });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(t("CUSTOM_PEN_NAME"))
|
||||
.setDesc(t("CUSTOM_PEN_DESC"))
|
||||
.addDropdown((dropdown) =>
|
||||
dropdown
|
||||
.addOption("0","0")
|
||||
.addOption("1","1")
|
||||
.addOption("2","2")
|
||||
.addOption("3","3")
|
||||
.addOption("4","4")
|
||||
.addOption("5","5")
|
||||
.addOption("6","6")
|
||||
.addOption("7","7")
|
||||
.addOption("8","8")
|
||||
.addOption("9","9")
|
||||
.addOption("10","10")
|
||||
.setValue(this.plugin.settings.numberOfCustomPens.toString())
|
||||
.onChange((value)=>{
|
||||
this.plugin.settings.numberOfCustomPens = parseInt(value);
|
||||
this.requestUpdatePinnedPens = true;
|
||||
this.applySettingsUpdate(false);
|
||||
})
|
||||
)
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(t("ENABLE_FOURTH_FONT_NAME"))
|
||||
.setDesc(fragWithHTML(t("ENABLE_FOURTH_FONT_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.experimentalEnableFourthFont)
|
||||
.onChange(async (value) => {
|
||||
this.requestReloadDrawings = true;
|
||||
this.plugin.settings.experimentalEnableFourthFont = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(t("FOURTH_FONT_NAME"))
|
||||
.setDesc(fragWithHTML(t("FOURTH_FONT_DESC")))
|
||||
.addDropdown(async (d: DropdownComponent) => {
|
||||
d.addOption("Virgil", "Virgil");
|
||||
this.app.vault
|
||||
.getFiles()
|
||||
.filter((f) => ["ttf", "woff", "woff2"].contains(f.extension))
|
||||
.forEach((f: TFile) => {
|
||||
d.addOption(f.path, f.name);
|
||||
});
|
||||
d.setValue(this.plugin.settings.experimantalFourthFont).onChange(
|
||||
(value) => {
|
||||
this.requestReloadDrawings = true;
|
||||
this.plugin.settings.experimantalFourthFont = value;
|
||||
this.applySettingsUpdate(true);
|
||||
this.plugin.initializeFourthFont();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
this.containerEl.createEl("h1", { text: t("EXPERIMENTAL_HEAD") });
|
||||
this.containerEl.createEl("p", { text: t("EXPERIMENTAL_DESC") });
|
||||
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/628
|
||||
new Setting(containerEl)
|
||||
@@ -1245,10 +1434,6 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
this.applySettingsUpdate();
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
this.containerEl.createEl("h1", { text: t("EXPERIMENTAL_HEAD") });
|
||||
this.containerEl.createEl("p", { text: t("EXPERIMENTAL_DESC") });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(t("FIELD_SUGGESTER_NAME"))
|
||||
@@ -1300,39 +1485,43 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(t("ENABLE_FOURTH_FONT_NAME"))
|
||||
.setDesc(fragWithHTML(t("ENABLE_FOURTH_FONT_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.experimentalEnableFourthFont)
|
||||
.onChange(async (value) => {
|
||||
this.requestReloadDrawings = true;
|
||||
this.plugin.settings.experimentalEnableFourthFont = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
this.containerEl.createEl("h2", { text: t("TASKBONE_HEAD") });
|
||||
this.containerEl.createEl("p", { text: t("TASKBONE_DESC") });
|
||||
let taskboneAPIKeyText: TextComponent;
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(t("FOURTH_FONT_NAME"))
|
||||
.setDesc(fragWithHTML(t("FOURTH_FONT_DESC")))
|
||||
.addDropdown(async (d: DropdownComponent) => {
|
||||
d.addOption("Virgil", "Virgil");
|
||||
this.app.vault
|
||||
.getFiles()
|
||||
.filter((f) => ["ttf", "woff", "woff2"].contains(f.extension))
|
||||
.forEach((f: TFile) => {
|
||||
d.addOption(f.path, f.name);
|
||||
});
|
||||
d.setValue(this.plugin.settings.experimantalFourthFont).onChange(
|
||||
(value) => {
|
||||
this.requestReloadDrawings = true;
|
||||
this.plugin.settings.experimantalFourthFont = value;
|
||||
this.applySettingsUpdate(true);
|
||||
this.plugin.initializeFourthFont();
|
||||
},
|
||||
);
|
||||
});
|
||||
.setName(t("TASKBONE_ENABLE_NAME"))
|
||||
.setDesc(fragWithHTML(t("TASKBONE_ENABLE_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.taskboneEnabled)
|
||||
.onChange(async (value) => {
|
||||
taskboneAPIKeyText.setDisabled(!value);
|
||||
this.plugin.settings.taskboneEnabled = value;
|
||||
if(this.plugin.settings.taskboneAPIkey === "") {
|
||||
const apiKey = await this.plugin.taskbone.initialize(false);
|
||||
if(apiKey) {
|
||||
taskboneAPIKeyText.setValue(apiKey);
|
||||
}
|
||||
}
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(t("TASKBONE_APIKEY_NAME"))
|
||||
.setDesc(fragWithHTML(t("TASKBONE_APIKEY_DESC")))
|
||||
.addText((text) => {
|
||||
taskboneAPIKeyText = text;
|
||||
taskboneAPIKeyText
|
||||
.setValue(this.plugin.settings.taskboneAPIkey)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.taskboneAPIkey = value;
|
||||
this.applySettingsUpdate();
|
||||
})
|
||||
.setDisabled(!this.plugin.settings.taskboneEnabled);
|
||||
}
|
||||
);
|
||||
|
||||
//-------------------------------------
|
||||
//Script settings
|
||||
|
||||
@@ -15,7 +15,7 @@ export function get(el: Element, attr: string, backup?: string): string {
|
||||
|
||||
export function getNum(el: Element, attr: string, backup?: number): number {
|
||||
const numVal = Number(get(el, attr));
|
||||
return numVal === NaN ? backup || 0 : numVal;
|
||||
return Number.isNaN(numVal) ? backup || 0 : numVal;
|
||||
}
|
||||
|
||||
const presAttrs = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { randomId, randomInteger } from "../utils";
|
||||
|
||||
import { ExcalidrawLinearElement, FillStyle, GroupId, StrokeSharpness, StrokeStyle } from "@zsviczian/excalidraw/types/element/types";
|
||||
import { ExcalidrawLinearElement, FillStyle, GroupId, RoundnessType, StrokeStyle } from "@zsviczian/excalidraw/types/element/types";
|
||||
|
||||
export type Point = [number, number];
|
||||
|
||||
@@ -13,7 +13,7 @@ export type ExcalidrawElementBase = {
|
||||
fillStyle: FillStyle;
|
||||
strokeWidth: number;
|
||||
strokeStyle: StrokeStyle;
|
||||
strokeSharpness: StrokeSharpness;
|
||||
roundness: null | { type: RoundnessType; value?: number };
|
||||
roughness: number;
|
||||
opacity: number;
|
||||
width: number;
|
||||
@@ -71,7 +71,7 @@ export function createExElement(): ExcalidrawElementBase {
|
||||
fillStyle: "solid",
|
||||
strokeWidth: 1,
|
||||
strokeStyle: "solid",
|
||||
strokeSharpness: "sharp",
|
||||
roundness: null,
|
||||
roughness: 0,
|
||||
opacity: 100,
|
||||
width: 0,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { GITHUB_RELEASES } from "src/Constants";
|
||||
import { ExcalidrawGenericElement } from "./ExcalidrawElement";
|
||||
declare const PLUGIN_VERSION:string;
|
||||
|
||||
class ExcalidrawScene {
|
||||
type = "excalidraw";
|
||||
version = 2;
|
||||
source = "https://excalidraw.com";
|
||||
source = GITHUB_RELEASES+PLUGIN_VERSION;
|
||||
elements: ExcalidrawGenericElement[] = [];
|
||||
|
||||
constructor(elements:any = []) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ExcalidrawElement, ExcalidrawLinearElement, ExcalidrawTextElement, FillStyle, GroupId, StrokeSharpness, StrokeStyle } from "@zsviczian/excalidraw/types/element/types";
|
||||
import { ExcalidrawElement, ExcalidrawLinearElement, ExcalidrawTextElement, FillStyle, GroupId, RoundnessType, StrokeStyle } from "@zsviczian/excalidraw/types/element/types";
|
||||
|
||||
export type PathCommand = {
|
||||
type: string;
|
||||
@@ -53,7 +53,7 @@ type _ExcalidrawElementBase = Readonly<{
|
||||
fillStyle: FillStyle;
|
||||
strokeWidth: number;
|
||||
strokeStyle: StrokeStyle;
|
||||
strokeSharpness: StrokeSharpness;
|
||||
roundness: null | { type: RoundnessType; value?: number };
|
||||
roughness: number;
|
||||
opacity: number;
|
||||
width: number;
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
import { getTransformMatrix, transformPoints } from "./transform";
|
||||
import { pointsOnPath } from "points-on-path";
|
||||
import { randomId, getWindingOrder } from "./utils";
|
||||
import { ROUNDNESS } from "../Constants";
|
||||
|
||||
const SUPPORTED_TAGS = [
|
||||
"svg",
|
||||
@@ -352,7 +353,7 @@ const walkers = {
|
||||
y: result[13],
|
||||
width: result[0],
|
||||
height: result[5],
|
||||
strokeSharpness: isRound ? "round" : "sharp",
|
||||
roundness: isRound ? {type:ROUNDNESS.LEGACY} : null,
|
||||
};
|
||||
|
||||
scene.elements.push(rect);
|
||||
|
||||
5
src/types.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
import { ExcalidrawBindableElement, ExcalidrawElement, ExcalidrawImageElement, FileId, FillStyle, NonDeletedExcalidrawElement, StrokeSharpness, StrokeStyle } from "@zsviczian/excalidraw/types/element/types";
|
||||
import { ExcalidrawBindableElement, ExcalidrawElement, ExcalidrawImageElement, FileId, FillStyle, NonDeletedExcalidrawElement, RoundnessType, StrokeRoundness, StrokeStyle } from "@zsviczian/excalidraw/types/element/types";
|
||||
import { Point } from "@zsviczian/excalidraw/types/types";
|
||||
import { TFile, WorkspaceLeaf } from "obsidian";
|
||||
import { EmbeddedFilesLoader } from "./EmbeddedFileLoader";
|
||||
@@ -28,7 +28,8 @@ export interface ExcalidrawAutomateInterface {
|
||||
strokeStyle: StrokeStyle; //type StrokeStyle = "solid" | "dashed" | "dotted"
|
||||
roughness: number;
|
||||
opacity: number;
|
||||
strokeSharpness: StrokeSharpness; //type StrokeSharpness = "round" | "sharp"
|
||||
strokeSharpness?: StrokeRoundness; //defaults to undefined, use strokeRoundess and roundess instead. Only kept for legacy script compatibility type StrokeRoundness = "round" | "sharp"
|
||||
roundness: null | { type: RoundnessType; value?: number };
|
||||
fontFamily: number; //1: Virgil, 2:Helvetica, 3:Cascadia, 4:LocalFont
|
||||
fontSize: number;
|
||||
textAlign: string; //"left"|"right"|"center"
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { normalizePath, Notice, TAbstractFile, TFile, TFolder, Vault } from "obsidian";
|
||||
import { DataURL } from "@zsviczian/excalidraw/types/types";
|
||||
import { normalizePath, Notice, requestUrl, RequestUrlResponse, TAbstractFile, TFile, TFolder, Vault } from "obsidian";
|
||||
import { URLFETCHTIMEOUT } from "src/Constants";
|
||||
import { MimeType } from "src/EmbeddedFileLoader";
|
||||
import { ExcalidrawSettings } from "src/Settings";
|
||||
import { errorlog, getDataURL } from "./Utils";
|
||||
|
||||
/**
|
||||
* Splits a full path including a folderpath and a filename into separate folderpath and filename components
|
||||
@@ -26,7 +30,7 @@ export function splitFolderAndFilename(filepath: string): {
|
||||
* @param data
|
||||
* @param filename
|
||||
*/
|
||||
export function download(encoding: string, data: any, filename: string) {
|
||||
export const download = (encoding: string, data: any, filename: string) => {
|
||||
const element = document.createElement("a");
|
||||
element.setAttribute("href", (encoding ? `${encoding},` : "") + data);
|
||||
element.setAttribute("download", filename);
|
||||
@@ -145,3 +149,41 @@ export async function checkAndCreateFolder(folderpath: string) {
|
||||
await vault.createFolder(folderpath);
|
||||
}
|
||||
|
||||
export const getURLImageExtension = (url: string):string => {
|
||||
const corelink = url.split("?")[0];
|
||||
return corelink.substring(corelink.lastIndexOf(".")+1);
|
||||
}
|
||||
|
||||
export const getMimeType = (extension: string):MimeType => {
|
||||
switch (extension) {
|
||||
case "png": return "image/png";
|
||||
case "jpeg": return "image/jpeg";
|
||||
case "jpg": return "image/jpeg";
|
||||
case "gif": return "image/gif";
|
||||
case "webp": return "image/webp";
|
||||
case "bmp": return "image/bmp";
|
||||
case "ico": return "image/x-icon";
|
||||
case "svg": return "image/svg+xml";
|
||||
case "md": return "image/svg+xml";
|
||||
default: return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
|
||||
const getFileFromURL = async (url: string, mimeType: MimeType, timeout: number = URLFETCHTIMEOUT):Promise<RequestUrlResponse> => {
|
||||
try {
|
||||
return await Promise.race([
|
||||
(async () => new Promise<RequestUrlResponse>((resolve) => setTimeout(()=>resolve(null), timeout)))(),
|
||||
requestUrl({url: url, method: "get", contentType: mimeType, throw: false })
|
||||
])
|
||||
} catch (e) {
|
||||
errorlog({where: getFileFromURL, message: `URL did not load within timeout period of ${timeout}ms`, url: url});
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export const getDataURLFromURL = async (url: string, mimeType: MimeType, timeout: number = URLFETCHTIMEOUT):Promise<DataURL> => {
|
||||
const response = await getFileFromURL(url, mimeType, timeout);
|
||||
return response && response.status === 200
|
||||
? await getDataURL(response.arrayBuffer, mimeType)
|
||||
: url as DataURL;
|
||||
}
|
||||
42
src/utils/Frontmatter.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
// alternative https://github.com/OPD-libs/OPD-libs
|
||||
|
||||
export default class FrontmatterEditor {
|
||||
private frontmatterStr:string;
|
||||
private dataWOfrontmatter: string;
|
||||
private initialized:boolean = false;
|
||||
|
||||
constructor (data:string) {
|
||||
this.dataWOfrontmatter = data;
|
||||
|
||||
data = data.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
|
||||
const tmp = data.split(/^---(?:.|\n)*(?:^---\n)/gm);
|
||||
if(tmp.length!==2) return;
|
||||
this.dataWOfrontmatter = tmp[1];
|
||||
this.frontmatterStr = data.match(/^---((?:.|\n)*)(?:^---\n)/gm)[0].replaceAll(/(^---\n|^\n)/gm,"").trim()+"\n";
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
public hasKey(key:string):boolean {
|
||||
if(!this.initialized) return false;
|
||||
const reg = new RegExp(`^${key}:`,"gm");
|
||||
return Boolean(this.frontmatterStr.match(reg));
|
||||
}
|
||||
|
||||
public setKey(key:string, value:string) {
|
||||
if(!this.initialized) return;
|
||||
value = value.replaceAll("\r\n", "\n").replaceAll("\r", "\n").replaceAll(":",";").trim().split("\n").join(" ");
|
||||
if(this.hasKey(key)) {
|
||||
const reg = new RegExp(`^${key}:.*\\n(?:\\s\\s.*\\n)*`,"gm");
|
||||
this.frontmatterStr =
|
||||
this.frontmatterStr.split(reg).join("\n").trim() +
|
||||
`\n${key}: ${value}`;
|
||||
return;
|
||||
}
|
||||
this.frontmatterStr = this.frontmatterStr.trim()+`\n${key}: ${value}`;
|
||||
}
|
||||
|
||||
get data() {
|
||||
if(!this.initialized) return this.dataWOfrontmatter;
|
||||
return ["---",this.frontmatterStr,"---",this.dataWOfrontmatter].join("\n");
|
||||
}
|
||||
}
|
||||
56
src/utils/ModifierkeyHelper.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { DEVICE, isDarwin } from "src/Constants";
|
||||
export type ModifierKeys = {shiftKey:boolean, ctrlKey: boolean, metaKey: boolean, altKey: boolean};
|
||||
export type KeyEvent = PointerEvent | MouseEvent | KeyboardEvent | React.DragEvent | React.PointerEvent | React.MouseEvent | ModifierKeys;
|
||||
export type PaneTarget = "active-pane"|"new-pane"|"popout-window"|"new-tab"|"md-properties";
|
||||
export type ExternalDragAction = "insert-link"|"image-url"|"image-import";
|
||||
export type InternalDragAction = "link"|"image"|"image-fullsize";
|
||||
|
||||
export const labelCTRL = () => DEVICE.isIOS || DEVICE.isMacOS ? "CMD" : "CTRL";
|
||||
export const labelALT = () => DEVICE.isIOS || DEVICE.isMacOS ? "OPT" : "ALT";
|
||||
export const labelMETA = () => DEVICE.isIOS || DEVICE.isMacOS ? "CTRL" : (DEVICE.isWindows ? "WIN" : "META");
|
||||
export const labelSHIFT = () => "SHIFT";
|
||||
|
||||
export const isCTRL = (e:KeyEvent) => DEVICE.isIOS || DEVICE.isMacOS ? e.metaKey : e.ctrlKey;
|
||||
export const isALT = (e:KeyEvent) => e.altKey;
|
||||
export const isMETA = (e:KeyEvent) => DEVICE.isIOS || DEVICE.isMacOS ? e.ctrlKey : e.metaKey;
|
||||
export const isSHIFT = (e:KeyEvent) => e.shiftKey;
|
||||
|
||||
export const mdPropModifier = (ev: KeyEvent): boolean => !isSHIFT(ev) && isCTRL(ev) && !isALT(ev) && isMETA(ev);
|
||||
export const scaleToFullsizeModifier = (ev: KeyEvent) =>
|
||||
( isSHIFT(ev) && !isCTRL(ev) && !isALT(ev) && isMETA(ev)) ||
|
||||
(!isSHIFT(ev) && isCTRL(ev) && isALT(ev) && !isMETA(ev));
|
||||
|
||||
export const linkClickModifierType = (ev: KeyEvent):PaneTarget => {
|
||||
if(isCTRL(ev) && !isALT(ev) && isSHIFT(ev) && !isMETA(ev)) return "active-pane";
|
||||
if(isCTRL(ev) && !isALT(ev) && !isSHIFT(ev) && !isMETA(ev)) return "new-tab";
|
||||
if(isCTRL(ev) && isALT(ev) && !isSHIFT(ev) && !isMETA(ev)) return "new-pane";
|
||||
if(DEVICE.isDesktop && isCTRL(ev) && isALT(ev) && isSHIFT(ev) && !isMETA(ev) ) return "popout-window";
|
||||
if(isCTRL(ev) && isALT(ev) && isSHIFT(ev) && !isMETA(ev)) return "new-tab";
|
||||
if(mdPropModifier(ev)) return "md-properties";
|
||||
return "active-pane";
|
||||
}
|
||||
|
||||
export const externalDragModifierType = (ev: KeyEvent):ExternalDragAction => {
|
||||
if(!isSHIFT(ev) && isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "insert-link";
|
||||
if(!isSHIFT(ev) && !isCTRL(ev) && !isALT(ev) && isMETA(ev)) return "insert-link";
|
||||
if( isSHIFT(ev) && !isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "image-import";
|
||||
if(!isSHIFT(ev) && !isCTRL(ev) && isALT(ev) && !isMETA(ev)) return "image-import";
|
||||
return "image-url";
|
||||
}
|
||||
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/468
|
||||
export const internalDragModifierType = (ev: KeyEvent):InternalDragAction => {
|
||||
if( isSHIFT(ev) && !isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "image";
|
||||
if(!isSHIFT(ev) && isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "image";
|
||||
if(scaleToFullsizeModifier(ev)) return "image-fullsize";
|
||||
return "link";
|
||||
}
|
||||
|
||||
export const emulateCTRLClickForLinks = (e:KeyEvent) => {
|
||||
return {
|
||||
shiftKey: e.shiftKey,
|
||||
ctrlKey: e.ctrlKey || !(DEVICE.isIOS || DEVICE.isMacOS),
|
||||
metaKey: e.metaKey || (DEVICE.isIOS || DEVICE.isMacOS),
|
||||
altKey: e.altKey
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,10 @@ import {
|
||||
App,
|
||||
normalizePath, Notice, WorkspaceLeaf
|
||||
} from "obsidian";
|
||||
import { DEVICE } from "src/Constants";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { checkAndCreateFolder, splitFolderAndFilename } from "./FileUtils";
|
||||
import { isALT, isCTRL, isMETA, isSHIFT, KeyEvent, linkClickModifierType, ModifierKeys } from "./ModifierkeyHelper";
|
||||
|
||||
export const getParentOfClass = (element: HTMLElement, cssClass: string):HTMLElement | null => {
|
||||
let parent = element.parentElement;
|
||||
@@ -19,6 +21,56 @@ export const getParentOfClass = (element: HTMLElement, cssClass: string):HTMLEle
|
||||
};
|
||||
|
||||
|
||||
|
||||
export const getLeaf = (
|
||||
plugin: ExcalidrawPlugin,
|
||||
origo: WorkspaceLeaf,
|
||||
ev: ModifierKeys
|
||||
) => {
|
||||
const newTab = ():WorkspaceLeaf => {
|
||||
if(!plugin.settings.openInMainWorkspace) return app.workspace.getLeaf('tab');
|
||||
const [leafLoc, mainLeavesIds] = getLeafLoc(origo);
|
||||
if(leafLoc === 'main') return app.workspace.getLeaf('tab');
|
||||
return getNewOrAdjacentLeaf(plugin,origo);
|
||||
}
|
||||
const newTabGroup = ():WorkspaceLeaf => getNewOrAdjacentLeaf(plugin,origo);
|
||||
const newWindow = ():WorkspaceLeaf => app.workspace.openPopoutLeaf();
|
||||
|
||||
switch(linkClickModifierType(ev)) {
|
||||
case "active-pane": return origo;
|
||||
case "new-tab": return newTab();
|
||||
case "new-pane": return newTabGroup();
|
||||
case "popout-window": return newWindow();
|
||||
default: return newTab();
|
||||
}
|
||||
}
|
||||
|
||||
const getLeafLoc = (leaf: WorkspaceLeaf): ["main" | "popout" | "left" | "right" | "hover",any] => {
|
||||
//@ts-ignore
|
||||
const leafId = leaf.id;
|
||||
const layout = app.workspace.getLayout();
|
||||
const getLeaves = (l:any)=> l.children
|
||||
.filter((c:any)=>c.type!=="leaf")
|
||||
.map((c:any)=>getLeaves(c))
|
||||
.flat()
|
||||
.concat(l.children.filter((c:any)=>c.type==="leaf").map((c:any)=>c.id))
|
||||
|
||||
const mainLeavesIds = getLeaves(layout.main);
|
||||
|
||||
return [
|
||||
layout.main && mainLeavesIds.contains(leafId)
|
||||
? "main"
|
||||
: layout.floating && getLeaves(layout.floating).contains(leafId)
|
||||
? "popout"
|
||||
: layout.left && getLeaves(layout.left).contains(leafId)
|
||||
? "left"
|
||||
: layout.right && getLeaves(layout.right).contains(leafId)
|
||||
? "right"
|
||||
: "hover",
|
||||
mainLeavesIds
|
||||
];
|
||||
}
|
||||
|
||||
/*
|
||||
| Setting | Originating Leaf |
|
||||
| | Main Workspace | Hover Editor | Popout Window |
|
||||
@@ -28,32 +80,11 @@ export const getParentOfClass = (element: HTMLElement, cssClass: string):HTMLEle
|
||||
| !InMain && InAdjacent | 1.1 Reuse Leaf in Main Workspace | 3 Reuse Leaf in Current Hover Editor | 4 Reuse Leaf in Current Popout |
|
||||
| !InMain && !InAdjacent | 1.2 New Leaf in Main Workspace | 2 New Leaf in Current Hover Editor | 2 New Leaf in Current Popout |
|
||||
*/
|
||||
|
||||
export const getNewOrAdjacentLeaf = (
|
||||
plugin: ExcalidrawPlugin,
|
||||
leaf: WorkspaceLeaf
|
||||
): WorkspaceLeaf => {
|
||||
//@ts-ignore
|
||||
const leafId = leaf.id;
|
||||
const layout = app.workspace.getLayout();
|
||||
const getLeaves = (l:any)=> l.children
|
||||
.filter((c:any)=>c.type!=="leaf")
|
||||
.map((c:any)=>getLeaves(c))
|
||||
.flat()
|
||||
.concat(l.children.filter((c:any)=>c.type==="leaf").map((c:any)=>c.id))
|
||||
|
||||
const mainLeavesIds = getLeaves(layout.main);
|
||||
|
||||
const leafLoc =
|
||||
layout.main && mainLeavesIds.contains(leafId)
|
||||
? "main"
|
||||
: layout.floating && getLeaves(layout.floating).contains(leafId)
|
||||
? "popout"
|
||||
: layout.left && getLeaves(layout.left).contains(leafId)
|
||||
? "left"
|
||||
: layout.right && getLeaves(layout.right).contains(leafId)
|
||||
? "right"
|
||||
: "hover";
|
||||
const [leafLoc, mainLeavesIds] = getLeafLoc(leaf);
|
||||
|
||||
const getMainLeaf = ():WorkspaceLeaf => {
|
||||
let mainLeaf = app.workspace.getMostRecentLeaf();
|
||||
|
||||
221
src/utils/Pens.ts
Normal file
@@ -0,0 +1,221 @@
|
||||
import { PenStyle, PenType } from "src/PenTypes";
|
||||
|
||||
export const PENS:Record<PenType,PenStyle> = {
|
||||
"default": {
|
||||
type: "default",
|
||||
freedrawOnly: false,
|
||||
strokeColor: "#000000",
|
||||
backgroundColor: "transparent",
|
||||
fillStyle: "hachure",
|
||||
strokeWidth: 0,
|
||||
roughness: 0,
|
||||
penOptions: {
|
||||
highlighter: false,
|
||||
constantPressure: false,
|
||||
hasOutline: false,
|
||||
outlineWidth: 1,
|
||||
options: {
|
||||
thinning: 0.6,
|
||||
smoothing: 0.5,
|
||||
streamline: 0.5,
|
||||
easing: "easeOutSine",
|
||||
start: {
|
||||
cap: true,
|
||||
taper: 0,
|
||||
easing: "linear"
|
||||
},
|
||||
end: {
|
||||
cap: true,
|
||||
taper: 0,
|
||||
easing: "linear"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"highlighter": {
|
||||
type: "highlighter",
|
||||
freedrawOnly: true,
|
||||
strokeColor: "#FFC47C",
|
||||
backgroundColor: "#FFC47C",
|
||||
fillStyle: "solid",
|
||||
strokeWidth: 2,
|
||||
roughness: null,
|
||||
penOptions: {
|
||||
highlighter: true,
|
||||
constantPressure: true,
|
||||
hasOutline: true,
|
||||
outlineWidth: 4,
|
||||
options: {
|
||||
thinning: 1,
|
||||
smoothing: 0.5,
|
||||
streamline: 0.5,
|
||||
easing: "linear",
|
||||
start: {
|
||||
taper: 0,
|
||||
cap: true,
|
||||
easing: "linear"
|
||||
},
|
||||
end: {
|
||||
taper: 0,
|
||||
cap: true,
|
||||
easing: "linear"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"finetip": {
|
||||
type: "finetip",
|
||||
freedrawOnly: false,
|
||||
strokeColor: "#3E6F8D",
|
||||
backgroundColor: "transparent",
|
||||
fillStyle: "hachure",
|
||||
strokeWidth: 0.5,
|
||||
roughness: 0,
|
||||
penOptions: {
|
||||
highlighter: false,
|
||||
hasOutline: false,
|
||||
outlineWidth: 1,
|
||||
constantPressure: true,
|
||||
options: {
|
||||
smoothing: 0.4,
|
||||
thinning: -0.5,
|
||||
streamline: 0.4,
|
||||
easing: "linear",
|
||||
start: {
|
||||
taper: 5,
|
||||
cap: false,
|
||||
easing: "linear"
|
||||
},
|
||||
end: {
|
||||
taper: 5,
|
||||
cap: false,
|
||||
easing:"linear"
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"fountain": {
|
||||
type: "fountain",
|
||||
freedrawOnly: false,
|
||||
strokeColor: "#000000",
|
||||
backgroundColor: "transparent",
|
||||
fillStyle: "hachure",
|
||||
strokeWidth: 2,
|
||||
roughness: 0,
|
||||
penOptions: {
|
||||
highlighter: false,
|
||||
constantPressure: false,
|
||||
hasOutline: false,
|
||||
outlineWidth: 1,
|
||||
options: {
|
||||
smoothing: 0.2,
|
||||
thinning: 0.6,
|
||||
streamline: 0.2,
|
||||
easing: "easeInOutSine",
|
||||
start: {
|
||||
taper: 150,
|
||||
cap: true,
|
||||
easing: "linear"
|
||||
},
|
||||
end: {
|
||||
taper: 1,
|
||||
cap: true,
|
||||
easing: "linear"
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"marker": {
|
||||
type: "marker",
|
||||
freedrawOnly: true,
|
||||
strokeColor: "#B83E3E",
|
||||
backgroundColor: "#FF7C7C",
|
||||
fillStyle: "dashed",
|
||||
strokeWidth: 2,
|
||||
roughness: 3,
|
||||
penOptions: {
|
||||
highlighter: false,
|
||||
constantPressure: true,
|
||||
hasOutline: true,
|
||||
outlineWidth: 4,
|
||||
options: {
|
||||
thinning: 1,
|
||||
smoothing: 0.5,
|
||||
streamline: 0.5,
|
||||
easing: "linear",
|
||||
start: {
|
||||
taper: 0,
|
||||
cap: true,
|
||||
easing: "linear"
|
||||
},
|
||||
end: {
|
||||
taper: 0,
|
||||
cap: true,
|
||||
easing: "linear"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"thick-thin": {
|
||||
type: "thick-thin",
|
||||
freedrawOnly: true,
|
||||
strokeColor: "#CECDCC",
|
||||
backgroundColor: "transparent",
|
||||
fillStyle: "hachure",
|
||||
strokeWidth: 0,
|
||||
roughness: null,
|
||||
penOptions: {
|
||||
highlighter: true,
|
||||
constantPressure: true,
|
||||
hasOutline: false,
|
||||
outlineWidth: 1,
|
||||
options: {
|
||||
thinning: 1,
|
||||
smoothing: 0.5,
|
||||
streamline: 0.5,
|
||||
easing: "linear",
|
||||
start: {
|
||||
taper: 0,
|
||||
cap: true,
|
||||
easing: "linear"
|
||||
},
|
||||
end: {
|
||||
cap: true,
|
||||
taper: true,
|
||||
easing: "linear",
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"thin-thick-thin": {
|
||||
type: "thin-thick-thin",
|
||||
freedrawOnly: true,
|
||||
strokeColor: "#CECDCC",
|
||||
backgroundColor: "transparent",
|
||||
fillStyle: "hachure",
|
||||
strokeWidth: 0,
|
||||
roughness: null,
|
||||
penOptions: {
|
||||
highlighter: true,
|
||||
constantPressure: true,
|
||||
hasOutline: false,
|
||||
outlineWidth: 1,
|
||||
options: {
|
||||
thinning: 1,
|
||||
smoothing: 0.5,
|
||||
streamline: 0.5,
|
||||
easing: "linear",
|
||||
start: {
|
||||
cap: true,
|
||||
taper: true,
|
||||
easing: "linear",
|
||||
},
|
||||
end: {
|
||||
cap: true,
|
||||
taper: true,
|
||||
easing: "linear",
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
App,
|
||||
Notice,
|
||||
request,
|
||||
requestUrl,
|
||||
TFile,
|
||||
} from "obsidian";
|
||||
import { Random } from "roughjs/bin/math";
|
||||
@@ -22,6 +23,8 @@ import { ExcalidrawElement } from "@zsviczian/excalidraw/types/element/types";
|
||||
import { ExportSettings } from "../ExcalidrawView";
|
||||
import { compressToBase64, decompressFromBase64 } from "lz-string";
|
||||
import { getIMGFilename } from "./FileUtils";
|
||||
import ExcalidrawScene from "../svgToExcalidraw/elements/ExcalidrawScene";
|
||||
import { IMAGE_TYPES } from "../Constants";
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
|
||||
@@ -85,7 +88,7 @@ const random = new Random(Date.now());
|
||||
export const randomInteger = () => Math.floor(random.next() * 2 ** 31);
|
||||
|
||||
//https://macromates.com/blog/2006/wrapping-text-with-regular-expressions/
|
||||
export function wrapText(
|
||||
export function wrapTextAtCharLength(
|
||||
text: string,
|
||||
lineLen: number,
|
||||
forceWrap: boolean = false,
|
||||
@@ -361,7 +364,10 @@ export const getImageSize = async (
|
||||
): Promise<{ height: number; width: number }> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.onload = () => resolve({ height: img.naturalHeight, width: img.naturalWidth });
|
||||
img.onload = () => {
|
||||
//console.log({ height: img.naturalHeight, width: img.naturalWidth, img});
|
||||
resolve({ height: img.naturalHeight, width: img.naturalWidth });
|
||||
};
|
||||
img.onerror = reject;
|
||||
img.src = src;
|
||||
});
|
||||
@@ -596,6 +602,7 @@ export const getEmbeddedFilenameParts = (fname:string):{
|
||||
filepath: string,
|
||||
hasBlockref: boolean,
|
||||
hasGroupref: boolean,
|
||||
hasTaskbone: boolean,
|
||||
hasArearef: boolean,
|
||||
blockref: string,
|
||||
hasSectionref: boolean,
|
||||
@@ -603,13 +610,14 @@ export const getEmbeddedFilenameParts = (fname:string):{
|
||||
linkpartReference: string,
|
||||
linkpartAlias: string
|
||||
} => {
|
||||
// 0 1 23 4 5 6 7 8 9
|
||||
const parts = fname?.match(/([^#\^]*)((#\^)(group=|area=)?([^\|]*)|(#)(group=|area=)?([^\^\|]*))(.*)/);
|
||||
// 0 1 23 4 5 6 7 8 9
|
||||
const parts = fname?.match(/([^#\^]*)((#\^)(group=|area=|taskbone)?([^\|]*)|(#)(group=|area=|taskbone)?([^\^\|]*))(.*)/);
|
||||
if(!parts) {
|
||||
return {
|
||||
filepath: fname,
|
||||
hasBlockref: false,
|
||||
hasGroupref: false,
|
||||
hasTaskbone: false,
|
||||
hasArearef: false,
|
||||
blockref: "",
|
||||
hasSectionref: false,
|
||||
@@ -622,6 +630,7 @@ export const getEmbeddedFilenameParts = (fname:string):{
|
||||
filepath: parts[1],
|
||||
hasBlockref: Boolean(parts[3]),
|
||||
hasGroupref: (parts[4]==="group=") || (parts[7]==="group="),
|
||||
hasTaskbone: (parts[4]==="taskbone") || (parts[7]==="taskbone"),
|
||||
hasArearef: (parts[4]==="area=") || (parts[7]==="area="),
|
||||
blockref: parts[5],
|
||||
hasSectionref: Boolean(parts[6]),
|
||||
@@ -649,3 +658,67 @@ export const awaitNextAnimationFrame = async () => new Promise(requestAnimationF
|
||||
export const log = console.log.bind(window.console);
|
||||
export const debug = console.log.bind(window.console);
|
||||
//export const debug = function(){};
|
||||
|
||||
|
||||
export const getContainerElement = (
|
||||
element:
|
||||
| (ExcalidrawElement & { containerId: ExcalidrawElement["id"] | null })
|
||||
| null,
|
||||
scene: ExcalidrawScene,
|
||||
) => {
|
||||
if (!element) {
|
||||
return null;
|
||||
}
|
||||
if (element.containerId) {
|
||||
return scene.elements.filter(el=>el.id === element.containerId)[0] ?? null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const updateFrontmatterInString = (data:string, keyValuePairs: [string,string][]):string => {
|
||||
if(!data) return data;
|
||||
for(const kvp of keyValuePairs) {
|
||||
const r = new RegExp(`${kvp[0]}:\\s.*\\n`,"g");
|
||||
data = data.match(r)
|
||||
? data.replaceAll(r,`${kvp[0]}: ${kvp[1]}\n`)
|
||||
: data.replace(/^---\n/,`---\n${kvp[0]}: ${kvp[1]}\n`);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
const isHyperlink = (link:string) => link && !link.includes("\n") && !link.includes("\r") && link.match(/^https?:(\d*)?\/\/[^\s]*$/);
|
||||
|
||||
export const hyperlinkIsImage = (data: string):boolean => {
|
||||
if(!isHyperlink(data)) false;
|
||||
const corelink = data.split("?")[0];
|
||||
return IMAGE_TYPES.contains(corelink.substring(corelink.lastIndexOf(".")+1));
|
||||
}
|
||||
|
||||
export const hyperlinkIsYouTubeLink = (link:string): boolean =>
|
||||
isHyperlink(link) &&
|
||||
(link.startsWith("https://youtu.be") || link.startsWith("https://www.youtube.com") || link.startsWith("https://youtube.com") || link.startsWith("https//www.youtu.be")) &&
|
||||
link.match(/(youtu.be\/|v=)([^?\/\&]*)/)!==null
|
||||
|
||||
export const getYouTubeThumbnailLink = async (youtubelink: string):Promise<string> => {
|
||||
//https://stackoverflow.com/questions/2068344/how-do-i-get-a-youtube-video-thumbnail-from-the-youtube-api
|
||||
//https://youtu.be/z8UkHGpykYU?t=60
|
||||
//https://www.youtube.com/watch?v=z8UkHGpykYU&ab_channel=VerbaltoVisual
|
||||
const parsed = youtubelink.match(/(youtu.be\/|v=)([^?\/\&]*)/);
|
||||
if(!parsed || !parsed[2]) return null;
|
||||
const videoId = parsed[2];
|
||||
|
||||
let url = `https://i.ytimg.com/vi/${videoId}/maxresdefault.jpg`;
|
||||
let response = await requestUrl({url, method: "get", contentType: "image/jpeg", throw: false });
|
||||
if(response && response.status === 200) return url;
|
||||
|
||||
url = `https://i.ytimg.com/vi/${videoId}/hq720.jpg`;
|
||||
response = await requestUrl({url, method: "get", contentType: "image/jpeg", throw: false });
|
||||
if(response && response.status === 200) return url;
|
||||
|
||||
url = `https://i.ytimg.com/vi/${videoId}/mqdefault.jpg`;
|
||||
response = await requestUrl({url, method: "get", contentType: "image/jpeg", throw: false });
|
||||
if(response && response.status === 200) return url;
|
||||
|
||||
|
||||
return `https://i.ytimg.com/vi/${videoId}/default.jpg`;
|
||||
}
|
||||
121
styles.css
@@ -67,6 +67,17 @@ button.ToolIcon_type_button[title="Export"] {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.excalidraw-prompt-button {
|
||||
width: 9em;
|
||||
}
|
||||
|
||||
.excalidraw-prompt-buttons-div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
li[data-testid] {
|
||||
border: 0 !important;
|
||||
margin: 0 !important;
|
||||
@@ -96,8 +107,7 @@ li[data-testid] {
|
||||
|
||||
.ex-coffee-div {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.excalidraw-scriptengine-install td>img {
|
||||
@@ -157,6 +167,7 @@ li[data-testid] {
|
||||
|
||||
.workspace-leaf-content .excalidraw-view {
|
||||
padding: 0px 1px; /*1px so on ipad swipe in from left and right still works*/
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.excalidraw-videoWrapper {
|
||||
@@ -184,9 +195,8 @@ li[data-testid] {
|
||||
}
|
||||
|
||||
.excalidraw-release .modal {
|
||||
max-height: 90%;
|
||||
width: auto;
|
||||
max-width: 130ch;
|
||||
max-height: 80%;
|
||||
max-width: 42em;
|
||||
}
|
||||
|
||||
.excalidraw .Island .scrollbar {
|
||||
@@ -225,6 +235,105 @@ textarea.excalidraw-wysiwyg {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.is-tablet .excalidraw button {
|
||||
.is-tablet .excalidraw button,
|
||||
.is-mobile .excalidraw button {
|
||||
padding: initial;
|
||||
height: 1.8rem;
|
||||
}
|
||||
|
||||
.excalidraw button,
|
||||
.ToolIcon button {
|
||||
box-shadow: none;
|
||||
justify-content: initial;
|
||||
}
|
||||
|
||||
.excalidraw {
|
||||
--default-button-size: 2rem !important;
|
||||
--default-icon-size: 1rem !important;
|
||||
--lg-button-size: 1.8rem !important;
|
||||
--lg-icon-size: 1rem !important;
|
||||
}
|
||||
|
||||
.excalidraw .tray-zoom {
|
||||
pointer-events: initial;
|
||||
padding-bottom: 0.05rem;
|
||||
padding-top: 0.05rem;
|
||||
}
|
||||
|
||||
.excalidraw-container.theme--dark {
|
||||
background-color: #121212;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* https://discordapp.com/channels/686053708261228577/989603365606531104/1041266507256184863 */
|
||||
/*.workspace-leaf {
|
||||
contain: none !important;
|
||||
}*/
|
||||
|
||||
.color-picker-content {
|
||||
overflow-y: auto;
|
||||
max-height: 10rem;
|
||||
}
|
||||
|
||||
.excalidraw .FixedSideContainer_side_top {
|
||||
top: 0.3rem;
|
||||
}
|
||||
|
||||
.excalidraw .ToolIcon__keybinding {
|
||||
font-size: 0.45rem !important;
|
||||
}
|
||||
|
||||
.Island > .Stack > .Stack {
|
||||
padding:0.2rem;
|
||||
}
|
||||
|
||||
label.color-input-container > input {
|
||||
max-width: 5rem;
|
||||
}
|
||||
|
||||
.excalidraw .FixedSideContainer_side_top {
|
||||
left: 10px !important;
|
||||
top: 10px !important;
|
||||
right: 10px !important;
|
||||
bottom: 10px !important;
|
||||
}
|
||||
|
||||
.excalidraw-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.excalidraw .panelColumn .buttonList {
|
||||
max-width: 13rem;
|
||||
}
|
||||
|
||||
.excalidraw button {
|
||||
width: initial;
|
||||
}
|
||||
|
||||
.excalidraw input[type="color"] {
|
||||
width: 1.65rem;
|
||||
height: 1.65rem;
|
||||
}
|
||||
|
||||
.excalidraw input[type="color"]::-webkit-color-swatch {
|
||||
height: 1.65rem;
|
||||
}
|
||||
|
||||
|
||||
.excalidraw input[type="color"]::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.excalidraw-settings input {
|
||||
min-width: 10em;
|
||||
}
|
||||
|
||||
div.excalidraw-draginfo {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
color: var(--text-normal);
|
||||
padding: 3px;
|
||||
background: var(--color-base-40);
|
||||
display: block;
|
||||
border-radius: 5px;
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
"baseUrl": ".",
|
||||
"sourceMap": true,
|
||||
"module": "es2015",
|
||||
"target": "es2017",
|
||||
"target": "es2017", //script engine requires for async execution
|
||||
"allowJs": true,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node",
|
||||
@@ -12,7 +12,7 @@
|
||||
"lib": [
|
||||
"dom",
|
||||
"scripthost",
|
||||
"es2017",
|
||||
"es2015",
|
||||
"esnext",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"lib": [
|
||||
"dom",
|
||||
"scripthost",
|
||||
"es2017",
|
||||
"es2015",
|
||||
"esnext",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"1.8.5": "1.0.0",
|
||||
"1.7.13": "0.15.6",
|
||||
"1.7.8": "0.15.5",
|
||||
"1.7.7": "0.15.4",
|
||||
|
||||