Compare commits

...

269 Commits

Author SHA1 Message Date
zsviczian
063bef92b9 Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-04-15 18:43:09 +02:00
zsviczian
2bf9156808 2.10.0 2025-04-15 18:43:02 +02:00
zsviczian
391363c419 Merge pull request #2318 from dmscode/master
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
es-scripts/readme.md at 768aebf
2025-04-13 06:21:50 +02:00
dmscode
85ae7f7bec ExcalidrawScriptsEngine.md to Chinese at 768aebf 2025-04-13 09:06:15 +08:00
dmscode
03718bc927 es-scripts/readme.md at 768aebf
Added Chinese translation for the script index page (es-scripts/readme_CN.md)

Updated the format of language switcher links in both Chinese and English documentation
2025-04-13 07:43:05 +08:00
zsviczian
768aebf5d2 2.10.0-beta-1
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-04-12 23:02:52 +02:00
zsviczian
9e31e74d15 Merge pull request #2315 from dmscode/master
Update zh-cn.ts to e793526
2025-04-12 22:47:55 +02:00
dmscode
2acc99d307 fix links 2025-04-08 10:44:30 +08:00
dmscode
20892f8541 Chinese docs at e793526
docs: resolve typos and add Chinese documentation

- Fixed multiple spelling errors (e.g. "nmuber" → "number")
- Removed a blank file (foo)
- Added Chinese version of documentation with cross-linking between readme.md of languages

[Note] Added a translator donation link in docs/zh-cn/docs/readme.md - please confirm if appropriate
2025-04-08 10:42:02 +08:00
dmscode
e0f96a2650 Update zh-cn.ts to e793526
Fixed multiple issues in zh-cn.ts file:

1. Removed redundant spaces
2. Standardized punctuation formats
3. Ensured consistent code style
4. Fixed several instances of repeated text symbols
The changes were focused on improving code readability and maintaining consistent formatting throughout the file.
2025-04-08 08:30:15 +08:00
zsviczian
e793526cb2 2.9.2
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-03-13 16:37:48 +01:00
zsviczian
d1eb4cae57 2.9.1 - emergency rawText fix
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-03-13 11:21:42 +01:00
zsviczian
f33fa33a08 2.9.0 2025-03-13 07:18:48 +01:00
zsviczian
858ae11c8b 2.9.0-beta-1, 0.18.0-1, fixed group references in live preview due to Obsidian 1.8.9 language changes
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-03-09 12:37:18 +01:00
zsviczian
99a7e74825 Updated rollup to work with React 19
Resolved mobile tabs not visible (hack) due to Obsidain 18.x changes
2025-03-09 11:29:19 +01:00
zsviczian
304cef4d7d Merge pull request #2223 from dmscode/master
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
Update zh-cn.ts to 1215266
2025-03-08 09:28:03 +01:00
稻米鼠
7dba9b88dc Merge branch 'zsviczian:master' into master 2025-02-05 07:46:30 +08:00
zsviczian
bfb5de1525 2.8.3
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-02-04 21:25:13 +01:00
zsviczian
8071a2888b 2.8.2
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-02-02 22:43:35 +01:00
zsviczian
8b9abb13d0 2.8.2-beta-1
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
2025-02-02 07:14:56 +01:00
zsviczian
6dbae61212 2.8.1
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-01-31 22:08:16 +01:00
zsviczian
75c65d61c5 Merge pull request #2229 from md42/master
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
fix grammar
2025-01-26 06:21:57 +01:00
Martin Dürrmeier
37e0de41af fix grammar
affect as a verb and effect as a noun
2025-01-26 00:31:54 +01:00
zsviczian
57f9e43508 fixed layerUIWrapper not available
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
2025-01-25 16:16:47 +01:00
zsviczian
3b7f931f28 Fix noticeEl in slideshow 2025-01-25 16:13:46 +01:00
zsviczian
b37a7aad4f update manifest-beta.json 2025-01-25 15:11:01 +01:00
zsviczian
667ab31ed9 publish slideshow
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
2025-01-25 13:06:27 +01:00
zsviczian
b15ddef7fe 2.8.0 2025-01-25 13:04:21 +01:00
zsviczian
ef785e5fb0 Merge pull request #2228 from Sintuz/master
Fixing a couple issues with LaTeX
2025-01-25 12:21:42 +01:00
zsviczian
15ba4146ac styling 2025-01-25 10:58:03 +01:00
Enrico Sintoni
9956fd1756 FIX: trim additional newline characters after LaTeX formula before saving it to the MD file 2025-01-25 01:23:06 +01:00
Enrico Sintoni
b6f2161f1c FIX: lines not being displayed in the array environment in LaTeX 2025-01-25 01:21:42 +01:00
dmscode
e6fca1a2d0 Update zh-cn.ts to a9cad8c 2025-01-25 08:09:25 +08:00
稻米鼠
a9cad8c9f1 Merge branch 'zsviczian:master' into master 2025-01-25 07:56:55 +08:00
zsviczian
22b8b1f707 Merge pull request #2226 from Sintuz/master
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
Allow custom location for LaTeX preamble file and small fix
2025-01-24 23:47:58 +01:00
zsviczian
98f6871caa 2.8.0-rc-2 2025-01-24 22:38:18 +01:00
Enrico Sintoni
aae588249a FIX: LaTeX rendering not working if % is put at the end of the preamble 2025-01-24 18:54:08 +01:00
Enrico Sintoni
ef890d51e3 Allow specifying a custom LaTeX preamble file location 2025-01-24 18:48:53 +01:00
zsviczian
b0bc03437a 2.8.0-rc-1, 0.17.6-28
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
2025-01-24 17:11:28 +01:00
zsviczian
23b94da8f0 update LOCALE to read from localStorage
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
2025-01-24 13:12:24 +01:00
zsviczian
1f227ddd24 Update manifest-beta.json 2025-01-24 07:24:50 +01:00
dmscode
01a88a25a2 Update zh-cn.ts to 1215266 2025-01-23 08:33:03 +08:00
zsviczian
12152665af 2.8.0-beta-5
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-01-22 23:55:10 +01:00
zsviczian
064e17b29d rebuilt export PDF using Electron PDF export 2025-01-22 23:10:42 +01:00
zsviczian
0aaba80c82 replace foreign object
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
2025-01-21 23:28:33 +01:00
zsviczian
1744668fbd 2.8.0-beta-4
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-01-20 23:02:58 +01:00
zsviczian
8e3e2ffb25 Merge pull request #2222 from zsviczian/imagepathhook
imagepath hook
2025-01-20 22:41:41 +01:00
zsviczian
f5475bfde6 imagepath hook 2025-01-20 22:40:27 +01:00
zsviczian
27fa270b42 Merge pull request #2221 from dmscode/master
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
Update zh-cn.ts to a796621
2025-01-20 07:31:38 +01:00
dmscode
15ece75b5d Update zh-cn.ts to a796621 2025-01-20 07:50:42 +08:00
zsviczian
a796621f93 2.8.0-beta-3, import pdf-lib on demand, fixed area hover preview, fixed local font in popout windows, fixed scrollable elements panel and context menu on (not only mobile)
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
2025-01-19 20:11:19 +01:00
zsviczian
3c943c6685 Merge pull request #2220 from dmscode/master
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
Update zh-cn.ts to 01e3921
2025-01-19 18:50:27 +01:00
zsviczian
4209774b4e 2.8.0-beta-2 2025-01-19 15:56:39 +01:00
zsviczian
b18637f7d0 pdf export fitToPage fix number of pages
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
2025-01-19 08:35:01 +01:00
dmscode
af8a848d14 Update zh-cn.ts to 01e3921 2025-01-19 15:27:16 +08:00
zsviczian
01e392158d fix png, jpg conversion and move from symbol to inline
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
2025-01-18 22:42:49 +01:00
zsviczian
fc47b7aa0d 2.8.0-beta-1, 0.17.6-27 2025-01-18 19:12:57 +01:00
zsviczian
a0e0627a49 Merge pull request #2219 from zsviczian/PDF-export
Pdf export
2025-01-18 18:59:50 +01:00
zsviczian
efcb0c0580 added ExcalidrawAutomate getPagePDFDimensions and createPDF 2025-01-18 18:56:21 +01:00
zsviczian
23d7105fb1 export dialog buttons 2025-01-18 18:16:49 +01:00
zsviczian
5d9565bd7c PDF export settings 2025-01-18 17:01:40 +01:00
zsviczian
59785523ae carved out PDFExportSettingsComponent 2025-01-18 15:46:27 +01:00
zsviczian
2a21ed5fc7 vertical positioning fixed 2025-01-18 15:13:14 +01:00
zsviczian
3d3ce73fa1 export to vault, added pdf settings 2025-01-18 13:20:00 +01:00
zsviczian
c35bd385fe extracted strings to language file 2025-01-18 12:18:56 +01:00
zsviczian
a790b04547 initial implementation 2025-01-18 11:37:58 +01:00
zsviczian
5171978c37 Merge pull request #2217 from zsviczian/PDF-fix
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
Pdf crop fix
2025-01-17 06:46:59 +01:00
zsviczian
ea4a0c91e8 added PDF samples for testing 2025-01-17 06:45:30 +01:00
zsviczian
34af6dd447 getPDFCropRect, getPDFRect improved 90,180,270 calc, still issue with page offsets 2025-01-15 23:22:35 +01:00
zsviczian
ed2e700946 Merge pull request #2215 from zsviczian/local-graph-embed-sync
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
Set local graph when embeddable is activated/deactivated #2200
2025-01-15 09:38:13 +01:00
zsviczian
7eb23ab5e1 Set local graph when embeddable is activated/deactivated #2200 2025-01-15 08:22:38 +00:00
zsviczian
7cccf1d4e2 2.7.6-beta-1
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
2025-01-14 22:57:14 +01:00
zsviczian
2a5545964c update package-lock.json, update packages, style.css to support new arrow picker 2025-01-14 22:15:28 +01:00
zsviczian
4ce22883cc Merge pull request #2214 from zsviczian/allow-new-image-formats
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
fix: allow jfif and avif
2025-01-14 16:01:01 +01:00
zsviczian
272804afc8 allow jfif and avif 2025-01-14 14:59:47 +00:00
zsviczian
dc0b50f717 Update How-to.yml
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-01-09 08:52:41 +01:00
zsviczian
a0eb625b8a Update How-to.yml 2025-01-09 08:52:11 +01:00
zsviczian
524dc54d03 Update bug_report.yml 2025-01-09 08:50:35 +01:00
zsviczian
918718be90 Update bug_report.yml 2025-01-09 08:49:59 +01:00
zsviczian
78ee784be1 Update bug_report.yml 2025-01-09 08:48:58 +01:00
zsviczian
7e0e016bf9 Update bug_report.yml 2025-01-09 08:48:18 +01:00
zsviczian
4f875a03a0 2.7.5
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-01-05 23:09:16 +01:00
zsviczian
63c56e0e98 similar elements allows selection of containers
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
2025-01-05 12:49:11 +01:00
zsviczian
46477208be split ellipse and concatenate line now works with rotated lines
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
2025-01-05 11:38:45 +01:00
zsviczian
3194c014c7 updated concatenate lines 2025-01-05 10:33:31 +01:00
zsviczian
25ccb9dc43 updated EA lib in docs 2025-01-05 07:03:04 +01:00
zsviczian
fa46f8c39d Merge pull request #1880 from karaolidis/package-lock
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
Allow automated & deterministic builds
2025-01-04 22:18:09 +01:00
zsviczian
8ffe5c3942 2.7.5-beta-1 2025-01-04 21:26:49 +01:00
zsviczian
88f256cd8f Added comments to EA, moved non-class function to EAUtils 2025-01-04 20:23:14 +01:00
zsviczian
1562600cd3 Image mask offset, PDF drift and offset, addAppendUpdateCustomData on EA, some type cleanup
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
2025-01-04 19:06:43 +01:00
Nikolaos Karaolidis
d759abbc47 Allow commiting package-lock.json
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-01-04 18:38:56 +02:00
zsviczian
90533138e5 fixed embedding images into Excalidraw with areaRef links did not work as expected due to conflicting width and height values
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
2024-12-31 08:37:46 +01:00
zsviczian
80d8f0e5b6 image occlusion, full-year calendar 2024-12-29 10:52:54 +01:00
zsviczian
9829fab97c Merge pull request #2185 from simonperet/full-year-calendar-script
Add full year calendar generator script
2024-12-29 10:48:30 +01:00
Simon
a33c8b6eab Add a full year calendar generator script 2024-12-29 09:21:44 +01:00
zsviczian
f0921856c1 fix 2184 2024-12-29 07:15:47 +01:00
zsviczian
31e06ac0e0 updated add connector point and set stroke width 2024-12-28 22:19:47 +01:00
zsviczian
033d764b1c 2.7.4 2024-12-28 20:04:22 +01:00
zsviczian
00f98dd14e fix color picker in shade master 2024-12-28 11:14:27 +01:00
zsviczian
0f601d969a updated the script 2024-12-27 22:03:50 +01:00
zsviczian
8fa0fb37b2 shade master mobile friendly(er) 2024-12-27 21:35:57 +01:00
zsviczian
5a58d17d99 2.7.3 2024-12-27 21:04:33 +01:00
zsviczian
982958a4c6 Merge pull request #2179 from dmscode/master
Update zh-cn.ts to d3b61a0
2024-12-27 19:35:52 +01:00
dmscode
d425884bb8 Update zh-cn.ts to d3b61a0 2024-12-27 19:26:02 +08:00
zsviczian
d3b61a0df1 shade master icon corrected 2024-12-27 09:18:12 +01:00
zsviczian
4bab0162ba EA colorMap functions, new duplicate command, minor fileloader fixes, view.loadSceneFiles callback and filewhitelist, publish shade master 1.0 2024-12-27 08:05:41 +01:00
zsviczian
d3f4437478 shade master and new duplicate image command 2024-12-26 23:38:29 +01:00
zsviczian
a64586c3e6 shade master fully functional 2024-12-25 20:44:15 +01:00
zsviczian
7a92e78851 added queue for svg update 2024-12-25 10:10:44 +01:00
zsviczian
af0122b21a store original colors 2024-12-25 01:05:38 +01:00
zsviczian
1f95f57e97 queue and sliders 2024-12-25 00:31:24 +01:00
zsviczian
f384e95e44 modal is draggable 2024-12-24 22:51:52 +01:00
zsviczian
a40521f07b Shade Master v2 2024-12-24 22:36:08 +01:00
zsviczian
9649b36175 shade master v1 2024-12-24 20:05:16 +01:00
zsviczian
6cb1394793 Merge pull request #2176 from dmscode/master
Update zh-cn.ts to 22d3f25
2024-12-24 08:27:19 +01:00
dmscode
e5b2977c0c Update zh-cn.ts to 22d3f25
And remove spaces from line end
2024-12-24 08:18:04 +08:00
zsviczian
22d3f25dc4 renderingConcurrency; createSliderWithText 2024-12-23 21:15:06 +01:00
zsviczian
d9534fcc4f Fixed: toggleImageAnchoring 2024-12-23 20:04:33 +01:00
zsviczian
fd1604c3a4 slideshow script will now remember last slide on multiple presentations within the same session when starting slideshow holding down the SHIFT Modifier key 2024-12-23 08:56:06 +01:00
zsviczian
8f0f8d64df colors to lower case 2024-12-22 10:33:43 +01:00
zsviczian
5a413ab910 2.7.2 2024-12-21 10:15:33 +01:00
zsviczian
d3133f055c updated deconstruct selected elements script 2024-12-21 09:31:26 +01:00
zsviczian
fe05518e31 resolve minify iOS 15/16 compatibility issue 2024-12-20 22:36:37 +01:00
zsviczian
8adcb7d850 pdfjs rendering race condition 2024-12-20 20:08:55 +01:00
zsviczian
be383f2b48 moved Drop handlers to DropManager, added await to page.getViewport as there seems to be a race condition impacting page.render() 2024-12-20 19:32:37 +01:00
zsviczian
682307b51d Merge pull request #2169 from zsviczian/2.7.2-casing
2.7.2 file and folder name casing + empty line before ## Text Elements
2024-12-20 14:14:14 +01:00
zsviczian
60328613ea empty line before ## Text Elements 2024-12-20 13:12:36 +00:00
zsviczian
4a2e054ac6 cleaned up filename and folder letter-cases 2024-12-20 12:59:17 +00:00
zsviczian
eebc428f1b major file reorganization 2024-12-19 22:29:51 +01:00
zsviczian
ab8ba66eb5 Merge pull request #2166 from dmscode/master
Update zh-cn.ts to 6733f76
2024-12-19 15:09:09 +01:00
dmscode
97b3050270 Update zh-cn.ts to 6733f76
Please don't use auto formater in language file, now, in zh-cn.ts, every items have the same line-number with en.ts, for easy to find and change.
2024-12-19 09:44:26 +08:00
zsviczian
6733f76fbf 2.7.1 2024-12-18 21:59:46 +01:00
zsviczian
1dcc45585d Merge pull request #2165 from zsviczian/2.7.1-beta-1
fixed unescape, decodeURIComponent issue with non-latin characters.
2024-12-18 17:58:45 +01:00
zsviczian
0c5ceaa3f7 fixed unescape, decodeURIComponent issue with non-latin characters.
exitFullscreen when closing the view.
2024-12-18 10:22:27 +00:00
zsviczian
2e602d49a2 Merge pull request #2163 from hackerESQ/master
Allow poorly formated .excalidraw files to render
2024-12-18 10:10:08 +01:00
hackerESQ
84bcdf8bee Merge pull request #1 from hackerESQ/enable-poorly-formatted-json-legacy-mode
Allow poorly formated .excalidraw files to render
2024-12-17 22:07:26 -06:00
hackerESQ
6d60bcf6eb Allow poorly formated .excalidraw files to render
There are several excalidraw implementations which fail to follow the defined excalidraw JSON schema. This allows those poorly formatted files to render in legacy mode in Obsidian.
2024-12-17 22:06:54 -06:00
zsviczian
b832a51a5b Merge pull request #2160 from zsviczian/2.7.0-bugs
Fix ROOTELEMENTSIZE calc, view switch timestamp logic, update global app refs, minify packages, fix FileManager init, add 2.7.0 notes
2024-12-17 18:01:02 +01:00
zsviczian
dd4c07cbf9 ROOTELEMENTSIZE calculation based on overrideObsidianFontSize 2024-12-17 16:51:12 +00:00
zsviczian
6a86de3e1e splitViewLeafSwitchTimestamp should only be set if switching from markdown to Excalidraw view 2024-12-17 16:31:39 +00:00
zsviczian
ff8c649c6a isRecentSplitViewSwitch 2024-12-17 16:03:55 +00:00
zsviczian
ae34e124a7 updated remaining instances of reference to global app 2024-12-17 12:40:10 +00:00
zsviczian
5d084ffc30 minify react and excalidraw, fix filemanager instantiation, added 2.7.0 release notes 2024-12-17 10:34:59 +00:00
zsviczian
b0a9cf848e 2.7.0-beta-7 (0.17.6-22) Modified rollup to load packages in new Function instead of window.eval 2024-12-16 20:56:47 +01:00
zsviczian
37e06efa43 fixed packageLoader for popout windows, getSharedMermaidInstance (load mermaid) 2024-12-16 18:53:39 +01:00
zsviczian
3a6ad7d762 2.7.0-beta-6 (language compress) 2024-12-15 19:26:10 +01:00
zsviczian
2846b358f4 EventManager and improved type safety (removed //@ts-ignore 2024-12-15 15:28:10 +01:00
zsviczian
8b3c22cc7f Carved out CommandManager from main.ts 2024-12-15 07:48:38 +01:00
zsviczian
ee7fc3eddd 2.7.0-beta-5 Cleaned up FileManager, ObserverManager and PackageManager carveout 2024-12-14 23:04:16 +01:00
zsviczian
639ccdf83e Package Manager 2024-12-14 15:38:48 +01:00
zsviczian
2b901c473b Moved observers to OberverManager 2024-12-14 15:04:07 +01:00
zsviczian
b419079734 refactoring: filemanager, types moved to types 2024-12-14 14:30:08 +01:00
zsviczian
5c4d37cce4 2.7.0-beta-4 2024-12-14 13:13:42 +01:00
zsviczian
7b5f701f8f Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2024-12-14 09:55:03 +01:00
zsviczian
0eca97bf18 fixed scene reload on embeddable edit causing edit mode to be interrupted. fixed LaTeX.ts race condition. 2024-12-14 09:54:58 +01:00
zsviczian
f620263fc6 Merge pull request #2155 from dmscode/master
Update zh-cn.ts to b8655cf
2024-12-14 07:20:47 +01:00
dmscode
4e299677bd Update zh-cn.ts to b8655cf 2024-12-14 07:40:38 +08:00
zsviczian
b8655cff5e 2.7.0-beta-3 embeddable debugging 2024-12-13 23:07:29 +01:00
zsviczian
be452fee6d moved mathjax to a separate module and zip it in main.js 2024-12-13 20:02:28 +01:00
zsviczian
90589dd075 2.7.0-beta-2, 0.17.6-20 fixed mermaid race condition and settings save on startup 2024-12-12 22:46:43 +01:00
zsviczian
9c5b48c037 restructured onload 2024-12-12 11:47:03 +01:00
zsviczian
4406709920 fixed onceOffGPTVersionReset 2024-12-12 11:38:17 +01:00
zsviczian
b7ba0f8909 2.7.0-beta-1 2024-12-10 22:22:59 +01:00
zsviczian
c28911c739 Merge pull request #2144 from dmscode/master
Update zh-cn.ts to 9e1d491
2024-12-10 20:15:37 +01:00
dmscode
28088754ad Update zh-cn.ts to 9e1d491 2024-12-09 08:06:00 +08:00
zsviczian
9e1d491981 2.6.8 2024-12-08 16:09:00 +01:00
zsviczian
ab5caa4877 Merge pull request #2142 from TrillStones/master
[Script Contribution] [ Update] Image Occlusion
2024-12-08 15:56:47 +01:00
trillstones
44b580ae78 add image for image-occlusion script 2024-12-08 19:26:50 +08:00
trillstones
3859eddc80 add new image for image-occlusion script 2024-12-08 19:01:13 +08:00
trillstones
6098e1b42e add setting - Generate Images No Matter What
change card's and folder's naming logic
2024-12-08 17:29:00 +08:00
zsviczian
6ad8d2f620 2.6.8 - before field suggester implementation 2024-12-08 07:00:11 +01:00
zsviczian
5b3f3a56ad 2.6.8-beta-3, 0.17.6-17 2024-12-07 22:32:10 +01:00
zsviczian
f746b4f4ac Merge pull request #2140 from TrillStones/master
[Script Contribution] Image Occlusion
2024-12-07 21:29:55 +01:00
trillstones
3e4a3ace56 Update index-new.md for image occlusion script 2024-12-07 11:54:14 +08:00
trillstones
c72f6add40 Update index-new.md for image occlusion script 2024-12-07 11:45:21 +08:00
trillstones
6cfb125a38 Update index-new.md for image occlusion script 2024-12-07 11:44:27 +08:00
trillstones
c91e57e341 add image for Image-occlusion 2024-12-07 11:36:26 +08:00
trillstones
0ddd75e5fe Add Image Occlusion Script 2024-12-07 11:10:04 +08:00
zsviczian
382d4ca827 2.6.8-beta-2, Dynamic caret color based on text background 2024-12-01 16:32:26 +01:00
zsviczian
198e8f8cb7 2.6.8-beta-1 - settings loading is async, added detailed load timestamps ea.printStartupBreakdown(), delayed settings load 2024-12-01 11:28:05 +01:00
zsviczian
d3baa74ce7 Register ribbon icon during onLoad 2024-12-01 06:45:32 +01:00
zsviczian
995bfe962e 2.6.7, 0.17.6-14 2024-11-10 14:32:34 +01:00
zsviczian
59255fd954 2.6.6 2024-11-07 21:03:05 +01:00
zsviczian
1e9bed9192 Merge pull request #2101 from dmscode/master
Update zh-cn.ts to b0d3976
2024-11-05 07:42:13 +01:00
dmscode
a747a6f698 Update zh-cn.ts to b0d3976 2024-11-05 08:23:26 +08:00
zsviczian
b0d3976c27 2.6.5 2024-11-04 23:44:13 +01:00
zsviczian
7f77ab0743 2.6.5-beta-1 2024-11-04 19:11:32 +01:00
zsviczian
79da8afa0b Merge pull request #2099 from zsviczian/fix-textwrap-script-engine
fix script loading error
2024-11-04 13:30:19 +01:00
zsviczian
bb83523c0f fix script loading error 2024-11-04 12:29:20 +00:00
zsviczian
f83c0a8458 Merge pull request #2097 from dmscode/master
Update zh-cn.ts to 55ce645
2024-11-04 07:58:33 +01:00
dmscode
7411d51477 Update zh-cn.ts to 55ce645 2024-11-04 07:39:41 +08:00
zsviczian
55ce6456d8 2.6.4 2024-11-03 17:56:39 +01:00
zsviczian
da6619d55e 2.6.3 2024-11-03 15:07:11 +01:00
zsviczian
6033c057c2 2.6.3-beta-6 2024-11-03 13:32:18 +01:00
zsviczian
0efda1d6a6 2.6.3-beta-5 2024-11-03 00:54:38 +01:00
zsviczian
59107f0c2a 2.6.3-beta-4 2024-11-02 20:05:13 +01:00
zsviczian
f7cd05f6c4 2.6.3-beta-3 (refactored initiation) 2024-11-02 07:50:27 +01:00
zsviczian
5cbd98e543 Merge pull request #2092 from dmscode/master
Update zh-cn.ts  to dec2909
2024-11-02 07:45:49 +01:00
dmscode
e2d5966ca3 Update zh-cn.ts to dec2909 2024-11-01 18:37:48 +08:00
zsviczian
dec2909db0 2.6.2-beta-2, 0.17.6-10 PDFCropping 2024-11-01 07:44:58 +01:00
zsviczian
7233d1e037 2.6.3-beta-1 2024-10-30 23:02:05 +01:00
zsviczian
5972f83369 Merge pull request #2083 from dmscode/master
Update zh-cn.ts to 8f14f97
2024-10-30 22:08:28 +01:00
dmscode
0edfd7622c Update zh-cn.ts to 8f14f97 2024-10-29 07:36:30 +08:00
zsviczian
8f14f97007 2.6.2 2024-10-28 22:12:25 +01:00
zsviczian
758585a4c2 2.6.1 2024-10-28 20:26:57 +01:00
zsviczian
854eafaf91 2.6.0 2024-10-27 15:57:25 +01:00
zsviczian
ee89b80ce1 Merge pull request #2079 from dmscode/master
Update zh-cn.ts to ee9364b
2024-10-27 07:12:14 +01:00
dmscode
3e6200ac7e Update zh-cn.ts to ee9364b 2024-10-27 06:37:38 +08:00
zsviczian
ee9364b645 2.6.0-beta-4 2024-10-26 14:41:10 +02:00
zsviczian
5bbe66900e 2.6.0-beta-3 2024-10-26 13:53:08 +02:00
zsviczian
a775a858c7 2.6.0-beta-2 2024-10-26 08:39:28 +02:00
zsviczian
2dab801ff5 Merge pull request #2078 from dmscode/master
Update zh-cn.ts to 91be6e2
2024-10-26 08:11:17 +02:00
dmscode
07f8a87580 Update zh-cn.ts to 91be6e2 2024-10-26 07:41:49 +08:00
zsviczian
91be6e2a2f local cjk fonts 2024-10-25 23:54:01 +02:00
zsviczian
5c709588dd 2.6.0-beta-1, 0.17.6-6, embedded file loader batching 2024-10-23 22:23:24 +02:00
zsviczian
19a46e5b11 2.3.5-beta-5 2024-10-23 06:43:56 +02:00
zsviczian
e132d4a9fc 2.5.3-beta-4 improved loading speeds, image cropping 2024-10-22 20:44:17 +02:00
zsviczian
cf2d9bea24 2.5.3-beta-3 2024-10-21 21:17:44 +02:00
zsviczian
09cbffed1e 2.5.3-beta-2 2024-10-20 21:38:33 +02:00
zsviczian
368de8c1f4 Merge pull request #2070 from tovBender/master
Update ru.ts
2024-10-20 21:21:20 +02:00
zsviczian
7dcf9173c2 Merge pull request #2071 from dmscode/master
Update zh-cn.ts to 7cac94b
2024-10-20 21:19:32 +02:00
dmscode
eac312c3a2 Update zh-cn.ts to 7cac94b 2024-10-20 10:11:58 +08:00
tovBender
7a420a9d2d Update ru.ts 2024-10-19 22:18:52 +03:00
zsviczian
7cac94bf2f 2.5.3-beta-1 2024-10-19 20:43:48 +02:00
zsviczian
43e98db174 remove font assets, replace with zip 2024-10-19 16:21:20 +02:00
zsviczian
253575bf23 font assets 2024-10-19 16:11:24 +02:00
zsviczian
7a08ced65a Merge pull request #2066 from heinrich26/master
Make the Tab Icons color change as well, if a Tab is dirty (unsaved)
2024-10-19 06:40:18 +02:00
Hendrik Horstmann
5a64e1c75e Merge branch 'zsviczian:master' into master 2024-10-16 19:28:22 +02:00
zsviczian
fc0ac92dd3 2.5.2 2024-10-13 20:59:43 +02:00
zsviczian
4e2d7eb637 Update README.md 2024-09-28 22:32:18 +02:00
zsviczian
f8f280c7d5 2.5.1 2024-09-28 14:10:25 +02:00
zsviczian
00b87f99c0 Merge pull request #2038 from mxsdlr/master
Update color palette details in README
2024-09-25 17:14:56 +02:00
mxsdlr
0b5c74dde8 Add Decompress JSON hint to README.md
- Add hint to "Decompress Excalidraw JSON in Markdown View" setting when editing JSON content
2024-09-24 14:33:10 +02:00
mxsdlr
906b3bdf92 Update color palette details in README
- Change `customColorPalette` to `colorPalette`
- Add section about `topPicks`
2024-09-24 11:43:29 +02:00
zsviczian
0c28e82212 2.5.1-beta-2 2024-09-22 16:22:17 +02:00
zsviczian
beb4301f14 Merge pull request #2033 from dmscode/master
Update zh-cn.ts to 268680f
2024-09-22 15:21:11 +02:00
dmscode
e96fe9c491 Update zh-cn.ts to 268680f 2024-09-22 07:18:10 +08:00
zsviczian
268680f494 2.5.1-beta-1 2024-09-19 20:22:04 +02:00
zsviczian
a1512fce26 Merge pull request #2024 from dmscode/master
Update zh-cn.ts to 74c0af2
2024-09-19 20:08:58 +02:00
zsviczian
c2e79f3439 Update bug_report.yml 2024-09-14 11:25:36 +02:00
zsviczian
01780a2bf8 Update bug_report.yml 2024-09-14 11:24:44 +02:00
zsviczian
10e54eb03e Update bug_report.yml 2024-09-14 11:24:01 +02:00
zsviczian
2760a9966b Update bug_report.yml 2024-09-14 11:23:40 +02:00
dmscode
a297dbbe52 Update zh-cn.ts to 74c0af2
And fixed some link path
2024-09-14 08:09:10 +08:00
zsviczian
74c0af2032 2.5.0 2024-09-13 18:45:13 +02:00
zsviczian
813c85accd 2.5.0-rc-1 2024-09-13 08:18:19 +02:00
zsviczian
c97d08c997 Merge pull request #2017 from zsviczian/fix-getExcalidrawViews
fixed getExcalidrawView
2024-09-12 12:29:49 +02:00
zsviczian
097d1bcd1b fixed getExcalidrawView 2024-09-12 10:27:04 +00:00
zsviczian
7c91186ed5 Update ExcalidrawViewUtils.ts 2024-09-12 11:41:47 +02:00
zsviczian
904bc7c994 Update ExcalidrawView.ts 2024-09-12 11:39:25 +02:00
zsviczian
9fd4ae2615 Update manifest-beta.json 2024-09-12 11:16:51 +02:00
zsviczian
18fbb0934e Merge pull request #2016 from zsviczian/style-tweaks
Obsidian 1.7.2: rework getExcalidrawViews from leaves, fix tools pane…
2024-09-12 11:10:11 +02:00
zsviczian
3ae59c85d2 Obsidian 1.7.2: rework getExcalidrawViews from leaves, fix tools panel width, fix math types 2024-09-12 09:07:43 +00:00
zsviczian
55db9b0ddb 2.5.0-beta-3 2024-09-11 22:21:50 +02:00
zsviczian
3cca2fedb0 2.5.0-beta-2 2024-09-10 18:56:38 +02:00
zsviczian
a0682b8e3c Merge pull request #2014 from dmscode/master
Update zh-cn.ts to 2.4.3
2024-09-10 07:38:37 +02:00
dmscode
da19ce1b84 Update zh-cn.ts to 2.4.3
Add a folder in ./docs for more Chinese docs, and move zh-cn README into it. Translated AutomateHowTo.md
2024-09-10 07:54:46 +08:00
zsviczian
2b1f504b5b 2.5.0-beta-1, changed to Excalidraw.com search 2024-09-09 23:03:57 +02:00
zsviczian
d8f2776b40 Improved template load performance by filtering fileIds based on selection. 2024-09-09 21:27:39 +02:00
zsviczian
2fd800e0a0 2.4.3 2024-09-09 20:20:12 +02:00
zsviczian
712402f7b1 2.4.3-rc-2 2024-09-09 20:00:39 +02:00
zsviczian
b5c034ed92 2.4.3-rc-1 2024-09-08 21:16:11 +02:00
zsviczian
d2805e7a8a Rename feature_request.md to feature_request.md.old 2024-09-07 07:20:34 +02:00
zsviczian
4011b1c89a Rename feature-request-yml to feature-request.yml 2024-09-07 07:20:10 +02:00
zsviczian
5bc348611c Create feature-request-yml 2024-09-07 07:19:42 +02:00
zsviczian
dd24affdbb Create How-to.yml 2024-09-07 06:51:15 +02:00
zsviczian
d8542824a1 removed autodraw for pen and hardware eraser support scripts 2024-09-06 22:11:10 +02:00
zsviczian
c6bf24ed60 2.4.2 2024-09-06 19:33:16 +02:00
Hendrik Horstmann
a5771625df Make the Tab Icons color change as well, if a Tab is dirty (unsaved) 2024-05-22 16:17:30 +02:00
201 changed files with 55310 additions and 20895 deletions

77
.github/ISSUE_TEMPLATE/How-to.yml vendored Normal file
View File

@@ -0,0 +1,77 @@
name: How to? Support request
description: Ask for help with using the plugin or understanding its features.
title: "SUPPORT: "
body:
- type: markdown
attributes:
value: |
**Important: Please Read Before Submitting a Support Request**
I am a one-person team working on this plugin as a part-time hobby. Please help me manage the workload by following these guidelines. **Support requests that don't include enough details may be closed without review**.
Before submitting a support request, please:
1. **Review the [documentation](https://github.com/zsviczian/obsidian-excalidraw-plugin/wiki)** your question may already be answered.
2. **[Search issues](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues) (including closed ones)** to see if your question has already been addressed.
3. **[Watch the Feature Walkthrough Video](https://youtu.be/P_Q6avJGoWI)**: As it infact answers 90% of the typical questions I receive
4. **[Consult NotebookLM with your question](https://excalidraw-obsidian.online/WIKI/09+Video+Transcripts/Videos/Turn+any+YouTube+Channel+into+your+AI+Mentor+-+Obsidian+is+the+ultimate+automation+workbench+for+PKM)**
- type: markdown
attributes:
value: |
Please help by providing the following details. Requests without this information may be closed without review.
- type: markdown
attributes:
value: |
---
- type: checkboxes
id: search_existing_resources
attributes:
label: Have you checked the documentation and searched existing issues?
description: Please confirm that you've reviewed available resources before submitting your request.
options:
- label: Yes, I have reviewed the documentation and searched for related issues.
- type: textarea
id: notebook_lm
attributes:
label: "Your NotebookLM query"
description: "See point 4) above. Paste the question and answer you received from NotebookLM. This serves partly as proof, partly to help me see where the model is incorrect"
placeholder: "Copy/Paste your question and the resulting answer you got from NotebookLM"
- type: textarea
id: support_question
attributes:
label: "Your question"
description: "Provide a clear and concise description of the question or issue you need help with."
placeholder: "Describe your question or the problem you are trying to solve..."
- type: textarea
id: steps_tried
attributes:
label: "Steps you've already tried"
description: "List any steps you've taken to try to resolve or understand the issue."
placeholder: |
1. Tried reading the documentation.
2. Searched the issues for similar questions.
- type: textarea
id: expected_outcome
attributes:
label: "Expected outcome"
description: "Describe what you expected to happen or what you are trying to achieve."
placeholder: "Describe the result you are aiming for..."
- type: textarea
id: additional_context
attributes:
label: "Additional context"
description: "Add any other context or details that may help."
placeholder: "Include any other information that may be relevant..."
- type: markdown
attributes:
value: |
**Attachments: Screenshots and files are critical!**
A picture speaks a thousand words, and a screen recording even more. **Please attach screenshots, screen recordings, or sample files** to help illustrate your question or issue. Drag and drop them into the comment area or directly into any of the text fields above.

View File

@@ -1,5 +1,5 @@
name: Bug report
description: When something is clearly broken. Everything else is a feature request.
description: If something is clearly broken, its a bug. **Everything else** is a feature or support request. Most reported “bugs” are actually how-to questions or feature requests.
title: "BUG: "
body:
- type: markdown
@@ -12,6 +12,8 @@ body:
Before creating a bug report, please:
1. **Review recent [release notes](https://github.com/zsviczian/obsidian-excalidraw-plugin/releases)** maybe there is already an answer.
2. **[Search issues](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues) (including closed ones)** to see if there is anything similar.
3. **[Watch the Feature Walkthrough Video](https://youtu.be/P_Q6avJGoWI)**: As it infact answers 90% of the typical questions I receive
4. **[Consult NotebookLM with your question](https://excalidraw-obsidian.online/WIKI/09+Video+Transcripts/Videos/Turn+any+YouTube+Channel+into+your+AI+Mentor+-+Obsidian+is+the+ultimate+automation+workbench+for+PKM)**
- type: markdown
attributes:
@@ -46,6 +48,13 @@ body:
description: "Run `Command Palette/Show Debug info` in Obsidian and paste the result here."
placeholder: "Paste your Obsidian debug info here..."
- type: textarea
id: notebook_lm
attributes:
label: "Your NotebookLM query"
description: "See point 4) above. Paste the question and answer you received from NotebookLM. This serves partly as proof, partly to help me see where the model is incorrect"
placeholder: "Copy/Paste your question and the resulting answer you got from NotebookLM"
- type: textarea
id: bug_description
attributes:

View File

@@ -0,0 +1,69 @@
name: Feature request
description: Request a new feature for the Excalidraw plugin.
title: "FR: "
body:
- type: markdown
attributes:
value: |
🛠️ **Important: Please Read Before Submitting a Feature Request** 🛠️
I develop Excalidraw primarily as a hobby, and I prioritize features that I personally use regularly. Features I don't use often break without me noticing, which is why the plugin is as stable as it is—because I rely on it every day.
**Your task isn't just to describe the feature you want, but to sell me on the idea**. If I'm not convinced that the feature would significantly benefit my own workflow, it's unlikely I'll spend my free hobby time on it.
When creating your feature request:
- **Provide real-life usage scenarios**: How will this feature help you? How do you imagine using it in practice?
- **Sell the idea**: Convince me that I need this feature too.
- **Include supporting materials**: Reference other solutions? Include relevant screenshots, screen recordings, and links. The more work I need to do to understand your request, the less likely it is to be implemented.
**Additional Guidelines:**
- **Excalidraw Core Features**: If your request relates to core Excalidraw features, and not the Obsidian integration specifically, please raise it with the Excalidraw product team here: [Excalidraw Issues](https://github.com/excalidraw/excalidraw/issues).
- **Cross-Platform Compatibility**: Obsidian and Excalidraw are cross-platform solutions. I cannot develop platform-specific features like Apple Pencil or Samsung S-Pen support due to technical limitations. Please only request features that make sense across all platforms (Windows, Linux, ChromeOS, macOS, Android, iOS).
Remember: You're not asking from a genie. You're pitching to someone who will need to spend hours (even for small changes) on testing, releasing, documenting, and supporting the feature. If you don't sell it well, it won't get done.
**Explore Scripting First:**
Many feature requests can already be achieved with scripts using Excalidraw Automate. While the documentation is somewhat dated, there are nearly a hundred published scripts available. With a little determination, you can explore the possibilities. Feeding relevant scripts into GPT can often generate a working solution. Start here: [Excalidraw Automate Documentation](https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html).
- type: textarea
id: problem_description
attributes:
label: "Is your feature request related to a problem? Please describe."
description: "Provide a clear and concise description of the problem. Ex. I'm always frustrated when..."
placeholder: "Describe the problem you are facing..."
- type: textarea
id: solution_description
attributes:
label: "Describe the solution you'd like"
description: "Provide a clear and concise description of what you want to happen."
placeholder: "Describe the feature you are requesting..."
- type: textarea
id: usage_scenario
attributes:
label: "Real-life usage scenarios"
description: "Provide specific examples of how you would use this feature. Convince me that this is a feature I need in my own workflow."
placeholder: |
1. In this scenario, I would use the feature to...
2. Another use case is when I...
- type: textarea
id: alternatives_considered
attributes:
label: "Describe alternatives you've considered"
description: "Provide a clear and concise description of any alternative solutions or features you've considered."
placeholder: "Describe any alternative approaches you have considered..."
- type: textarea
id: additional_context
attributes:
label: "Additional context"
description: "Include any other context, screenshots, or references about the feature request."
placeholder: "Include screenshots, recordings, or other supporting material..."
- type: markdown
attributes:
value: |
**Attachments: Make your case visually!**
Supporting images, screenshots, or screen recordings are critical for helping me understand your request. Drag and drop them into the comment area or directly into any of the text fields above. Every bit of detail increases the chance of your request being understood and prioritized.

2
.gitignore vendored
View File

@@ -4,7 +4,6 @@
# npm
node_modules
package-lock.json
# build
main.js
@@ -14,6 +13,7 @@ hot-reload.bat
data.json
lib
dist
tmp
#VSCode
.vscode

2
.nvmrc
View File

@@ -1 +1 @@
18
18

View File

@@ -161,7 +161,7 @@ Number between 0 and 100. The opacity of an object, both stroke and fill.
### strokeSharpness, setStrokeSharpness()
```typescript
type StrokeSharpness = "round" | "sharp";
setStrokeSharpness(val:nmuber);
setStrokeSharpness(val:number);
```
strokeSharpness is a string.

View File

@@ -1,64 +1,46 @@
import { DataURL } from "@zsviczian/excalidraw/types/excalidraw/types";
import {mathjax} from "mathjax-full/js/mathjax";
import {TeX} from 'mathjax-full/js/input/tex.js';
import {SVG} from 'mathjax-full/js/output/svg.js';
import {LiteAdaptor, liteAdaptor} from 'mathjax-full/js/adaptors/liteAdaptor.js';
import {RegisterHTMLHandler} from 'mathjax-full/js/handlers/html.js';
import {AllPackages} from 'mathjax-full/js/input/tex/AllPackages.js';
import { customAlphabet } from "nanoid";
import ExcalidrawView from "./ExcalidrawView";
import { FileData, MimeType } from "./EmbeddedFileLoader";
import { FileId } from "@zsviczian/excalidraw/types/excalidraw/element/types";
import { getImageSize, svgToBase64 } from "./utils/Utils";
import { fileid } from "./constants/constants";
import { TFile } from "obsidian";
import { MathDocument } from "mathjax-full/js/core/MathDocument";
export const updateEquation = async (
equation: string,
fileId: string,
view: ExcalidrawView,
addFiles: Function,
) => {
const data = await tex2dataURL(equation);
if (data) {
const files: FileData[] = [];
files.push({
mimeType: data.mimeType,
id: fileId as FileId,
dataURL: data.dataURL,
created: data.created,
size: data.size,
hasSVGwithBitmap: false,
shouldScale: true,
});
addFiles(files, view);
}
};
type DataURL = string & { _brand: "DataURL" };
type FileId = string & { _brand: "FileId" };
const fileid = customAlphabet("1234567890abcdef", 40);
let adaptor: LiteAdaptor;
let html: MathDocument<any, any, any>;
let html: any;
let preamble: string;
export const clearMathJaxVariables = () => {
adaptor = null;
html = null;
preamble = null;
};
function svgToBase64(svg: string): string {
const cleanSvg = svg.replaceAll("&nbsp;", " ");
// Convert the string to UTF-8 and handle non-Latin1 characters
const encodedData = encodeURIComponent(cleanSvg)
.replace(/%([0-9A-F]{2})/g,
(match, p1) => String.fromCharCode(parseInt(p1, 16))
);
return `data:image/svg+xml;base64,${btoa(encodedData)}`;
}
//https://github.com/xldenis/obsidian-latex/blob/master/main.ts
const loadPreamble = async () => {
const file = app.vault.getAbstractFileByPath("preamble.sty");
preamble = file && file instanceof TFile
? await app.vault.read(file)
: null;
};
async function getImageSize(src: string): Promise<{ height: number; width: number }> {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve({ height: img.naturalHeight, width: img.naturalWidth });
img.onerror = reject;
img.src = src;
});
}
export async function tex2dataURL(
tex: string,
scale: number = 4 // Default scale value, adjust as needed
scale: number = 4,
plugin?: any
): Promise<{
mimeType: MimeType;
mimeType: string;
fileId: FileId;
dataURL: DataURL;
created: number;
@@ -68,25 +50,37 @@ export async function tex2dataURL(
let output: SVG<unknown, unknown, unknown>;
if(!adaptor) {
await loadPreamble();
if (plugin) {
const file = plugin.app.vault.getAbstractFileByPath(plugin.settings.latexPreambleLocation || "preamble.sty");
preamble = file ? await plugin.app.vault.read(file) : null;
}
adaptor = liteAdaptor();
RegisterHTMLHandler(adaptor);
input = new TeX({
packages: AllPackages,
...Boolean(preamble) ? {
...(preamble ? {
inlineMath: [['$', '$']],
displayMath: [['$$', '$$']]
} : {},
} : {}),
});
output = new SVG({ fontCache: "local" });
html = mathjax.document("", { InputJax: input, OutputJax: output });
}
try {
const node = html.convert(
Boolean(preamble) ? `${preamble}${tex}` : tex,
preamble ? `${preamble}\n${tex}` : tex,
{ display: true, scale }
);
const svg = new DOMParser().parseFromString(adaptor.innerHTML(node), "image/svg+xml").firstChild as SVGSVGElement;
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2195
//https://stackoverflow.com/a/77181931
let styleNode = document.createElement('style');
styleNode.setAttribute("type", "text/css");
styleNode.appendChild(document.createTextNode(".mjx-solid { stroke-width: 80px; }"));
svg.appendChild(styleNode);
if (svg) {
if(svg.width.baseVal.valueInSpecifiedUnits < 2) {
svg.width.baseVal.valueAsString = `${(svg.width.baseVal.valueInSpecifiedUnits+1).toFixed(3)}ex`;
@@ -107,4 +101,10 @@ export async function tex2dataURL(
console.error(e);
}
return null;
}
export function clearMathJaxVariables(): void {
adaptor = null;
html = null;
preamble = null;
}

1340
MathjaxToSVG/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

24
MathjaxToSVG/package.json Normal file
View File

@@ -0,0 +1,24 @@
{
"name": "@zsviczian/mathjax-to-svg",
"version": "1.0.0",
"main": "dist/index.js",
"scripts": {
"build": "cross-env NODE_ENV=production rollup --config rollup.config.js",
"dev": "cross-env NODE_ENV=development rollup --config rollup.config.js"
},
"dependencies": {
"mathjax-full": "^3.2.2",
"nanoid": "^4.0.2"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^26.0.1",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-typescript": "^12.1.2",
"cross-env": "^7.0.3",
"obsidian": "1.5.7-1",
"rollup": "^2.70.1",
"rollup-plugin-terser": "^7.0.2",
"tslib": "^2.8.1",
"typescript": "^5.7.3"
}
}

View File

@@ -0,0 +1,35 @@
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import { terser } from 'rollup-plugin-terser';
const isProd = (process.env.NODE_ENV === 'production');
export default {
input: './index.ts',
output: {
dir: 'dist',
format: 'iife',
name: 'MathjaxToSVG', // Global variable name
exports: 'named',
sourcemap: !isProd,
},
plugins: [
typescript({
tsconfig: 'tsconfig.json',
}),
commonjs(),
nodeResolve({
browser: true,
preferBuiltins: false
}),
isProd && terser({
format: {
comments: false,
},
compress: {
passes: 2,
}
})
].filter(Boolean)
};

View File

@@ -0,0 +1,26 @@
{
"compilerOptions": {
"baseUrl": ".",
"sourceMap": false,
"module": "es2020",
"target": "es2022", //min es2017 because script engine requires for async execution and min es2018 for named capture groups
"allowJs": false,
"noImplicitAny": true,
"moduleResolution": "node",
"esModuleInterop": true,
"importHelpers": true,
"resolveJsonModule": true,
"lib": [
"dom",
"scripthost",
"es2022",
"DOM.Iterable"
],
"jsx": "react",
},
"include": [
"**/*.ts",
"**/*.tsx", "src/shared/Dialogs/OpenDrawing.ts",
"src/types/types.d.ts",
]
}

View File

@@ -1,4 +0,0 @@
The project runs with `node 18`.
After running `npm -i` you'll need to make two manual changes:

View File

@@ -1,8 +1,8 @@
# Excalidraw
[简体中文](./README.zh-cn.md)
【English | [简体中文](./docs/zh-cn/README.md)
👉👉👉 Check out and contribute to the new [Obsidian-Excalidraw Community Wiki](https://excalidraw-obsidian.online/Hobbies/Excalidraw+Blog/WIKI/Welcome+to+the+WIKI)
👉👉👉 Check out and contribute to the new [Obsidian-Excalidraw Community Wiki](https://excalidraw-obsidian.online/WIKI/Welcome+to+the+WIKI)
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.
@@ -89,8 +89,8 @@ 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.
- **Display**: settings that affect 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 affect 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.
@@ -100,15 +100,17 @@ Plugin settings are grouped into the following sections:
#### 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.
- 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. With versions 1.6.13 or higher make sure to enable "Decompress Excalidraw JSON in Markdown View" in the settings before editing the JSON in the template. This can be disabled after the canges are performed.
- 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.
- Find `"colorPalette": {` 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.
- To change the previewed colors, a `"topPicks": {` may be specified containing the same three keys:
- `"canvasBackground":[], "elementBackground":[], "elementStroke": []`.
- Note that the corresponding arrays must contain 5 elements.
- See my videos above for further help.
#### Export
@@ -227,6 +229,7 @@ For more details, see this [video](https://youtu.be/yZQoJg2RCKI)
- `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.
- Since 1.6.13, enable "Decompress Excalidraw JSON in Markdown View" in the settings if you want to change any JSON content.
### Embed complete markdown files into your drawings

BIN
assets/excalidraw-fonts.zip Normal file

Binary file not shown.

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -17,7 +17,7 @@ import { ConnectionPoint, DeviceType } from "src/types";
import { ColorMaster } from "colormaster";
import { TInput } from "colormaster/types";
import { ClipboardData } from "@zsviczian/excalidraw/types/clipboard";
import { PaneTarget } from "src/utils/ModifierkeyHelper";
import { PaneTarget } from "src/utils/modifierkeyHelper";
export declare class ExcalidrawAutomate {
/**
* Utility function that returns the Obsidian Module object.

View File

@@ -54,7 +54,7 @@ Number between 0 and 100. The opacity of an object, both stroke and fill.
### strokeSharpness, setStrokeSharpness()
```typescript
type StrokeSharpness = "round" | "sharp";
setStrokeSharpness(val:nmuber);
setStrokeSharpness(val:number);
```
strokeSharpness is a string.

View File

@@ -1,5 +1,7 @@
# [◀ Excalidraw Automate How To](./readme.md)
【English | [简体中文](zh-cn/docs/ExcalidrawScriptsEngine.md)】
[![Script Engine](https://user-images.githubusercontent.com/14358394/145684531-8d9c2992-59ac-4ebc-804a-4cce1777ded2.jpg)](https://youtu.be/hePJcObHIso)
## Introduction

5782
docs/Release-notes.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,7 @@
# Excalidraw Automate How To
【English | [简体中文](zh-cn/docs/readme.md)】
Use ExcalidrawAutomate to create or manipulate Excalidraw drawings using the [ExcalidrawAutomate Script Engine](ExcalidrawScriptsEngine.md), the [Templater](https://silentvoid13.github.io/Templater/docs/) or the [QuickAdd](https://github.com/chhoumann/quickadd) plugins, and to generate embedded SVG and PNG images using [DataviewJS](https://blacksmithgu.github.io/obsidian-dataview/docs/api/intro/)
With a little work, using ExcalidrawAutomate you can generate simple mindmaps, build a family tree, fill out SVG forms, create customized charts, or automate simple tasks (i.e. create macros) in Excalidraw.

484
docs/zh-cn/AutomateHowTo.md Normal file
View File

@@ -0,0 +1,484 @@
# Excalidraw 自动化使用指南
> 此说明当前更新至 `5569cff`。
[English](../../AutomateHowTo.md)
Excalidraw 自动化允许您使用 [Templater](https://github.com/SilentVoid13/Templater) 插件创建 Excalidraw 绘图。
通过一些工作,使用 Excalidraw 自动化,您可以根据保管库中的文档生成简单的思维导图、填写 SVG 表单、创建自定义图表等。
您可以通过 ExcalidrawAutomate 对象访问 Excalidraw 自动化。我建议您以以下代码开始您的自动化脚本。
*使用 <kbd>CTRL+Shift+V</kbd> 将代码粘贴到 Obsidian 中!*
```javascript
const ea = ExcalidrawAutomate;
ea.reset();
```
第一行创建了一个实用的常量,这样您就可以避免写 100 次 `ExcalidrawAutomate`
第二行将 `ExcalidrawAutomate` 重置为默认值。这一点很重要,因为您将不知道之前执行了哪个模板,因此您也不知道 `Excalidraw` 的状态。
## 使用 Excalidraw 自动化的基本逻辑
1. 设置您想要绘制的元素的样式
2. 添加元素。每添加一个新元素,它都会在上一个元素的上方添加一层,因此在重叠对象的情况下,后添加的元素会在前一个元素之上。
3. 调用 `await ea.create();` 来实例化绘图
您可以在添加不同元素之间更改样式。我将元素样式与创建分开是基于这样的假设:您可能会设置描边颜色、描边样式、描边粗糙度等,并使用这些设置绘制大多数元素。每次添加元素时设置所有这些参数是没有意义的。
### 在深入探讨之前,这里有两个简单的示例脚本
#### 使用模板在自定义文件夹中创建具有自定义名称的新绘图
这个简单的脚本为您提供了比 Excalidraw 插件设置更大的灵活性,可以为您的绘图命名、将其放入文件夹中,并应用模板。
*使用 <kbd>CTRL+Shift+V</kbd> 将代码粘贴到 Obsidian 中!*
```javascript
<%*
const ea = ExcalidrawAutomate;
ea.reset();
await ea.create({
filename : tp.date.now("HH.mm"),
foldername : tp.date.now("YYYY-MM-DD"),
templatePath: "Excalidraw/Template1.excalidraw",
onNewPane : false
});
%>
```
#### 创建一个简单的绘图
*使用 <kbd>CTRL+Shift+V</kbd> 将代码粘贴到 Obsidian 中!*
```javascript
<%*
const ea = ExcalidrawAutomate;
ea.reset();
ea.addRect(-150,-50,450,300);
ea.addText(-100,70,"Left to right");
ea.addArrow([[-100,100],[100,100]]);
ea.style.strokeColor = "red";
ea.addText(100,-30,"top to bottom",{width:200,textAligh:"center"});
ea.addArrow([[200,0],[200,200]]);
await ea.create();
%>
```
该脚本将生成以下绘图:
![FristDemo](https://user-images.githubusercontent.com/14358394/116825643-6e5a8b00-ab90-11eb-9e3a-37c524620d0d.png)
## 属性和功能一览
这是 ExcalidrawAutomate 实现的接口:
*使用 <kbd>CTRL+Shift+V</kbd> 将代码粘贴到 Obsidian 中!*
```javascript
ExcalidrawAutomate: {
style: {
strokeColor: string;
backgroundColor: string;
angle: number;
fillStyle: FillStyle;
strokeWidth: number;
storkeStyle: StrokeStyle;
roughness: number;
opacity: number;
strokeSharpness: StrokeSharpness;
fontFamily: FontFamily;
fontSize: number;
textAlign: string;
verticalAlign: string;
startArrowHead: string;
endArrowHead: string;
}
canvas: {theme: string, viewBackgroundColor: string};
setFillStyle: Function;
setStrokeStyle: Function;
setStrokeSharpness: Function;
setFontFamily: Function;
setTheme: Function;
addRect: Function;
addDiamond: Function;
addEllipse: Function;
addText: Function;
addLine: Function;
addArrow: Function;
connectObjects: Function;
addToGroup: Function;
toClipboard: Function;
create: Function;
createPNG: Function;
createSVG: Function;
clear: Function;
reset: Function;
};
```
## 元素样式
正如您所注意到的,某些样式具有设置函数。这是为了帮助您浏览属性的可用值。不过,您并不需要使用设置函数,您也可以直接设置值。
### strokeColor
字符串。线条的颜色。[CSS 合法颜色值](https://www.w3schools.com/cssref/css_colors_legal.asp)
允许的值包括 [HTML 颜色名称](https://www.w3schools.com/colors/colors_names.asp)、十六进制 RGB 字符串,例如 `#FF0000` 表示红色。
### backgroundColor
字符串。对象的填充颜色。[CSS 合法颜色值](https://www.w3schools.com/cssref/css_colors_legal.asp)
允许的值包括 [HTML 颜色名称](https://www.w3schools.com/colors/colors_names.asp)、十六进制 RGB 字符串,例如 `#FF0000` 表示红色,或 `transparent`(透明)。
### angle
数字。以弧度表示的旋转。90° == `Math.PI/2`
### fillStyle, setFillStyle()
```typescript
type FillStyle = "hachure" | "cross-hatch" | "solid";
setFillStyle (val:number);
```
fillStyle 是一个字符串.
`setFillStyle()` 接受一个数字:
- 0: "hachure"(斜线填充)
- 1: "cross-hatch"(交叉斜线填充)
- 其他任何数字: "solid"(实心填充)
### strokeWidth
数字,设置描边的宽度。
### strokeStyle, setStrokeStyle()
```typescript
type StrokeStyle = "solid" | "dashed" | "dotted";
setStrokeStyle (val:number);
```
strokeStyle 是一个字符串。
`setStrokeStyle()` 接受一个数字:
- 0: "solid"(实线)
- 1: "dashed"(虚线)
- 其他任何数字: "dotted"(点线)
### roughness
数字。在 Excalidraw 中称为“粗糙度”。接受三个值:
- 0: 建筑师
- 1: 艺术家
- 2: 卡通画家
### opacity
介于 0 和 100 之间的数字。对象的透明度,包括描边和填充。
### strokeSharpness, setStrokeSharpness()
```typescript
type StrokeSharpness = "round" | "sharp";
setStrokeSharpness(val:number);
```
strokeSharpness 是一个字符串。
“round” 线条是曲线“sharp” 线条在转折点处断开(硬弯折)。
`setStrokeSharpness()` 接受一个数字:
- 0: "round"(圆滑)
- 其他任何数字: "sharp"(尖锐)
### fontFamily, setFontFamily()
数字。有效值为 1、2 和 3。
`setFontFamily()` 也会接受一个数字并返回字体名称。
- 1: "Virgil, Segoe UI Emoji"
- 2: "Helvetica, Segoe UI Emoji"
- 3: "Cascadia, Segoe UI Emoji"
### fontSize
数字。默认值为 20 像素。
### textAlign
字符串。文本的水平对齐方式。有效值为 "left"(左对齐)、"center"(居中对齐)、"right"(右对齐)。
在使用 `addText()` 函数设置固定宽度时,这一点很重要。
### verticalAlign
字符串。文本的垂直对齐方式。有效值为 "top"(顶部)和 "middle"(中间)。
在使用 `addText()` 函数设置固定高度时,这一点很重要。
### startArrowHead, endArrowHead
字符串。有效值为 "arrow"(箭头)、"bar"(线条)、"dot"(点)和 "none"(无)。指定箭头的起始和结束。
在使用 `addArrow()``connectObjects()` 函数时,这一点很重要。
## canvas
设置画布的属性。
### theme, setTheme()
字符串。有效值为 "light"(明亮)和 "dark"(黑暗)。
`setTheme()` 接受一个数字:
- 0: "light"(明亮)
- 其他任何数字: "dark"(黑暗)
### viewBackgroundColor
字符串。对象的填充颜色。[CSS 合法颜色值](https://www.w3schools.com/cssref/css_colors_legal.asp)
允许的值包括 [HTML 颜色名称](https://www.w3schools.com/colors/colors_names.asp)、十六进制 RGB 字符串,例如 `#FF0000` 表示红色,或 `transparent`(透明)。
## 添加对象
这些函数将向您的绘图中添加对象。画布是无限的,接受负值和正值的 X 和 Y 坐标。X 值从左到右增加Y 值从上到下增加。
![坐标](https://user-images.githubusercontent.com/14358394/116825632-6569b980-ab90-11eb-827b-ada598e91e46.png)
### addRect(), addDiamond(), addEllipse()
```typescript
addRect(topX:number, topY:number, width:number, height:number):string
addDiamond(topX:number, topY:number, width:number, height:number):string
addEllipse(topX:number, topY:number, width:number, height:number):string
```
返回对象的 `id`。在用线连接对象时,需要使用 `id`。请参见后文。
### addText
```typescript
addText(topX:number, topY:number, text:string, formatting?:{width:number, height:number,textAlign: string, verticalAlign:string, box: boolean, boxPadding: number}):string
```
向绘图中添加文本。
格式参数是可选的:
- 如果未指定 `width`(宽度)和 `height`(高度),函数将根据 `fontFamily``fontSize` 和提供的文本计算宽度和高度。
- 如果您希望文本相对于绘图中的其他元素居中,可以提供固定的高度和宽度,同时可以指定 `textAlign``verticalAlign`,如上所述。例如:`{width:500, textAlign:"center"}`
- 如果您想在文本周围添加一个框,请设置 `{box:true}`
返回对象的 `id`。在用线连接对象时,需要使用 `id`。请参见后文。如果 `{box:true}`,则返回包围框的 `id`
### addLine()
```typescript
addLine(points: [[x:number,y:number]]):void
```
添加一条连接提供的点的线。必须至少包含两个点 `points.length >= 2`。如果提供的点超过两个,间隔点将作为断点添加。如果 `strokeSharpness` 设置为 "sharp",线条将在转折处断开;如果设置为 "round",线条将是曲线。
### addArrow()
```typescript
addArrow(points: [[x:number,y:number]],formatting?:{startArrowHead:string,endArrowHead:string,startObjectId:string,endObjectId:string}):void
```
添加一条连接提供的点的箭头。必须至少包含两个点 `points.length >= 2`。如果提供的点超过两个,间隔点将作为断点添加。如果元素 `style.strokeSharpness` 设置为 "sharp",线条将在转折处断开;如果设置为 "round",线条将是曲线。
`startArrowHead``endArrowHead` 指定要使用的箭头类型,如上所述。有效值为 "none"(无)、"arrow"(箭头)、"dot"(点)和 "bar"(线条)。例如:`{startArrowHead: "dot", endArrowHead: "arrow"}`
`startObjectId``endObjectId` 是连接对象的对象 ID。我建议使用 `connectObjects` 而不是调用 `addArrow()` 来连接对象。
### connectObjects()
```typescript
declare type ConnectionPoint = "top"|"bottom"|"left"|"right";
connectObjects(objectA: string, connectionA: ConnectionPoint, objectB: string, connectionB: ConnectionPoint, formatting?:{numberOfPoints: number,startArrowHead:string,endArrowHead:string, padding: number}):void
```
连接两个对象的箭头。
`objectA``objectB` 是字符串。这些是要连接的对象的 ID。这些 ID 是通过 `addRect()``addDiamond()``addEllipse()``addText()` 创建这些对象时返回的。
`connectionA``connectionB` 指定在对象上的连接位置。有效值为:"top"(上)、"bottom"(下)、"left"(左)和 "right"(右)。
`numberOfPoints` 设置线条的间隔断点数量。默认值为零,意味着箭头的起点和终点之间不会有断点。当在绘图中移动对象时,这些断点将影响 Excalidraw 如何重新调整线条。
`startArrowHead``endArrowHead` 的功能与 `addArrow()` 中描述的一致。
### addToGroup()
```typescript
addToGroup(objectIds:[]):void
```
`objectIds` 中列出的对象进行分组。
## 实用函数
### clear()
`clear()` 将从缓存中清除对象,但会保留元素样式设置。
### reset()
`reset()` 将首先调用 `clear()`,然后将元素样式重置为默认值。
### toClipboard()
```typescript
async toClipboard(templatePath?:string)
```
将生成的图形放入剪贴板。当您不想创建新图形,而是想将其他项目粘贴到现有图形上时,这非常有用。
### create()
```typescript
async create(params?:{filename: string, foldername:string, templatePath:string, onNewPane: boolean})
```
创建图形并打开它。
`filename` 是要创建的图形的文件名(不带扩展名)。如果为 `null`,则 Excalidraw 会生成一个文件名。
`foldername` 是文件应创建的文件夹。如果为 `null`,则将根据 Excalidraw 设置使用新图形的默认文件夹。
`templatePath` 是包含完整路径和扩展名的模板文件名。该模板文件将作为基础层添加,所有通过 ExcalidrawAutomate 添加的额外对象将出现在模板元素之上。如果为 `null`,则不使用模板,即空白图形将作为添加对象的基础。
`onNewPane` 定义新图形应创建的位置。`false` 将在当前活动的标签页中打开图形;`true` 将通过垂直分割当前标签页来打开图形。
示例:
```javascript
create({filename:"my drawing", foldername:"myfolder/subfolder/", templatePath: "Excalidraw/template.excalidraw", onNewPane: true});
```
### createSVG()
```typescript
async createSVG(templatePath?:string)
```
返回一个包含生成图形的 HTML `SVGSVGElement`
### createPNG()
```typescript
async createPNG(templatePath?:string)
```
返回一个包含生成图形的 PNG 图像的 blob。
## 示例
### 将新图形插入到当前编辑的文档中
此模板将提示您输入图形的标题。它将在您提供的标题下创建一个新图形,并在您正在编辑的文档的文件夹中。然后,它将在光标位置插入新图形,并通过分割当前标签页在新的工作区标签页中打开新图形。
*使用 <kbd>CTRL+Shift+V</kbd> 将代码粘贴到 Obsidian 中!*
```javascript
<%*
const defaultTitle = tp.date.now("HHmm")+' '+tp.file.title;
const title = await tp.system.prompt("Title of the drawing?", defaultTitle);
const folder = tp.file.folder(true);
const transcludePath = (folder== '/' ? '' : folder + '/') + title + '.excalidraw';
tR = String.fromCharCode(96,96,96)+'excalidraw\n[['+transcludePath+']]\n'+String.fromCharCode(96,96,96);
const ea = ExcalidrawAutomate;
ea.reset();
ea.setTheme(1); //set Theme to dark
await ea.create({
filename : title,
foldername : folder,
//templatePath: 'Excalidraw/Template.excalidraw', //uncomment if you want to use a template
onNewPane : true
});
%>
```
### 连接对象
*使用 <kbd>CTRL+Shift+V</kbd> 将代码粘贴到 Obsidian 中!*
```javascript
<%*
const ea = ExcalidrawAutomate;
ea.reset();
ea.addText(-130,-100,"Connecting two objects");
const a = ea.addRect(-100,-100,100,100);
const b = ea.addEllipse(200,200,100,100);
ea.connectObjects(a,"bottom",b,"left",{numberOfPoints: 2}); //see how the line breaks differently when moving objects around
ea.style.strokeColor = "red";
ea.connectObjects(a,"right",b,"top",1);
await ea.create();
%>
```
### 使用模板
这个示例与第一个类似,但旋转了 90°并使用了模板同时指定了文件名和保存图形的文件夹并在新的标签页中打开新图形。
*使用 <kbd>CTRL+Shift+V</kbd> 将代码粘贴到 Obsidian 中!*
```javascript
<%*
const ea = ExcalidrawAutomate;
ea.reset();
ea.style.angle = Math.PI/2;
ea.style.strokeWidth = 3.5;
ea.addRect(-150,-50,450,300);
ea.addText(-100,70,"Left to right");
ea.addArrow([[-100,100],[100,100]]);
ea.style.strokeColor = "red";
await ea.addText(100,-30,"top to bottom",{width:200,textAlign:"center"});
ea.addArrow([[200,0],[200,200]]);
await ea.create({filename:"My Drawing",foldername:"myfolder/fordemo/",templatePath:"Excalidraw/Template2.excalidraw",onNewPane:true});
%>
```
### 从文本大纲生成简单思维导图
这是一个稍微复杂一些的示例。这个示例将从一个表格化的大纲生成思维导图。
![Drawing 2021-05-05 20 52 34](https://user-images.githubusercontent.com/14358394/117194124-00a69d00-ade4-11eb-8b75-5e18a9cbc3cd.png)
输入示例:
```
- Test 1
- Test 1.1
- Test 2
- Test 2.1
- Test 2.2
- Test 2.2.1
- Test 2.2.2
- Test 2.2.3
- Test 2.2.3.1
- Test 3
- Test 3.1
```
脚本:
*使用 <kbd>CTRL+Shift+V</kbd> 将代码粘贴到 Obsidian 中!*
```javascript
<%*
const IDX = Object.freeze({"depth":0, "text":1, "parent":2, "size":3, "children": 4, "objectId":5});
//check if an editor is the active view
const editor = this.app.workspace.activeLeaf?.view?.editor;
if(!editor) return;
//initialize the tree with the title of the document as the first element
let tree = [[0,this.app.workspace.activeLeaf?.view?.getDisplayText(),-1,0,[],0]];
const linecount = editor.lineCount();
//helper function, use regex to calculate indentation depth, and to get line text
function getLineProps (i) {
props = editor.getLine(i).match(/^(\t*)-\s+(.*)/);
return [props[1].length+1, props[2]];
}
//a vector that will hold last valid parent for each depth
let parents = [0];
//load outline into tree
for(i=0;i<linecount;i++) {
[depth,text] = getLineProps(i);
if(depth>parents.length) parents.push(i+1);
else parents[depth] = i+1;
tree.push([depth,text,parents[depth-1],1,[]]);
tree[parents[depth-1]][IDX.children].push(i+1);
}
//recursive function to crawl the tree and identify height aka. size of each node
function crawlTree(i) {
if(i>linecount) return 0;
size = 0;
if((i+1<=linecount && tree[i+1][IDX.depth] <= tree[i][IDX.depth])|| i == linecount) { //I am a leaf
tree[i][IDX.size] = 1;
return 1;
}
tree[i][IDX.children].forEach((node)=>{
size += crawlTree(node);
});
tree[i][IDX.size] = size;
return size;
}
crawlTree(0);
//Build the mindmap in Excalidraw
const width = 300;
const height = 100;
const ea = ExcalidrawAutomate;
ea.reset();
//stores position offset of branch/leaf in height units
offsets = [0];
for(i=0;i<=linecount;i++) {
depth = tree[i][IDX.depth];
if (depth == 1) ea.style.strokeColor = '#'+(Math.random()*0xFFFFFF<<0).toString(16);
tree[i][IDX.objectId] = ea.addText(depth*width,((tree[i][IDX.size]/2)+offsets[depth])*height,tree[i][IDX.text],{box:true});
//set child offset equal to parent offset
if((depth+1)>offsets.length) offsets.push(offsets[depth]);
else offsets[depth+1] = offsets[depth];
offsets[depth] += tree[i][IDX.size];
if(tree[i][IDX.parent]!=-1) {
ea.connectObjects(tree[tree[i][IDX.parent]][IDX.objectId],"right",tree[i][IDX.objectId],"left",{startArrowHead: 'dot'});
}
}
await ea.create({onNewPane: true});
%>
```

View File

@@ -1,6 +1,10 @@
# Excalidraw
> 此说明当前更新至 2.4.0-beta-9
> 此说明当前更新至 `5569cff`
【[English](../../README.md) | 简体中文】
👉👉👉 快来查看并为新的 [Obsidian-Excalidraw 社区维基](https://excalidraw-obsidian.online/Hobbies/Excalidraw+Blog/WIKI/Welcome+to+the+WIKI)贡献你的力量吧
Obsidian-Excalidraw 插件将 [Excalidraw](https://excalidraw.com/) 这一功能丰富的草图工具集成到 Obsidian 中。您可以在您的库中存储和编辑 Excalidraw 文件,可以将图形嵌入到文档中,还可以在 Excalidraw 中链接到文档和其他图形。有关 Excalidraw 功能的展示,请查看我的博客文章 [这里](https://www.zsolt.blog/2021/03/showcasing-excalidraw.html) 或观看以下视频。
@@ -103,7 +107,7 @@ Obsidian-Excalidraw 插件将 [Excalidraw](https://excalidraw.com/) 这一功能
#### 模板
- 新绘图的模板。该模板将恢复笔画属性。这意味着您可以在模板中设置笔画颜色、笔画宽度、不透明度、字体系列、字体大小、填充样式、笔画样式等的默认值。这同样适用于 ExcalidrawAutomate。
- 新绘图的模板。该模板将恢复笔画属性。这意味着您可以在模板中设置笔画颜色、笔画宽度、不透明度、字体系列、字体大小、填充样式、笔画样式等的默认值。这同样适用于 ExcalidrawAutomate。对于 1.6.13 或更高版本,在编辑模板中的 JSON 之前,请确保在设置中启用"在 Markdown 视图中解压缩 Excalidraw JSON"。完成更改后可以禁用此选项。
- 通过模板,您可以自定义 Excalidraw 使用的调色板。
- 切换到 Markdown 视图。
- 滚动到文件底部,找到 `"AppState": {`
@@ -214,6 +218,7 @@ Obsidian-Excalidraw 插件将 [Excalidraw](https://excalidraw.com/) 这一功能
- `excalidraw-export-dark` true == 深色模式 / false == 浅色模式。
- `excalidraw-export-padding`:指定图像的导出边距。
- `excalidraw-export-pngscale`:这仅影响导出为 PNG。指定图像的导出比例。典型范围在 0.5 到 5 之间,但您也可以尝试其他值。
- 从 1.6.13 版本开始,如果您想修改任何 JSON 内容,请在设置中启用"在 Markdown 视图中解压缩 Excalidraw JSON"。
### 将完整的 Markdown 文件嵌入到您的绘图中

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,782 @@
# [◀ Excalidraw 自动化使用指南](../readme.md)
## 属性和函数概览
以下是 ExcalidrawAutomate 实现的接口:
你可以在这里找到源文件: [ExcalidrawAutomate.d.ts](ExcalidrawAutomate.d.ts)。
```javascript
/// <reference types="react" />
import ExcalidrawPlugin from "src/main";
import { FillStyle, StrokeStyle, ExcalidrawElement, ExcalidrawBindableElement, FileId, NonDeletedExcalidrawElement, ExcalidrawImageElement, StrokeRoundness, RoundnessType } from "@zsviczian/excalidraw/types/element/types";
import { Editor, OpenViewState, TFile, WorkspaceLeaf } from "obsidian";
import * as obsidian_module from "obsidian";
import ExcalidrawView, { ExportSettings } from "src/ExcalidrawView";
import { AppState, BinaryFileData, DataURL, ExcalidrawImperativeAPI, Point } from "@zsviczian/excalidraw/types/types";
import { EmbeddedFilesLoader } from "src/EmbeddedFileLoader";
import { ConnectionPoint, DeviceType } from "src/types";
import { ColorMaster } from "colormaster";
import { TInput } from "colormaster/types";
import { ClipboardData } from "@zsviczian/excalidraw/types/clipboard";
import { PaneTarget } from "src/utils/modifierkeyHelper";
export declare class ExcalidrawAutomate {
/**
* Utility function that returns the Obsidian Module object.
*/
get obsidian(): typeof obsidian_module;
get DEVICE(): DeviceType;
getAttachmentFilepath(filename: string): Promise<string>;
/**
* Prompts the user with a dialog to select new file action.
* - create markdown file
* - create excalidraw file
* - cancel action
* The new file will be relative to this.targetView.file.path, unless parentFile is provided.
* If shouldOpenNewFile is true, the new file will be opened in a workspace leaf.
* targetPane control which leaf will be used for the new file.
* Returns the TFile for the new file or null if the user cancelled the action.
* @param newFileNameOrPath
* @param shouldOpenNewFile
* @param targetPane //type PaneTarget = "active-pane"|"new-pane"|"popout-window"|"new-tab"|"md-properties";
* @param parentFile
* @returns
*/
newFilePrompt(newFileNameOrPath: string, shouldOpenNewFile: boolean, targetPane?: PaneTarget, parentFile?: TFile): Promise<TFile | null>;
/**
* Generates a new Obsidian Leaf following Excalidraw plugin settings such as open in Main Workspace or not, open in adjacent pane if available, etc.
* @param origo // the currently active leaf, the origin of the new leaf
* @param targetPane //type PaneTarget = "active-pane"|"new-pane"|"popout-window"|"new-tab"|"md-properties";
* @returns
*/
getLeaf(origo: WorkspaceLeaf, targetPane?: PaneTarget): WorkspaceLeaf;
/**
* Returns the editor or leaf.view of the currently active embedded obsidian file.
* If view is not provided, ea.targetView is used.
* If the embedded file is a markdown document the function will return
* {file:TFile, editor:Editor} otherwise it will return {view:any}. You can check view type with view.getViewType();
* @param view
* @returns
*/
getActiveEmbeddableViewOrEditor(view?: ExcalidrawView): {
view: any;
} | {
file: TFile;
editor: Editor;
} | null;
plugin: ExcalidrawPlugin;
elementsDict: {
[key: string]: any;
};
imagesDict: {
[key: FileId]: any;
};
mostRecentMarkdownSVG: SVGSVGElement;
style: {
strokeColor: string;
backgroundColor: string;
angle: number;
fillStyle: FillStyle;
strokeWidth: number;
strokeStyle: StrokeStyle;
roughness: number;
opacity: number;
strokeSharpness?: StrokeRoundness;
roundness: null | {
type: RoundnessType;
value?: number;
};
fontFamily: number;
fontSize: number;
textAlign: string;
verticalAlign: string;
startArrowHead: string;
endArrowHead: string;
};
canvas: {
theme: string;
viewBackgroundColor: string;
gridSize: number;
};
colorPalette: {};
constructor(plugin: ExcalidrawPlugin, view?: ExcalidrawView);
/**
*
* @returns the last recorded pointer position on the Excalidraw canvas
*/
getViewLastPointerPosition(): {
x: number;
y: number;
};
/**
*
* @returns
*/
getAPI(view?: ExcalidrawView): ExcalidrawAutomate;
/**
* @param val //0:"hachure", 1:"cross-hatch" 2:"solid"
* @returns
*/
setFillStyle(val: number): "hachure" | "cross-hatch" | "solid";
/**
* @param val //0:"solid", 1:"dashed", 2:"dotted"
* @returns
*/
setStrokeStyle(val: number): "solid" | "dashed" | "dotted";
/**
* @param val //0:"round", 1:"sharp"
* @returns
*/
setStrokeSharpness(val: number): "round" | "sharp";
/**
* @param val //1: Virgil, 2:Helvetica, 3:Cascadia
* @returns
*/
setFontFamily(val: number): "Virgil, Segoe UI Emoji" | "Helvetica, Segoe UI Emoji" | "Cascadia, Segoe UI Emoji" | "LocalFont";
/**
* @param val //0:"light", 1:"dark"
* @returns
*/
setTheme(val: number): "light" | "dark";
/**
* @param objectIds
* @returns
*/
addToGroup(objectIds: string[]): string;
/**
* @param templatePath
*/
toClipboard(templatePath?: string): Promise<void>;
/**
* @param file: TFile
* @returns ExcalidrawScene
*/
getSceneFromFile(file: TFile): Promise<{
elements: ExcalidrawElement[];
appState: AppState;
}>;
/**
* get all elements from ExcalidrawAutomate elementsDict
* @returns elements from elementsDict
*/
getElements(): ExcalidrawElement[];
/**
* get single element from ExcalidrawAutomate elementsDict
* @param id
* @returns
*/
getElement(id: string): ExcalidrawElement;
/**
* create a drawing and save it to filename
* @param params
* filename: if null, default filename as defined in Excalidraw settings
* foldername: if null, default folder as defined in Excalidraw settings
* @returns
*/
create(params?: {
filename?: string;
foldername?: string;
templatePath?: string;
onNewPane?: boolean;
frontmatterKeys?: {
"excalidraw-plugin"?: "raw" | "parsed";
"excalidraw-link-prefix"?: string;
"excalidraw-link-brackets"?: boolean;
"excalidraw-url-prefix"?: string;
"excalidraw-export-transparent"?: boolean;
"excalidraw-export-dark"?: boolean;
"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;
}): Promise<string>;
/**
*
* @param templatePath
* @param embedFont
* @param exportSettings use ExcalidrawAutomate.getExportSettings(boolean,boolean)
* @param loader use ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?)
* @param theme
* @returns
*/
createSVG(templatePath?: string, embedFont?: boolean, exportSettings?: ExportSettings, loader?: EmbeddedFilesLoader, theme?: string, padding?: number): Promise<SVGSVGElement>;
/**
*
* @param templatePath
* @param scale
* @param exportSettings use ExcalidrawAutomate.getExportSettings(boolean,boolean)
* @param loader use ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?)
* @param theme
* @returns
*/
createPNG(templatePath?: string, scale?: number, exportSettings?: ExportSettings, loader?: EmbeddedFilesLoader, theme?: string, padding?: number): Promise<any>;
/**
*
* @param text
* @param lineLen
* @returns
*/
wrapText(text: string, lineLen: number): string;
private boxedElement;
addIFrame(topX: number, topY: number, width: number, height: number, url?: string, file?: TFile): string;
/**
*
* @param topX
* @param topY
* @param width
* @param height
* @returns
*/
addEmbeddable(topX: number, topY: number, width: number, height: number, url?: string, file?: TFile): string;
/**
*
* @param topX
* @param topY
* @param width
* @param height
* @returns
*/
addRect(topX: number, topY: number, width: number, height: number): string;
/**
*
* @param topX
* @param topY
* @param width
* @param height
* @returns
*/
addDiamond(topX: number, topY: number, width: number, height: number): string;
/**
*
* @param topX
* @param topY
* @param width
* @param height
* @returns
*/
addEllipse(topX: number, topY: number, width: number, height: number): string;
/**
*
* @param topX
* @param topY
* @param width
* @param height
* @returns
*/
addBlob(topX: number, topY: number, width: number, height: number): string;
/**
* Refresh the size of a text element to fit its contents
* @param id - the id of the text element
*/
refreshTextElementSize(id: string): void;
/**
*
* @param topX
* @param topY
* @param text
* @param formatting
* box: if !null, text will be boxed
* @param id
* @returns
*/
addText(topX: number, topY: number, text: string, formatting?: {
wrapAt?: number;
width?: number;
height?: number;
textAlign?: "left" | "center" | "right";
box?: boolean | "box" | "blob" | "ellipse" | "diamond";
boxPadding?: number;
boxStrokeColor?: string;
textVerticalAlign?: "top" | "middle" | "bottom";
}, id?: string): string;
/**
*
* @param points
* @returns
*/
addLine(points: [[x: number, y: number]]): string;
/**
*
* @param points
* @param formatting
* @returns
*/
addArrow(points: [x: number, y: number][], formatting?: {
startArrowHead?: string;
endArrowHead?: string;
startObjectId?: string;
endObjectId?: string;
}): string;
/**
*
* @param topX
* @param topY
* @param imageFile
* @returns
*/
addImage(topX: number, topY: number, imageFile: TFile | string, scale?: boolean, //default is true which will scale the image to MAX_IMAGE_SIZE, false will insert image at 100% of its size
anchor?: boolean): Promise<string>;
/**
*
* @param topX
* @param topY
* @param tex
* @returns
*/
addLaTex(topX: number, topY: number, tex: string): Promise<string>;
/**
*
* @param objectA
* @param connectionA type ConnectionPoint = "top" | "bottom" | "left" | "right" | null
* @param objectB
* @param connectionB when passed null, Excalidraw will automatically decide
* @param formatting
* numberOfPoints: points on the line. Default is 0 ie. line will only have a start and end point
* startArrowHead: "triangle"|"dot"|"arrow"|"bar"|null
* endArrowHead: "triangle"|"dot"|"arrow"|"bar"|null
* padding:
* @returns
*/
connectObjects(objectA: string, connectionA: ConnectionPoint | null, objectB: string, connectionB: ConnectionPoint | null, formatting?: {
numberOfPoints?: number;
startArrowHead?: "triangle" | "dot" | "arrow" | "bar" | null;
endArrowHead?: "triangle" | "dot" | "arrow" | "bar" | null;
padding?: number;
}): string;
/**
* Adds a text label to a line or arrow. Currently only works with a straight (2 point - start & end - line)
* @param lineId id of the line or arrow object in elementsDict
* @param label the label text
* @returns undefined (if unsuccessful) or the id of the new text element
*/
addLabelToLine(lineId: string, label: string): string;
/**
* clear elementsDict and imagesDict only
*/
clear(): void;
/**
* clear() + reset all style values to default
*/
reset(): void;
/**
* returns true if MD file is an Excalidraw file
* @param f
* @returns
*/
isExcalidrawFile(f: TFile): boolean;
targetView: ExcalidrawView;
/**
* 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 targetView
*/
setView(view?: ExcalidrawView | "first" | "active"): ExcalidrawView;
/**
*
* @returns https://github.com/excalidraw/excalidraw/tree/master/src/packages/excalidraw#ref
*/
getExcalidrawAPI(): any;
/**
* get elements in View
* @returns
*/
getViewElements(): ExcalidrawElement[];
/**
*
* @param elToDelete
* @returns
*/
deleteViewElements(elToDelete: ExcalidrawElement[]): boolean;
/**
* get the selected element in the view, if more are selected, get the first
* @returns
*/
getViewSelectedElement(): any;
/**
*
* @returns
*/
getViewSelectedElements(): any[];
/**
*
* @param el
* @returns TFile file handle for the image element
*/
getViewFileForImageElement(el: ExcalidrawElement): TFile | null;
/**
* copies elements from view to elementsDict for editing
* @param elements
*/
copyViewElementsToEAforEditing(elements: ExcalidrawElement[]): void;
/**
*
* @param forceViewMode
* @returns
*/
viewToggleFullScreen(forceViewMode?: boolean): void;
setViewModeEnabled(enabled: boolean): void;
/**
* 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): void;
/**
* connect an object to the selected element in the view
* @param objectA ID of the element
* @param connectionA
* @param connectionB
* @param formatting
* @returns
*/
connectObjectWithViewSelectedElement(objectA: string, connectionA: ConnectionPoint | null, connectionB: ConnectionPoint | null, formatting?: {
numberOfPoints?: number;
startArrowHead?: "triangle" | "dot" | "arrow" | "bar" | null;
endArrowHead?: "triangle" | "dot" | "arrow" | "bar" | null;
padding?: number;
}): boolean;
/**
* 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;
/**
* Adds elements from elementsDict to the current view
* @param repositionToCursor default is false
* @param save default is true
* @param newElementsOnTop controls whether elements created with ExcalidrawAutomate
* are added at the bottom of the stack or the top of the stack of elements already in the view
* Note that elements copied to the view with copyViewElementsToEAforEditing retain their
* position in the stack of elements in the view even if modified using EA
* default is false, i.e. the new elements get to the bottom of the stack
* @param shouldRestoreElements - restore elements - auto-corrects broken, incomplete or old elements included in the update
* @returns
*/
addElementsToView(repositionToCursor?: boolean, save?: boolean, newElementsOnTop?: boolean, shouldRestoreElements?: boolean): Promise<boolean>;
/**
* Register instance of EA to use for hooks with TargetView
* By default ExcalidrawViews will check window.ExcalidrawAutomate for event hooks.
* Using this event you can set a different instance of Excalidraw Automate for hooks
* @returns true if successful
*/
registerThisAsViewEA(): boolean;
/**
* Sets the targetView EA to window.ExcalidrawAutomate
* @returns true if successful
*/
deregisterThisAsViewEA(): boolean;
/**
* If set, this callback is triggered when the user closes an Excalidraw view.
*/
onViewUnloadHook: (view: ExcalidrawView) => void;
/**
* If set, this callback is triggered, when the user changes the view mode.
* You can use this callback in case you want to do something additional when the user switches to view mode and back.
*/
onViewModeChangeHook: (isViewModeEnabled: boolean, view: ExcalidrawView, ea: ExcalidrawAutomate) => void;
/**
* If set, this callback is triggered, when the user hovers a link in the scene.
* You can use this callback in case you want to do something additional when the onLinkHover event occurs.
* This callback must return a boolean value.
* In case you want to prevent the excalidraw onLinkHover action you must return false, it will stop the native excalidraw onLinkHover management flow.
*/
onLinkHoverHook: (element: NonDeletedExcalidrawElement, linkText: string, view: ExcalidrawView, ea: ExcalidrawAutomate) => boolean;
/**
* If set, this callback is triggered, when the user clicks a link in the scene.
* You can use this callback in case you want to do something additional when the onLinkClick event occurs.
* This callback must return a boolean value.
* In case you want to prevent the excalidraw onLinkClick action you must return false, it will stop the native excalidraw onLinkClick management flow.
*/
onLinkClickHook: (element: ExcalidrawElement, linkText: string, event: MouseEvent, view: ExcalidrawView, ea: ExcalidrawAutomate) => boolean;
/**
* If set, this callback is triggered, when Excalidraw receives an onDrop event.
* You can use this callback in case you want to do something additional when the onDrop event occurs.
* This callback must return a boolean value.
* In case you want to prevent the excalidraw onDrop action you must return false, it will stop the native excalidraw onDrop management flow.
*/
onDropHook: (data: {
ea: ExcalidrawAutomate;
event: React.DragEvent<HTMLDivElement>;
draggable: any;
type: "file" | "text" | "unknown";
payload: {
files: TFile[];
text: string;
};
excalidrawFile: TFile;
view: ExcalidrawView;
pointerPosition: {
x: number;
y: number;
};
}) => boolean;
/**
* If set, this callback is triggered, when Excalidraw receives an onPaste event.
* You can use this callback in case you want to do something additional when the
* onPaste event occurs.
* This callback must return a boolean value.
* In case you want to prevent the excalidraw onPaste action you must return false,
* it will stop the native excalidraw onPaste management flow.
*/
onPasteHook: (data: {
ea: ExcalidrawAutomate;
payload: ClipboardData;
event: ClipboardEvent;
excalidrawFile: TFile;
view: ExcalidrawView;
pointerPosition: {
x: number;
y: number;
};
}) => boolean;
/**
* if set, this callback is triggered, when an Excalidraw file is opened
* You can use this callback in case you want to do something additional when the file is opened.
* This will run before the file level script defined in the `excalidraw-onload-script` frontmatter.
*/
onFileOpenHook: (data: {
ea: ExcalidrawAutomate;
excalidrawFile: TFile;
view: ExcalidrawView;
}) => Promise<void>;
/**
* if set, this callback is triggered, when an Excalidraw file is created
* see also: https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1124
*/
onFileCreateHook: (data: {
ea: ExcalidrawAutomate;
excalidrawFile: TFile;
view: ExcalidrawView;
}) => Promise<void>;
/**
* If set, this callback is triggered whenever the active canvas color changes
*/
onCanvasColorChangeHook: (ea: ExcalidrawAutomate, view: ExcalidrawView, //the excalidraw view
color: string) => void;
/**
* utility function to generate EmbeddedFilesLoader object
* @param isDark
* @returns
*/
getEmbeddedFilesLoader(isDark?: boolean): EmbeddedFilesLoader;
/**
* utility function to generate ExportSettings object
* @param withBackground
* @param withTheme
* @returns
*/
getExportSettings(withBackground: boolean, withTheme: boolean): ExportSettings;
/**
* get bounding box of elements
* bounding box is the box encapsulating all of the elements completely
* @param elements
* @returns
*/
getBoundingBox(elements: ExcalidrawElement[]): {
topX: number;
topY: number;
width: number;
height: number;
};
/**
* elements grouped by the highest level groups
* @param elements
* @returns
*/
getMaximumGroups(elements: ExcalidrawElement[]): ExcalidrawElement[][];
/**
* gets the largest element from a group. useful when a text element is grouped with a box, and you want to connect an arrow to the box
* @param elements
* @returns
*/
getLargestElement(elements: ExcalidrawElement[]): ExcalidrawElement;
/**
* @param element
* @param a
* @param b
* @param gap
* @returns 2 or 0 intersection points between line going through `a` and `b`
* and the `element`, in ascending order of distance from `a`.
*/
intersectElementWithLine(element: ExcalidrawBindableElement, a: readonly [number, number], b: readonly [number, number], gap?: number): Point[];
/**
* 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[];
/**
* Gets all the elements from elements[] that are contained in the frame.
* @param element
* @param elements - typically all the non-deleted elements in the scene
* @returns
*/
getElementsInFrame(frameElement: ExcalidrawElement, elements: ExcalidrawElement[]): ExcalidrawElement[];
/**
* See OCR plugin for example on how to use scriptSettings
* Set by the ScriptEngine
*/
activeScript: string;
/**
*
* @returns script settings. Saves settings in plugin settings, under the activeScript key
*/
getScriptSettings(): {};
/**
* sets script settings.
* @param settings
* @returns
*/
setScriptSettings(settings: any): Promise<void>;
/**
* Open a file in a new workspaceleaf or reuse an existing adjacent leaf depending on Excalidraw Plugin Settings
* @param file
* @param openState - if not provided {active: true} will be used
* @returns
*/
openFileInNewOrAdjacentLeaf(file: TFile, openState?: OpenViewState): WorkspaceLeaf;
/**
* measure text size based on current style settings
* @param text
* @returns
*/
measureText(text: string): {
width: number;
height: number;
};
/**
* Returns the size of the image element at 100% (i.e. the original size)
* @param imageElement an image element from the active scene on targetView
*/
getOriginalImageSize(imageElement: ExcalidrawImageElement): Promise<{
width: number;
height: number;
}>;
/**
* verifyMinimumPluginVersion returns true if plugin version is >= than required
* recommended use:
* if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.20")) {new Notice("message");return;}
* @param requiredVersion
* @returns
*/
verifyMinimumPluginVersion(requiredVersion: string): boolean;
/**
* Check if view is instance of ExcalidrawView
* @param view
* @returns
*/
isExcalidrawView(view: any): boolean;
/**
* sets selection in view
* @param elements
* @returns
*/
selectElementsInView(elements: ExcalidrawElement[] | string[]): void;
/**
* @returns an 8 character long random id
*/
generateElementId(): string;
/**
* @param element
* @returns a clone of the element with a new id
*/
cloneElement(element: ExcalidrawElement): ExcalidrawElement;
/**
* Moves the element to a specific position in the z-index
*/
moveViewElementToZIndex(elementId: number, newZIndex: number): void;
/**
* Deprecated. Use getCM / ColorMaster instead
* @param color
* @returns
*/
hexStringToRgb(color: string): number[];
/**
* Deprecated. Use getCM / ColorMaster instead
* @param color
* @returns
*/
rgbToHexString(color: number[]): string;
/**
* Deprecated. Use getCM / ColorMaster instead
* @param color
* @returns
*/
hslToRgb(color: number[]): number[];
/**
* Deprecated. Use getCM / ColorMaster instead
* @param color
* @returns
*/
rgbToHsl(color: number[]): number[];
/**
*
* @param color
* @returns
*/
colorNameToHex(color: string): string;
/**
* https://github.com/lbragile/ColorMaster
* @param color
* @returns
*/
getCM(color: TInput): ColorMaster;
importSVG(svgString: string): boolean;
}
export declare function initExcalidrawAutomate(plugin: ExcalidrawPlugin): Promise<ExcalidrawAutomate>;
export declare function destroyExcalidrawAutomate(): void;
export declare function _measureText(newText: string, fontSize: number, fontFamily: number, lineHeight: number): {
w: number;
h: number;
baseline: number;
};
export declare const generatePlaceholderDataURL: (width: number, height: number) => DataURL;
export declare function createPNG(templatePath: string, scale: number, exportSettings: ExportSettings, loader: EmbeddedFilesLoader, forceTheme: string, canvasTheme: string, canvasBackgroundColor: string, automateElements: ExcalidrawElement[], plugin: ExcalidrawPlugin, depth: number, padding?: number, imagesDict?: any): Promise<Blob>;
export declare function createSVG(templatePath: string, embedFont: boolean, exportSettings: ExportSettings, loader: EmbeddedFilesLoader, forceTheme: string, canvasTheme: string, canvasBackgroundColor: string, automateElements: ExcalidrawElement[], plugin: ExcalidrawPlugin, depth: number, padding?: number, imagesDict?: any, convertMarkdownLinksToObsidianURLs?: boolean): Promise<SVGSVGElement>;
export declare function estimateBounds(elements: ExcalidrawElement[]): [number, number, number, number];
export declare function repositionElementsToCursor(elements: ExcalidrawElement[], newPosition: {
x: number;
y: number;
}, center: boolean, api: ExcalidrawImperativeAPI): ExcalidrawElement[];
export declare const insertLaTeXToView: (view: ExcalidrawView) => void;
export declare const search: (view: ExcalidrawView) => Promise<void>;
/**
*
* @param elements
* @param query
* @param exactMatch - when searching for section header exactMatch should be set to true
* @returns the elements matching the query
*/
export declare const getTextElementsMatchingQuery: (elements: ExcalidrawElement[], query: string[], exactMatch?: boolean) => ExcalidrawElement[];
/**
*
* @param elements
* @param query
* @param exactMatch - when searching for section header exactMatch should be set to true
* @returns the elements matching the query
*/
export declare const getFrameElementsMatchingQuery: (elements: ExcalidrawElement[], query: string[], exactMatch?: boolean) => ExcalidrawElement[];
export declare const cloneElement: (el: ExcalidrawElement) => any;
export declare const verifyMinimumPluginVersion: (requiredVersion: string) => boolean;
```

View File

@@ -0,0 +1,19 @@
# [◀ Excalidraw 自动化指南](../readme.md)
## 画布样式设置
设置画布的属性。
### theme, setTheme()
字符串。有效值为 "light"(明亮)和 "dark"(黑暗)。
`setTheme()` 接受一个数字参数:
- 0"light"(明亮)
- 其他任何数字:"dark"(黑暗)
### viewBackgroundColor
字符串。这是对象的填充颜色。[CSS 合法颜色值](https://www.w3schools.com/cssref/css_colors_legal.asp)
允许的值包括 [HTML 颜色名称](https://www.w3schools.com/colors/colors_names.asp)、十六进制 RGB 字符串(例如红色使用 `#FF0000`)或 `transparent`(透明)。
### gridSize
数字。网格的大小。如果设置为零,则不显示网格。

View File

@@ -0,0 +1,110 @@
# [◀ Excalidraw 自动化指南](../readme.md)
## 元素样式设置
你会注意到一些样式有setter函数。这是为了帮助你设置属性的允许值。但是你不必使用setter函数也可以直接设置值。
### strokeColor
字符串类型。线条的颜色。[CSS合法颜色值](https://www.w3schools.com/cssref/css_colors_legal.asp)
允许的值包括[HTML颜色名称](https://www.w3schools.com/colors/colors_names.asp)、十六进制RGB字符串例如红色为`#FF0000`
### backgroundColor
字符串类型。这是对象的填充颜色。[CSS合法颜色值](https://www.w3schools.com/cssref/css_colors_legal.asp)
允许的值包括[HTML颜色名称](https://www.w3schools.com/colors/colors_names.asp)、十六进制RGB字符串例如红色为`#FF0000`,或者`transparent`(透明)。
### angle
数字类型。以弧度为单位的旋转角度。90度等于`Math.PI/2`
### fillStyle, setFillStyle()
```typescript
type FillStyle = "hachure" | "cross-hatch" | "solid";
setFillStyle (val:number);
```
fillStyle 是一个字符串。
`setFillStyle()` 接受一个数字参数:
- 0: "hachure"(斜线填充)
- 1: "cross-hatch"(交叉填充)
- 其他任意数字: "solid"(实心填充)
### strokeWidth
数字类型。设置线条的宽度。
### strokeStyle, setStrokeStyle()
```typescript
type StrokeStyle = "solid" | "dashed" | "dotted";
setStrokeStyle (val:number);
```
strokeStyle 是一个字符串。
`setStrokeStyle()` 接受一个数字参数:
- 0: "solid"(实线)
- 1: "dashed"(虚线)
- 其他任意数字: "dotted"(点线)
### roughness
数字类型。在 Excalidraw 中称为随意度。接受三个值:
- 0建筑师风格
- 1艺术家风格
- 2卡通家风格
### opacity
数字类型,取值范围在 0~100 之间。用于设置对象的不透明度,同时影响线条和填充的透明度。
### strokeSharpness, setStrokeSharpness()
```typescript
type StrokeSharpness = "round" | "sharp";
setStrokeSharpness(val:number);
```
strokeSharpness 是一个字符串。
"round"(圆滑)线条是弯曲的,"sharp"(尖锐)线条在转折点处会形成尖角。
`setStrokeSharpness()` 接受一个数字参数:
- 0"round"(圆滑)
- 其他任意数字:"sharp"(尖锐)
### fontFamily, setFontFamily()
数字类型。有效值为 1、2 和 3。
`setFontFamily()` 也接受一个数字参数并返回字体名称:
- 1: "Virgil, Segoe UI Emoji"
- 2: "Helvetica, Segoe UI Emoji"
- 3: "Cascadia, Segoe UI Emoji"
### fontSize
数字类型。默认值为 20px。
### textAlign
字符串类型。文本的水平对齐方式。有效值为 "left"(左对齐)、"center"(居中对齐)、"right"(右对齐)。
这在使用 `addText()` 函数设置固定宽度时很有用。
### verticalAlign
字符串类型。文本的垂直对齐方式。有效值为 "top"(顶部对齐)和 "middle"(居中对齐)。
这在使用 `addText()` 函数设置固定高度时很有用。
### startArrowHead, endArrowHead
字符串类型。有效值为 "arrow"(箭头)、"bar"(线段)、"dot"(圆点)和 "none"(无)。用于指定箭头的起始和结束样式。
这在使用 `addArrow()``connectObjects()` 函数时很有用。

View File

@@ -0,0 +1,113 @@
# [◀ Excalidraw Automate 使用指南](../readme.md)
## API 介绍
你可以通过 ExcalidrawAutomate 对象来访问 Excalidraw Automate。我建议在 Templater、DataView 和 QuickAdd 脚本中使用以下代码开始:
*使用 <kbd>CTRL+Shift+V</kbd> 将代码粘贴到 Obsidian 中!*
```javascript
const ea = ExcalidrawAutomate;
ea.reset();
```
第一行创建了一个常量,这样你就可以避免重复写 ExcalidrawAutomate 上百次。
第二行将 ExcalidrawAutomate 重置为默认值。这一步很重要,因为你不会知道之前执行了哪个模板,因此也不知道 Excalidraw 处于什么状态。
**⚠ 注意:** 如果你正在使用 Excalidraw 插件内置的[脚本引擎](../ExcalidrawScriptsEngine.md),引擎会自动处理 `ea` 对象的初始化。
### Excalidraw Automate 的基本使用逻辑
1. 设置要绘制元素的样式
2. 添加元素。当你添加元素时,每个新元素都会被添加到前一个元素的上层,因此在元素重叠的情况下,后添加的元素会显示在先添加元素的上方。
3. 调用 `await ea.create();` 来实例化绘图,或使用 `ea.setView();` 后跟 `ea.addElementsToView();` 将元素添加到现有视图中,或使用 `await ea.createSVG();``await ea.createPNG();` 从你的元素创建 PNG 或 SVG 图像。
你可以在添加不同元素之间改变样式。我将元素样式和创建分开的逻辑基于这样一个假设:你可能会设置一个描边颜色、描边样式、描边粗糙度等,并使用这些设置来绘制大多数元素。每次添加元素时都重新设置所有这些参数是没有意义的。
### 在深入了解之前,这里有三个简单的 [Templater](https://github.com/SilentVoid13/Templater) 脚本示例
#### 使用模板在自定义文件夹中创建具有自定义名称的新绘图
这个简单的脚本相比 Excalidraw 插件设置提供了更大的灵活性,让你可以为绘图命名、将其放入文件夹中并应用模板。
*使用 <kbd>CTRL+Shift+V</kbd> 将代码粘贴到 Obsidian 中!*
```javascript
<%*
const ea = ExcalidrawAutomate;
ea.reset();
await ea.create({
filename : tp.date.now("HH.mm"),
foldername : tp.date.now("YYYY-MM-DD"),
templatePath: "Excalidraw/Template1.excalidraw",
onNewPane : false
});
%>
```
#### 创建一个简单的绘图
*使用 <kbd>CTRL+Shift+V</kbd> 将代码粘贴到 Obsidian 中!*
```javascript
<%*
const ea = ExcalidrawAutomate;
ea.reset();
ea.addRect(-150,-50,450,300);
ea.addText(-100,70,"Left to right");
ea.addArrow([[-100,100],[100,100]]);
ea.style.strokeColor = "red";
ea.addText(100,-30,"top to bottom",{width:200,textAligh:"center"});
ea.addArrow([[200,0],[200,200]]);
await ea.create();
%>
```
该脚本将生成以下绘图:
![FristDemo](https://user-images.githubusercontent.com/14358394/116825643-6e5a8b00-ab90-11eb-9e3a-37c524620d0d.png)
#### 在打开的 Excalidraw 视图中添加一个带框的文本元素
将新元素放置在当前选中元素的下方,并用箭头从选中的元素指向新添加的文本。
*使用 <kbd>CTRL+Shift+V</kbd> 将代码粘贴到 Obsidian 中!*
```javascript
<%*
const ea = ExcalidrawAutomate;
ea.reset();
ea.setView("first");
selectedElement = ea.getViewSelectedElement();
ea.setStrokeSharpness(0);
const boxPadding = 5;
id = ea.addText(
selectedElement.x + boxPadding,
selectedElement.y+selectedElement.height+100,
"[[Next process step]]",
{
textAlign:"center",
box:true,
boxPadding:boxPadding,
width:selectedElement.width-boxPadding*2,
}
);
ea.setStrokeSharpness(1);
ea.style.roughness= 0;
ea.connectObjectWithViewSelectedElement(
id,
"top",
"bottom",
{
numberOfPoints:2,
startArrowHead:"arrow",
endArrowHead:"dot",
padding:5
});
ea.addElementsToView();
%>
```
[点击此处查看动画演示](https://user-images.githubusercontent.com/14358394/131967188-2a488e38-f742-49d9-ae98-33238a8d4712.mp4)

View File

@@ -0,0 +1,94 @@
# [◀ Excalidraw 自动化使用指南](../readme.md)
## 添加对象
这些函数将向你的绘图中添加对象。画布是无限的,可以接受负数和正数的 X 和 Y 值。X 值从左到右递增Y 值从上到下递增。
![坐标系](https://user-images.githubusercontent.com/14358394/116825632-6569b980-ab90-11eb-827b-ada598e91e46.png)
### addRect(), addDiamond(), addEllipse()
```typescript
addRect(topX:number, topY:number, width:number, height:number):string
addDiamond(topX:number, topY:number, width:number, height:number):string
addEllipse(topX:number, topY:number, width:number, height:number):string
```
返回对象的 `id`。当使用线条连接对象时需要用到这个 `id`,详见后文。
### addText()
```typescript
addText(
topX:number,
topY:number,
text:string,
formatting?:{
wrapAt?:number,
width?:number,
height?:number,
textAlign?:string,
box?: "box"|"blob"|"ellipse"|"diamond",
boxPadding?:number
},
id?:string
):string
```
向绘图中添加文本。
格式化参数是可选的:
- 如果未指定 `width``height`函数将根据字体系列fontFamily、字体大小fontSize和提供的文本来计算宽度和高度。
- 如果你想要将文本相对于绘图中的其他元素居中对齐,你可以提供固定的高度和宽度,并且可以像上面描述的那样指定 `textAlign``verticalAlign`。例如:`{width:500, textAlign:"center"}`
- 如果你想在文本周围添加一个框,设置 `{box:"box"|"blob"|"ellipse"|"diamond"}`(分别对应矩形框、气泡框、椭圆框、菱形框)
返回对象的 `id`。当使用线条连接对象时需要用到这个 `id`,详见后文。如果设置了 `{box:}`,则返回包围框对象的 id。
### addLine()
```typescript
addLine(points: [[x:number,y:number]]):string
```
根据提供的点添加一条线。必须包含至少两个点 `points.length >= 2`。如果提供了超过 2 个点,中间点将被添加为断点。当 `strokeSharpness` 设置为 "sharp" 时,线条会以角度方式折断;设置为 "round" 时,线条会呈现曲线。
返回对象的 `id`
### addArrow()
```typescript
addArrow(points: [[x:number,y:number]],formatting?:{startArrowHead?:string,endArrowHead?:string,startObjectId?:string,endObjectId?:string}):string ;
```
根据提供的点添加一个箭头。必须包含至少两个点 `points.length >= 2`。如果提供了超过 2 个点,中间点将被添加为断点。当元素的 `style.strokeSharpness` 设置为 "sharp" 时,线条会以角度方式折断;设置为 "round" 时,线条会呈现曲线。
`startArrowHead``endArrowHead` 指定要使用的箭头类型,如上所述。有效值包括 "none"(无)、"arrow"(箭头)、"dot"(圆点)和 "bar"(线条)。例如:`{startArrowHead: "dot", endArrowHead: "arrow"}`
`startObjectId``endObjectId` 是连接对象的 ID。如果是为了连接对象建议使用 `connectObjects` 而不是调用 addArrow()。
返回对象的 `id`
### connectObjects()
```typescript
declare type ConnectionPoint = "top"|"bottom"|"left"|"right";
connectObjects(objectA: string, connectionA: ConnectionPoint, objectB: string, connectionB: ConnectionPoint, formatting?:{numberOfPoints: number,startArrowHead:string,endArrowHead:string, padding: number}):void
```
使用箭头连接两个对象。如果两个元素中的任何一个是 `line`(线条)、`arrow`(箭头)或 `freedraw`(自由绘制)类型,则不会执行任何操作。
`objectA``objectB` 是字符串类型,表示要连接的对象的 ID。这些 ID 是在创建对象时由 addRect()、addDiamond()、addEllipse() 和 addText() 函数返回的。
`connectionA``connectionB` 指定在对象上的连接位置。有效值包括:"top"(顶部)、"bottom"(底部)、"left"(左侧)和 "right"(右侧)。
`numberOfPoints` 设置线条的中间断点数量。默认值为零,表示箭头的起点和终点之间没有断点。当在绘图上移动对象时,这些断点会影响 Excalidraw 重新路由线条的方式。
`startArrowHead``endArrowHead` 的工作方式如上文 `addArrow()` 中所述。
### addToGroup()
```typescript
addToGroup(objectIds:[]):string
```
`objectIds` 中列出的对象组合成一个组。返回该组的 `id`

View File

@@ -0,0 +1,219 @@
# [◀ Excalidraw Automate How To](../readme.md)
## 实用工具函数
### isExcalidrawFile()
```typescript
isExcalidrawFile(f:TFile): boolean
```
如果提供的文件是有效的 Excalidraw 文件(可以是传统的 `*.excalidraw` 文件或在 front-matter 中包含 excalidraw 键的 markdown 文件),则返回 true。
### clear()
`clear()` 将清除缓存中的对象,但会保留元素样式设置。
### reset()
`reset()` 会先调用 `clear()`,然后将元素样式重置为默认值。
### toClipboard()
```typescript
async toClipboard(templatePath?:string)
```
将生成的绘图放置到剪贴板中。当你不想创建新的绘图,而是想将额外的元素粘贴到现有绘图上时,这个功能很有用。
### getElements()
```typescript
getElements():ExcalidrawElement[];
```
以数组形式返回 ExcalidrawAutomate 中的 ExcalidrawElement 元素。这种格式在使用 ExcalidrawRef 时特别有用。
### getElement()
```typescript
getElement(id:string):ExcalidrawElement;
```
返回与指定 id 匹配的元素对象。如果元素不存在,则返回 null。
### create()
```typescript
async create(params?:{filename: string, foldername:string, templatePath:string, onNewPane: boolean})
```
创建并打开绘图。返回创建文件的完整路径。
`filename` 是要创建的绘图文件名(不包含扩展名)。如果为 `null`Excalidraw 将自动生成文件名。
`foldername` 是文件创建的目标文件夹。如果为 `null`,则会根据 Excalidraw 设置使用默认的新建绘图文件夹。
`templatePath` 是模板文件的完整路径(包含文件名和扩展名)。该模板文件将作为基础图层,所有通过 ExcalidrawAutomate 添加的对象都会显示在模板元素的上层。如果为 `null`,则不使用模板,即使用空白画布作为添加对象的基础。
`onNewPane` 定义新绘图的创建位置。`false` 将在当前活动页签中打开绘图。`true` 将通过垂直分割当前页签来打开绘图。
`frontmatterKeys` 是要应用到文档的 frontmatter 键值集合
{
excalidraw-plugin?: "raw"|"parsed",
excalidraw-link-prefix?: string,
excalidraw-link-brackets?: boolean,
excalidraw-url-prefix?: string
}
示例:
```javascript
create (
{
filename:"my drawing",
foldername:"myfolder/subfolder/",
templatePath: "Excalidraw/template.excalidraw",
onNewPane: true,
frontmatterKeys: {
"excalidraw-plugin": "parsed",
"excalidraw-link-prefix": "",
"excalidraw-link-brackets": true,
"excalidraw-url-prefix": "🌐",
}
}
);
```
### createSVG()
```typescript
async createSVG(templatePath?:string)
```
返回包含生成绘图的 HTML SVGSVGElement 元素。
### createPNG()
```typescript
async createPNG(templatePath?:string, scale:number=1)
```
返回包含生成绘图的 PNG 图像 blob 对象。
### wrapText()
```typescript
wrapText(text:string, lineLen:number):string
```
返回一个按照指定最大行长度换行的字符串。
### 访问打开的 Excalidraw 视图
在使用任何视图操作函数之前,你需要先初始化 targetView。
#### targetView
```typescript
targetView: ExcalidrawView
```
已打开的 Excalidraw 视图,被配置为视图操作的目标。使用 `setView` 进行初始化。
#### setView()
```typescript
setView(view:ExcalidrawView|"first"|"active"):ExcalidrawView
```
设置将作为视图操作目标的 ExcalidrawView。有效的 `view` 输入值包括:
- ExcalidrawView 的对象实例
- "first":如果打开了多个 Excalidraw 视图,则选择 `app.workspace.getLeavesOfType("Excalidraw")` 返回的第一个视图
- "active":表示当前活动的视图
#### getExcalidrawAPI()
```typescript
getExcalidrawAPI():any
```
返回在 `targetView` 中指定的当前活动绘图的原生 Excalidraw APIref.current
查看 Excalidraw 文档请访问https://www.npmjs.com/package/@excalidraw/excalidraw#ref
#### getViewElements()
```typescript
getViewElements():ExcalidrawElement[]
```
返回视图中的所有元素。
#### deleteViewElements()
```typescript
deleteViewElements(elToDelete: ExcalidrawElement[]):boolean
```
从视图中删除与输入参数中提供的元素相匹配的元素。
示例:从视图中删除选中的元素:
```typescript
ea = ExcalidrawAutomate;
ea.setView("active");
el = ea.getViewSelectedElements();
ea.deleteViewElements();
```
#### getViewSelectedElement()
```typescript
getViewSelectedElement():ExcalidrawElement
```
首先需要调用 `setView()` 来设置视图。
如果在目标视图 (targetView) 中选中了一个元素,该函数将返回被选中的元素。如果选中了多个元素(通过 <kbd>SHIFT+点击</kbd> 选择多个元素,或者选择一个组),将返回第一个元素。如果你想从一个组中指定要选择的元素,请双击该组中想要的元素。
当你想要添加一个与绘图中现有元素相关的新元素时,这个函数会很有帮助。
#### getViewSelectedElements()
```typescript
getViewSelectedElements():ExcalidrawElement[]
```
首先需要调用 `setView()` 来设置视图。
获取场景中选中元素的数组。如果没有选中任何元素,则返回 []。
注意:你可以调用 `getExcalidrawAPI().getSceneElements()` 来获取场景中的所有元素。
#### viewToggleFullScreen()
```typescript
viewToggleFullScreen(forceViewMode?:boolean):void;
```
在目标视图 (targetView) 中切换全屏模式和普通模式。通过将 forceViewMode 设置为 `true` 可以将 Excalidraw 切换到查看模式。默认值为 `false`
此功能在 Obsidian 移动端上不生效。
#### connectObjectWithViewSelectedElement()
```typescript
connectObjectWithViewSelectedElement(objectA:string,connectionA: ConnectionPoint, connectionB: ConnectionPoint, formatting?:{numberOfPoints?: number,startArrowHead?:string,endArrowHead?:string, padding?: number}):boolean
```
与 `connectObjects()` 功能相同,但 ObjectB 是目标 ExcalidrawView 中当前选中的元素。该函数有助于在新创建的对象和目标 ExcalidrawView 中选中的元素之间放置一个箭头。
#### addElementsToView()
```typescript
async addElementsToView(repositionToCursor:boolean=false, save:boolean=false):Promise<boolean>
```
将使用 ExcalidrawAutomate 创建的元素添加到目标 ExcalidrawView 中。
`repositionToCursor` 默认值为 false
- true元素将被移动使其中心点与 ExcalidrawView 上当前指针的位置对齐。你可以使用此开关将元素指向并放置到绘图中的所需位置。
- false元素将按照每个元素的 x&y 坐标定义的位置进行放置。
`save` 默认值为 false
- true元素添加后绘图将被保存。
- false绘图将在下一个自动保存周期时保存。当连续添加多个元素时使用 false。否则最好使用 true 以最小化数据丢失的风险。
### onDropHook
```typescript
onDropHook (data: {
ea: ExcalidrawAutomate,
event: React.DragEvent<HTMLDivElement>,
draggable: any, //Obsidian draggable object
type: "file"|"text"|"unknown",
payload: {
files: TFile[], //TFile[] array of dropped files
text: string, //string
},
excalidrawFile: TFile, //the file receiving the drop event
view: ExcalidrawView, //the excalidraw view receiving the drop
pointerPosition: {x:number, y:number} //the pointer position on canvas at the time of drop
}):boolean;
```
当可拖拽项被拖放到 Excalidraw 上时触发的回调函数。
该函数应返回一个布尔值。如果拖放由钩子函数处理且应停止进一步的原生处理,则返回 true如果应让 Excalidraw 继续处理拖放操作,则返回 false。
拖放类型可以是以下之一:
- "file":当从 Obsidian 文件浏览器中拖放文件到 Excalidraw 时。在这种情况下payload.files 将包含被拖放文件的列表。
- "text":当拖放链接(如 URL 或 wiki 链接或其他文本时。在这种情况下payload.text 将包含接收到的字符串。
- "unknown":当 Excalidraw 插件无法识别拖放对象的类型时。在这种情况下,你可以使用 React.DragEvent 来分析拖放的对象。
使用 Templater 启动模板或类似方法来设置钩子函数。
```typescript
ea = ExcalidrawAutomate;
ea.onDropHook = (data) => {
console.log(data);
return false;
}
```

View File

@@ -0,0 +1,28 @@
# [◀ Excalidraw 自动化使用指南](../readme.md)
## 将 Excalidraw 模板应用到新绘图
这个示例与介绍中的类似只是将图形旋转了90度并且使用了模板同时指定了保存绘图的文件名和文件夹还会在新窗格中打开这个新绘图。
*使用 <kbd>CTRL+Shift+V</kbd> 将代码粘贴到 Obsidian 中!*
```javascript
<%*
const ea = ExcalidrawAutomate;
ea.reset();
ea.style.angle = Math.PI/2;
ea.style.strokeWidth = 3.5;
ea.addRect(-150,-50,450,300);
ea.addText(-100,70,"Left to right");
ea.addArrow([[-100,100],[100,100]]);
ea.style.strokeColor = "red";
await ea.addText(100,-30,"top to bottom",{width:200,textAlign:"center"});
ea.addArrow([[200,0],[200,200]]);
await ea.create({
filename :"My Drawing",
foldername :"myfolder/fordemo/",
templatePath:"Excalidraw/Template2.excalidraw",
onNewPane :true});
%>
```

View File

@@ -0,0 +1,21 @@
# [◀ Excalidraw Automate 使用指南](../readme.md)
## 连接对象
这个 [Templater](https://github.com/SilentVoid13/Templater) 模板演示了如何使用 ExcalidrawAutomate 连接两个对象。
*使用 <kbd>CTRL+Shift+V</kbd> 将代码粘贴到 Obsidian 中!*
```javascript
<%*
const ea = ExcalidrawAutomate;
ea.reset();
ea.addText(-130,-100,"Connecting two objects");
const a = ea.addRect(-100,-100,100,100);
const b = ea.addEllipse(200,200,100,100);
ea.connectObjects(a,"bottom",b,"left",{numberOfPoints: 2}); //see how the line breaks differently when moving objects around
ea.style.strokeColor = "red";
ea.connectObjects(a,"right",b,"top",1);
await ea.create();
%>
```

View File

@@ -0,0 +1,70 @@
# [◀ Excalidraw Automate 使用指南](../readme.md)
## 使用 dataviewjs 生成家谱树
这个示例与使用 dataviewjs 生成思维导图的脚本类似,但输出结果是垂直呈现的。
### 输出效果
![image](https://user-images.githubusercontent.com/14358394/117549637-d3ecc280-b03b-11eb-952a-840a9a75b6ca.png)
### 输入文件
任务列表格式如下:
```markdown
- [ ] OBSIDIAN
- [ ] Silver
- [ ] PawPaw Silv
- [ ] MawMaw Silv
- [ ] Licat
- [ ] PeePaw Li
- [ ] MeeMaw Li
```
### dataviewjs 脚本
渲染 Excalidraw 图形的代码如下:
```javascript
function crawl(subtasks) {
let size = subtasks.length > 0 ? 0 : 1; //if no children then a leaf with size 1
for (let task of subtasks) {
task["size"] = crawl(task.subtasks);
size += task.size;
}
return size;
}
const tasks = dv.page("FamilyTree.md").file.tasks[0];
tasks["size"] = crawl(tasks.subtasks);
const width = 300;
const height = 150;
const ea = ExcalidrawAutomate;
ea.reset();
function buildMindmap(subtasks, depth, offset, parentObjectID) {
if (subtasks.length == 0) return;
let task;
for (let i = 0; i < subtasks.length; i++) {
task = subtasks[i]
if (depth == 1) ea.style.strokeColor = '#'+(Math.random()*0xFFFFFF<<0).toString(16).padStart(6,"0");
task["objectID"] = ea.addText((task.size/2+offset)*width,depth*height,task.text,{box:true})
ea.connectObjects(parentObjectID,"top",task.objectID,"bottom",{startArrowHead: 'arrow', endArrowHead: 'dot'});
if (i >= 1) {
ea.connectObjects(subtasks[i-1]['objectID'],"right",task.objectID,"left", {endArrowHead: 'none'});
}
buildMindmap(task.subtasks, depth-1,offset,task.objectID);
offset += task.size/1.5;
}
}
tasks["objectID"] = ea.addText(width*1.5,height*(tasks.size-1),tasks.text,{box:true, textAlign:"center"});
buildMindmap(tasks.subtasks, 2, 0, tasks.objectID);
ea.createSVG().then((svg)=>dv.span(svg.outerHTML));
```

View File

@@ -0,0 +1,64 @@
# [◀ Excalidraw Automate 使用指南](../readme.md)
## 使用 dataviewjs 从任务列表生成思维导图
这个方法与使用 templater 生成思维导图的脚本类似,但由于 dataview 已经以树形结构返回任务,所以实现起来稍微简单一些
### 输出效果
![image](https://user-images.githubusercontent.com/14358394/117548665-71dd8e80-b036-11eb-8a45-4169fdd7cc05.png)
### 输入文件
输入文件是 `Demo.md`,其内容如下:
```markdown
- [ ] Root task
- [ ] task 1.1
- [ ] task 1.2
- [ ] task 1.2.1
- [ ] task 1.2.2
- [ ] task 1.3
- [ ] task 1.3.1
```
### dataviewjs 脚本
`dataviewjs` 脚本如下所示:
*使用 <kbd>CTRL+Shift+V</kbd> 将代码粘贴到 Obsidian 中!*
```javascript
function crawl(subtasks) {
let size = subtasks.length > 0 ? 0 : 1; //if no children then a leaf with size 1
for (let task of subtasks) {
task["size"] = crawl(task.subtasks);
size += task.size;
}
return size;
}
const tasks = dv.page("Demo.md").file.tasks[0];
tasks["size"] = crawl(tasks.subtasks);
const width = 300;
const height = 100;
const ea = ExcalidrawAutomate;
ea.reset();
function buildMindmap(subtasks, depth, offset, parentObjectID) {
if (subtasks.length == 0) return;
for (let task of subtasks) {
if (depth == 1) ea.style.strokeColor = '#'+(Math.random()*0xFFFFFF<<0).toString(16).padStart(6,"0");
task["objectID"] = ea.addText(depth*width,(task.size/2+offset)*height,task.text,{box:true})
ea.connectObjects(parentObjectID,"right",task.objectID,"left",{startArrowHead: 'dot'});
buildMindmap(task.subtasks, depth+1,offset,task.objectID);
offset += task.size;
}
}
tasks["objectID"] = ea.addText(0,(tasks.size/2)*height,tasks.text,{box:true});
buildMindmap(tasks.subtasks, 1, 0, tasks.objectID);
ea.createSVG().then((svg)=>dv.span(svg.outerHTML));
```

View File

@@ -0,0 +1,26 @@
# [◀ Excalidraw 自动化使用指南](../readme.md)
## 在当前编辑的文档中插入新绘图
这个 [Templater](https://github.com/SilentVoid13/Templater) 模板会提示你输入绘图的标题。它将使用提供的标题创建一个新的绘图,并将其保存在你正在编辑的文档所在的文件夹中。然后,它会在光标位置嵌入新绘图,并通过拆分当前页面的方式在新的工作区中打开这个绘图。
*使用 <kbd>CTRL+Shift+V</kbd> 将代码粘贴到 Obsidian 中!*
```javascript
<%*
const defaultTitle = tp.date.now("HHmm")+' '+tp.file.title;
const title = await tp.system.prompt("Title of the drawing?", defaultTitle);
const folder = tp.file.folder(true);
const transcludePath = (folder== '/' ? '' : folder + '/') + title + '.excalidraw';
tR = '![['+transcludePath+']]';
const ea = ExcalidrawAutomate;
ea.reset();
ea.setTheme(1); //set Theme to dark
await ea.create({
filename : title,
foldername : folder,
//templatePath: 'Excalidraw/Template.excalidraw', //uncomment if you want to use a template
onNewPane : true
});
%>
```

View File

@@ -0,0 +1,103 @@
# [◀ Excalidraw Automate 使用指南](../readme.md)
## 从文本大纲生成简单思维导图
这是一个稍微复杂一点的示例。它将从制表符缩进的大纲生成思维导图。
### 输出效果
![Drawing 2021-05-05 20 52 34](https://user-images.githubusercontent.com/14358394/117194124-00a69d00-ade4-11eb-8b75-5e18a9cbc3cd.png)
### 输入文件
示例输入:
```
- Test 1
- Test 1.1
- Test 2
- Test 2.1
- Test 2.2
- Test 2.2.1
- Test 2.2.2
- Test 2.2.3
- Test 2.2.3.1
- Test 3
- Test 3.1
```
### Templater 脚本
*使用 <kbd>CTRL+Shift+V</kbd> 将代码粘贴到 Obsidian 中!*
```javascript
<%*
const IDX = Object.freeze({"depth":0, "text":1, "parent":2, "size":3, "children": 4, "objectId":5});
//check if an editor is the active view
const editor = this.app.workspace.activeLeaf?.view?.editor;
if(!editor) return;
//initialize the tree with the title of the document as the first element
let tree = [[0,this.app.workspace.activeLeaf?.view?.getDisplayText(),-1,0,[],0]];
const linecount = editor.lineCount();
//helper function, use regex to calculate indentation depth, and to get line text
function getLineProps (i) {
props = editor.getLine(i).match(/^(\t*)-\s+(.*)/);
return [props[1].length+1, props[2]];
}
//a vector that will hold last valid parent for each depth
let parents = [0];
//load outline into tree
for(i=0;i<linecount;i++) {
[depth,text] = getLineProps(i);
if(depth>parents.length) parents.push(i+1);
else parents[depth] = i+1;
tree.push([depth,text,parents[depth-1],1,[]]);
tree[parents[depth-1]][IDX.children].push(i+1);
}
//recursive function to crawl the tree and identify height aka. size of each node
function crawlTree(i) {
if(i>linecount) return 0;
size = 0;
if((i+1<=linecount && tree[i+1][IDX.depth] <= tree[i][IDX.depth])|| i == linecount) { //I am a leaf
tree[i][IDX.size] = 1;
return 1;
}
tree[i][IDX.children].forEach((node)=>{
size += crawlTree(node);
});
tree[i][IDX.size] = size;
return size;
}
crawlTree(0);
//Build the mindmap in Excalidraw
const width = 300;
const height = 100;
const ea = ExcalidrawAutomate;
ea.reset();
//stores position offset of branch/leaf in height units
offsets = [0];
for(i=0;i<=linecount;i++) {
depth = tree[i][IDX.depth];
if (depth == 1) ea.style.strokeColor = '#'+(Math.random()*0xFFFFFF<<0).toString(16).padStart(6,"0");
tree[i][IDX.objectId] = ea.addText(depth*width,((tree[i][IDX.size]/2)+offsets[depth])*height,tree[i][IDX.text],{box:true});
//set child offset equal to parent offset
if((depth+1)>offsets.length) offsets.push(offsets[depth]);
else offsets[depth+1] = offsets[depth];
offsets[depth] += tree[i][IDX.size];
if(tree[i][IDX.parent]!=-1) {
ea.connectObjects(tree[tree[i][IDX.parent]][IDX.objectId],"right",tree[i][IDX.objectId],"left",{startArrowHead: 'dot'});
}
}
await ea.create({onNewPane: true});
%>
```

View File

@@ -0,0 +1,410 @@
# [◀ Excalidraw 自动化使用指南](./readme.md)
> 此说明当前更新至 `768aebf`。
【[English](../../ExcalidrawScriptsEngine.md) | 简体中文】
[![脚本引擎](https://user-images.githubusercontent.com/14358394/145684531-8d9c2992-59ac-4ebc-804a-4cce1777ded2.jpg)](https://youtu.be/hePJcObHIso)
## 简介
请将你的 ExcalidrawAutomate 脚本放入 Excalidraw 设置中定义的文件夹中。脚本文件夹不能是你的 Vault 根目录。
![image](https://user-images.githubusercontent.com/14358394/145673547-b4f57d01-3643-40f9-abfd-14c3bfa5ab93.png)
EA 脚本可以是 markdown 文件、纯文本文件或 .js 文件。唯一的要求是它们必须包含有效的 JavaScript 代码。
![image](https://user-images.githubusercontent.com/14358394/145673674-bb59f227-8eea-43dc-83b8-4d750e1920a8.png)
你可以通过 Obsidian 命令面板从 Excalidraw 访问你的脚本。
![image](https://user-images.githubusercontent.com/14358394/145673652-6b1713e2-edc8-4bc8-8246-3f8df8a4b273.png)
这样你就可以像设置其他 Obsidian 命令一样,为你喜欢的脚本分配快捷键。
![image](https://user-images.githubusercontent.com/14358394/145673633-83b6c969-cead-429b-9721-fd047f980279.png)
## 脚本开发
Excalidraw 脚本会自动接收两个对象:
- `ea`:脚本引擎会初始化 `ea` 对象,包括设置调用脚本时的活动视图为当前视图。
- `utils`:我从 [QuickAdd](https://github.com/chhoumann/quickadd/blob/master/docs/QuickAddAPI.md) 借用了一些实用函数,但目前并非所有 QuickAdd 实用函数都在 Excalidraw 中实现。目前可用的函数如下。详见下方示例。
- `inputPrompt: (header: string, placeholder?: string, value?: string, buttons?: [{caption:string, action:Function}])`
- 打开一个提示框请求输入。返回输入的字符串。
- 你需要使用 await 等待 inputPrompt 的结果。
- `buttons.action(input: string) => string`。按钮动作将接收当前输入字符串。如果动作返回 null输入将保持不变。如果动作返回字符串inputPrompt 将解析为该值。
```typescript
let fileType = "";
const filename = await utils.inputPrompt (
"Filename for new document",
"Placeholder",
"DefaultFilename.md",
[
{
caption: "Markdown",
action: ()=>{fileType="md";return;}
},
{
caption: "Excalidraw",
action: ()=>{fileType="ex";return;}
}
]
);
```
- `suggester: (displayItems: string[], items: any[], hint?: string, instructions?:Instruction[])`
- 打开一个建议器。显示 displayItems 并返回 items[] 中对应的项。
- 你需要使用 await 等待 suggester 的结果。
- 如果用户取消(按ESC键)suggester 将返回 `undefined`
- Hint(提示)和 instructions(说明)参数是可选的。
```typescript
interface Instruction {
command: string;
purpose: string;
}
```
- 脚本可以有设置。这些设置作为插件设置的一部分存储,用户也可以通过 Obsidian 插件设置窗口更改。
- 你可以使用 `ea.getScriptSettings()` 访问当前脚本的设置,并使用 `ea.setScriptSettings(settings:any)` 存储设置值
- 在插件设置中显示脚本设置的规则如下:
- 如果设置是简单的字面量(布尔值、数字、字符串),这些将按原样显示在设置中。设置的名称将作为值的键。
```javascript
ea.setScriptSettings({
"value 1": true,
"value 2": 1,
"value 3": "my string"
})
```
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/SimpleSettings.jpg)
- 如果设置是一个对象并遵循以下结构,则可以添加描述和值集。也可以使用 `hidden` 键从用户界面中隐藏值。
```javascript
ea.setScriptSettings({
"value 1": {
"value": true,
"description": "This is the description for my boolean value"
},
"value 2": {
"value": 1,
"description": "This is the description for my numeric value"
},
"value 3": {
"value": "my string",
"description": "This is the description for my string value",
"valueset": ["allowed 1","allowed 2","allowed 3"]
},
"value 4": {
"value": "my value",
"hidden": true
}
});
```
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/ComplexSettings.jpg)
---------
## Excalidraw 自动化脚本示例
这些脚本可以在 GitHub [这个](https://github.com/zsviczian/obsidian-excalidraw-plugin/tree/master/ea-scripts)文件夹 📂 中下载为 `.md` 文件。
### 为选中元素添加边框
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-box-elements.jpg)
此脚本将在 Excalidraw 中当前选中的元素周围添加一个包围框
```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();
//check if settings exist. If not, set default values on first run
if(!settings["Default padding"]) {
settings = {
"Prompt for padding?": true,
"Default padding" : {
value: 10,
description: "Padding between the bounding box of the selected elements, and the box the script creates"
}
};
ea.setScriptSettings(settings);
}
let padding = settings["Default padding"].value;
if(settings["Prompt for padding?"]) {
padding = parseInt (await utils.inputPrompt("padding?","number",padding.toString()));
}
if(isNaN(padding)) {
new Notice("The padding value provided is not a number");
return;
}
elements = ea.getViewSelectedElements();
const box = ea.getBoundingBox(elements);
color = ea
.getExcalidrawAPI()
.getAppState()
.currentItemStrokeColor;
//uncomment for random color:
//color = '#'+(Math.random()*0xFFFFFF<<0).toString(16).padStart(6,"0");
ea.style.strokeColor = color;
id = ea.addRect(
box.topX - padding,
box.topY - padding,
box.width + 2*padding,
box.height + 2*padding
);
ea.copyViewElementsToEAforEditing(elements);
ea.addToGroup([id].concat(elements.map((el)=>el.id)));
ea.addElementsToView(false);
```
----
### 用箭头连接选中的元素
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-connect-elements.jpg)
此脚本将用箭头连接两个对象。如果任一对象是一组分组元素(例如,一个文本元素与一个包围它的矩形分组),脚本会识别这些组,并将箭头连接到组中最大的对象(假设你想将箭头连接到文本元素周围的框)。
```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["Starting arrowhead"]) {
settings = {
"Starting arrowhead" : {
value: "none",
valueset: ["none","arrow","triangle","bar","dot"]
},
"Ending arrowhead" : {
value: "triangle",
valueset: ["none","arrow","triangle","bar","dot"]
},
"Line points" : {
value: 1,
description: "Number of line points between start and end"
}
};
ea.setScriptSettings(settings);
}
const arrowStart = settings["Starting arrowhead"].value === "none" ? null : settings["Starting arrowhead"].value;
const arrowEnd = settings["Ending arrowhead"].value === "none" ? null : settings["Ending arrowhead"].value;
const linePoints = Math.floor(settings["Line points"].value);
const elements = ea.getViewSelectedElements();
ea.copyViewElementsToEAforEditing(elements);
groups = ea.getMaximumGroups(elements);
if(groups.length !== 2) {
//unfortunately getMaxGroups returns duplicated resultset for sticky notes
//needs additional filtering
cleanGroups=[];
idList = [];
for (group of groups) {
keep = true;
for(item of group) if(idList.contains(item.id)) keep = false;
if(keep) {
cleanGroups.push(group);
idList = idList.concat(group.map(el=>el.id))
}
}
if(cleanGroups.length !== 2) return;
groups = cleanGroups;
}
els = [
ea.getLargestElement(groups[0]),
ea.getLargestElement(groups[1])
];
ea.style.strokeColor = els[0].strokeColor;
ea.style.strokeWidth = els[0].strokeWidth;
ea.style.strokeStyle = els[0].strokeStyle;
ea.style.strokeSharpness = els[0].strokeSharpness;
ea.connectObjects(
els[0].id,
null,
els[1].id,
null,
{
endArrowHead: arrowEnd,
startArrowHead: arrowStart,
numberOfPoints: linePoints
}
);
ea.addElementsToView();
```
----
### 反转选中的箭头
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-reverse-arrow.jpg)
反转选中元素范围内**箭头**的方向。
```javascript
elements = ea.getViewSelectedElements().filter((el)=>el.type==="arrow");
if(!elements || elements.length===0) return;
elements.forEach((el)=>{
const start = el.startArrowhead;
el.startArrowhead = el.endArrowhead;
el.endArrowhead = start;
});
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView();
```
----
### 设置选中元素的线条宽度
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-stroke-width.jpg)
当你缩放自由绘制的草图并想要减小或增加它们的线条宽度时,这个脚本会很有帮助。
```javascript
let width = (ea.getViewSelectedElement().strokeWidth??1).toString();
width = await utils.inputPrompt("Width?","number",width);
const elements=ea.getViewSelectedElements();
ea.copyViewElementsToEAforEditing(elements);
ea.getElements().forEach((el)=>el.strokeWidth=width);
ea.addElementsToView();
```
----
### 设置网格大小
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-grid.jpg)
Excalidraw 中默认的网格大小是 20。目前通过用户界面无法更改网格大小。
```javascript
const grid = parseInt(await utils.inputPrompt("Grid size?",null,"20"));
const api = ea.getExcalidrawAPI();
let appState = api.getAppState();
appState.gridSize = grid;
api.updateScene({
appState,
commitToHistory:false
});
```
----
### 设置元素尺寸和位置
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-dimensions.jpg)
目前在 Excalidraw 中还没有办法指定对象的精确位置和大小。你可以使用以下简单脚本来解决这个问题。
```javascript
const elements = ea.getViewSelectedElements();
if(elements.length === 0) return;
const el = ea.getLargestElement(elements);
const sizeIn = [el.x,el.y,el.width,el.height].join(",");
let res = await utils.inputPrompt("x,y,width,height?",null,sizeIn);
res = res.split(",");
if(res.length !== 4) return;
let size = [];
for (v of res) {
const i = parseInt(v);
if(isNaN(i)) return;
size.push(i);
}
el.x = size[0];
el.y = size[1];
el.width = size[2];
el.height = size[3];
ea.copyViewElementsToEAforEditing([el]);
ea.addElementsToView();
```
----
### 项目符号
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-bullet-point.jpg)
此脚本会在选中的每个文本元素的左上角添加一个小圆圈,并将文本和"项目符号"组合成一个组。
```javascript
elements = ea.getViewSelectedElements().filter((el)=>el.type==="text");
ea.copyViewElementsToEAforEditing(elements);
const padding = 10;
elements.forEach((el)=>{
ea.style.strokeColor = el.strokeColor;
const size = el.fontSize/2;
const ellipseId = ea.addEllipse(
el.x-padding-size,
el.y+size/2,
size,
size
);
ea.addToGroup([el.id,ellipseId]);
});
ea.addElementsToView();
```
----
### 按行分割文本
**!!!需要 Excalidraw 1.5.1 或更高版本**
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-split-lines.jpg)
将文本块按行分割成单独的文本元素,以便更容易重新组织
```javascript
elements = ea.getViewSelectedElements().filter((el)=>el.type==="text");
elements.forEach((el)=>{
ea.style.strokeColor = el.strokeColor;
ea.style.fontFamily = el.fontFamily;
ea.style.fontSize = el.fontSize;
const text = el.text.split("\n");
for(i=0;i<text.length;i++) {
ea.addText(el.x,el.y+i*el.height/text.length,text[i]);
}
});
ea.addElementsToView();
ea.deleteViewElements(elements);
```
----
### 设置文本对齐方式
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-text-align.jpg)
设置文本块的对齐方式(居中、右对齐、左对齐)。如果你想为选择文本对齐方式设置键盘快捷键,这个脚本会很有用。
```javascript
elements = ea.getViewSelectedElements().filter((el)=>el.type==="text");
if(elements.length===0) return;
let align = ["left","right","center"];
align = await utils.suggester(align,align);
elements.forEach((el)=>el.textAlign = align);
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView();
```
----
### 设置字体
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-font-family.jpg)
设置文本块的字体(Virgil、Helvetica、Cascadia)。如果你想为选择字体设置键盘快捷键,这个功能会很有用。
```javascript
elements = ea.getViewSelectedElements().filter((el)=>el.type==="text");
if(elements.length===0) return;
let font = ["Virgil","Helvetica","Cascadia"];
font = parseInt(await utils.suggester(font,["1","2","3"]));
if (isNaN(font)) return;
elements.forEach((el)=>el.fontFamily = font);
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView();
```

File diff suppressed because it is too large Load Diff

57
docs/zh-cn/docs/readme.md Normal file
View File

@@ -0,0 +1,57 @@
# Excalidraw 自动化使用指南
> 此说明当前更新至 `e793526`。
【[English](../../readme.md) | 简体中文】
使用 ExcalidrawAutomate 可以通过 [ExcalidrawAutomate 脚本引擎](ExcalidrawScriptsEngine.md)、[Templater](https://silentvoid13.github.io/Templater/docs/) 或 [QuickAdd](https://github.com/chhoumann/quickadd) 插件来创建或操作 Excalidraw 绘图,并使用 [DataviewJS](https://blacksmithgu.github.io/obsidian-dataview/docs/api/intro/) 生成嵌入式的 SVG 和 PNG 图像。
通过一些简单的配置,使用 ExcalidrawAutomate 你可以:
- 生成简单的思维导图
- 构建家谱
- 填写 SVG 表单
- 创建自定义图表
- 在 Excalidraw 中自动化简单任务(即创建宏)
![功能示例](https://user-images.githubusercontent.com/14358394/117549619-bae41180-b03b-11eb-968d-c909e79a7524.png)
## API 文档
- **从这里开始** [API 介绍](API/introduction.md)
- [属性和函数概览](API/attributes_functions_overview.md)
- [元素样式](API/element_style.md)
- [画布样式](API/canvas_style.md)
- [添加对象](API/objects.md)
- [实用函数](API/utility.md)
## ExcalidrawAutomate 脚本引擎
当你想要自动化一些简单的步骤时,比如在文本元素周围添加一个框、用箭头连接两个对象、或者设置自定义的线宽或网格值等类似"宏"的自动化操作,我建议使用脚本引擎。
- [ExcalidrawAutomate 脚本引擎](ExcalidrawScriptsEngine.md)
## 示例
- **Templater**
- [在当前编辑的文档中插入新绘图](Examples/insert_new_drawing.md)
- [连接对象](Examples/connect_objects.md)
- [应用 Excalidraw 模板](Examples/apply_template.md)
- [使用 Templater 创建思维导图](Examples/templater_mindmap.md)
- **Dataview**
- [使用 Dataview 创建思维导图](Examples/dataviewjs_mindmap.md)
- [使用 Dataview 创建家谱](Examples/dataviewjs_familytree.md)
## 支持与反馈
### 支持原作者
如果你喜欢这个插件,请通过以下方式支持:
- 在社交媒体上分享这个插件
- 在 Twitter 上关注作者 [@zsviczian](https://twitter.com/zsviczian)
- 访问作者的博客 [zsolt.blog](https://zsolt.blog)
- [赞助作者](https://ko-fi.com/zsolt)
[<img src="https://user-images.githubusercontent.com/14358394/115450238-f39e8100-a21b-11eb-89d0-fa4b82cdbce8.png" width="150" alt="赞助按钮">](https://ko-fi.com/zsolt)
### 支持中文翻译
如果这些中文翻译对你有帮助,欢迎通过 [爱发电](https://afdian.com/a/daomishu) 支持译者。

View File

@@ -0,0 +1,91 @@
# Excalidraw 脚本引擎脚本库
> 此说明当前更新至 `768aebf`。
【[English](../../../ea-scripts/README.md) | 简体中文】
点击观看介绍视频:
[![脚本引擎](https://user-images.githubusercontent.com/14358394/145684531-8d9c2992-59ac-4ebc-804a-4cce1777ded2.jpg)](https://youtu.be/hePJcObHIso)
> **警告**
> 相比视频中展示的方法,现在有更简单的方式来安装/管理脚本
查看 [Excalidraw 脚本引擎](../../ExcalidrawScriptsEngine.md) 文档了解更多详情。
## 如何在 Obsidian 仓库中安装脚本
安装内置脚本的步骤:
- 在 Obsidian 中打开一个 Excalidraw 绘图
- 在面板下拉菜单中选择"安装或更新 Excalidraw 脚本"
- 点击其中一个可用脚本
- 点击"安装此脚本"(注意如果脚本已经安装,你会看到更新选项)
- 重启 Obsidian 使脚本生效
注意:默认情况下,脚本会被安装到你仓库中的 `Excalidraw/Scripts/Downloaded` 文件夹
<details><summary>手动安装脚本</summary>
打开你感兴趣的脚本,将其保存到你的 Obsidian 仓库中(包括第一行的 `/*`),或者在"Raw"模式下打开并将全部内容复制到 Obsidian 中。
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-download-raw.jpg)
</details>
## 可用脚本列表
|标题|描述|图标|贡献者|
|----|----|----|----|
|[添加连接点](../../../ea-scripts/Add%20Connector%20Point.md)|此脚本将在选中文本元素的左上角添加一个小圆圈,并将文本和"圆点"组合成一组。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-bullet-point.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[添加现有文件链接并打开](../../../ea-scripts/Add%20Link%20to%20Existing%20File%20and%20Open.md)|提示从保险库Vault中选择文件。为选中的元素添加指向所选文件的链接。你可以在设置中控制是在当前活动面板还是相邻面板中打开文件。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-link-and-open.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[添加新页面链接并打开](../../../ea-scripts/Add%20Link%20and%20Open%20Page.md)|提示输入文件名。提供创建和打开新的 Markdown 或 Excalidraw 文档的选项。为绘图中选中的对象添加指向新文件的链接。你可以在设置中控制是在当前活动面板或是相邻面板中打开文件。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-link-to-new-page-and-pen.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[添加流程下一步](../../../ea-scripts/Add%20Link%20to%20New%20Page%20and%20Open.md)|此脚本将提示你输入流程步骤的标题,然后创建带有该文本的便签。如果选中了某个元素,脚本将用箭头将这个新步骤与上一步骤(选中的元素)连接起来。如果没有选中元素,脚本会假定这是流程的第一步,只会输出带有输入文本的便签。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-add-process-step.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[分割椭圆](../../../ea-scripts/Boolean%20Operations.md)|使用此脚本可以对形状进行布尔运算。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-boolean-operations-showcase.png)|[@GColoy](https://github.com/GColoy)|
|[为每个选中的组添加边框](../../../ea-scripts/Box%20Each%20Selected%20Groups.md)|此脚本将为 Excalidraw 中当前选中的每个组添加封装框。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-box-each-selected-groups.png)|[@1-2-3](https://github.com/1-2-3)|
|[为选中元素添加边框](../../../ea-scripts/Box%20Selected%20Elements.md)|此脚本将为 Excalidraw 中当前选中的元素添加一个封装框。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-box-elements.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[更改选中元素的形状](../../../ea-scripts/Change%20shape%20of%20selected%20elements.md)|此脚本允许你更改选中的矩形、菱形和椭圆的形状|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-change-shape.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[连接元素](../../../ea-scripts/Connect%20elements.md)|此脚本将用箭头连接两个对象。如果任一对象是一组分组的元素(例如,与封装矩形分组的文本元素),脚本将识别这些组,并将箭头连接到组中最大的对象(假设你想将箭头连接到文本元素周围的框)。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-connect-elements.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[将自由绘制转换为线条](../../../ea-scripts/Convert%20freedraw%20to%20line.md)|将选中的自由绘制对象转换为可编辑的线条。这样你就可以通过拖动线条点来调整绘图,如果是封闭线条还可以选择形状填充。你可以在设置中调整转换点的密度|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-convert-freedraw-to-line.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[将选中的文本元素转换为便签](../../../ea-scripts/Convert%20selected%20text%20elements%20to%20sticky%20notes.md)|将选中的纯文本元素转换为具有透明背景和透明描边颜色的便签。本质上是将文本元素转换为可换行的格式。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-textelement-to-transparent-stickynote.png)|[@zsviczian](https://github.com/zsviczian)|
|[将文本转换为带文件夹和别名的链接](../../../ea-scripts/Convert%20text%20to%20link%20with%20folder%20and%20alias.md)|将文本元素转换为指向所选文件夹中文件的链接并将原始文本设置为别名。脚本会提示用户从保险库Vault中选择一个现有文件夹。|`原始文本` => `[[选定文件夹/原始文本\|原始文本]]`|[@zsviczian](https://github.com/zsviczian)|
|[将选中元素样式复制到全局](../../../ea-scripts/Copy%20Selected%20Element%20Styles%20to%20Global)|此脚本会将任何选中元素的样式复制到 Excalidraw 的全局样式中。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-copy-selected-element-styles-to-global.png)|[@1-2-3](https://github.com/1-2-3)|
|[创建新的 Markdown 文件并嵌入到当前绘图中](../../../ea-scripts/Create%20new%20markdown%20file%20and%20embed%20into%20active%20drawing.md)|此脚本会提示你输入文件名,然后创建一个具有该文件名的新 Markdown 文档,在相邻面板中打开新的 Markdown 文档,并将该 Markdown 文档嵌入到当前的 Excalidraw 绘图中。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-create-and-embed-new-markdown-file.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[加深背景颜色](../../../ea-scripts/Darken%20background%20color.md)|此脚本每次将选中元素的背景颜色加深 2%。你可以多次使用此脚本直到满意为止。建议为此脚本设置快捷键,这样你就可以快速尝试加深和减淡颜色效果。与"修改背景颜色不透明度"脚本相比,其优点是元素的背景颜色不受画布颜色影响,并且颜色值不会以奇怪的 rgba() 形式出现。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/darken-lighten-background-color.png)|[@1-2-3](https://github.com/1-2-3)|
|[肘形连接器](../../../ea-scripts/Elbow%20connectors.md)|此脚本将选中的连接器转换为肘形。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/elbow-connectors.png)|[@1-2-3](https://github.com/1-2-3)|
|[水平扩展矩形并保持文本居中](../../../ea-scripts/Expand%20rectangles%20horizontally%20keep%20text20%centered.md)|此脚本会扩展选中矩形的宽度,直到它们都具有相同的宽度,并保持文本居中。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-expand-rectangles.gif)|[@1-2-3](https://github.com/1-2-3)|
|[水平扩展矩形](../../../ea-scripts/Expand%20rectangles%20horizontally.md)|此脚本会扩展选中矩形的宽度,直到它们都具有相同的宽度。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-expand-rectangles.gif)|[@1-2-3](https://github.com/1-2-3)|
|[垂直扩展矩形并保持文本居中](../../../ea-scripts/Expand%20rectangles%20vertically%20keep%20text%20centered.md)|此脚本会扩展选中矩形的高度,直到它们都具有相同的高度,并保持文本居中。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-expand-rectangles.gif)|[@1-2-3](https://github.com/1-2-3)|
|[垂直扩展矩形](../../../ea-scripts/Expand%20rectangles%20vertically.md)|此脚本会扩展选中矩形的高度,直到它们都具有相同的高度。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-expand-rectangles.gif)|[@1-2-3](https://github.com/1-2-3)|
|[固定中心点水平距离](../../../ea-scripts/Fixed%20horizontal%20distance%20between%20centers.md)|此脚本会以固定的中心点间距水平排列选中的元素。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fixed-horizontal-distance-between-centers.png)|[@1-2-3](https://github.com/1-2-3)|
|[固定内部距离](../../../ea-scripts/Fixed%20inner%20distance.md)|此脚本会以固定的内部距离排列选中的元素和组。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fixed-inner-distance.png)|[@1-2-3](https://github.com/1-2-3)|
|[固定间距](../../../ea-scripts/Fixed%20spacing.md)|此脚本会以固定的间距水平排列选中的元素。当我们创建架构图或思维导图时,经常需要以固定间距排列大量元素。"固定间距"和"固定垂直距离"脚本可以为我们节省大量时间。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fix-space-demo.png)|[@1-2-3](https://github.com/1-2-3)|
|[固定中心点垂直距离](../../../ea-scripts/Fixed%20vertical%20distance%20between%20centers.md)|此脚本会以固定的中心点间距垂直排列选中的元素。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fixed-vertical-distance-between-centers.png)|[@1-2-3](https://github.com/1-2-3)|
|[固定垂直距离](../../../ea-scripts/Fixed%20vertical%20distance.md)|此脚本会以固定间距垂直排列选中的元素。当我们创建架构图或思维导图时,经常需要以固定间距排列大量元素。`固定间距``固定垂直距离`脚本可以为我们节省大量时间。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fixed-vertical-distance.png)|[@1-2-3](https://github.com/1-2-3)|
|[减淡背景颜色](../../../ea-scripts/Lighten%20background%20color.md)|此脚本每次将选中元素的背景颜色减淡 2%。你可以多次使用此脚本直到满意为止。建议为此脚本设置快捷键,这样你就可以快速尝试加深和减淡颜色效果。与"修改背景颜色不透明度"脚本相比,其优点是元素的背景颜色不受画布颜色影响,并且颜色值不会以奇怪的 rgba() 形式出现。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/darken-lighten-background-color.png)|[@1-2-3](https://github.com/1-2-3)|
|[思维导图连接器](../../../ea-scripts/Mindmap%20connector.md)|此脚本为选中的元素创建类似思维导图的连线(目前仅支持右侧和向下方向)。连线的起点将根据元素的创建时间确定。因此你应该先创建标题元素。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/mindmap%20connector.png)|[@xllowl](https://github.com/xllowl)|
|[修改背景颜色不透明度](../../../ea-scripts/Modify%20background%20color%20opacity.md)|此脚本会更改选中框的背景颜色不透明度。Excalidraw 中的默认背景颜色太深,导致文字难以阅读。你可以通过设置透明度来使颜色变浅。你可以反复调整透明度直到满意为止。虽然 Excalidraw 在其原生属性设置中有不透明度选项,但它也会改变边框的透明度。使用此脚本可以只更改背景颜色的不透明度而不影响边框。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-modify-background-color-opacity.png)|[@1-2-3](https://github.com/1-2-3)|
|[标准化选中箭头](../../../ea-scripts/Normalize%20Selected%20Arrows.md)|此脚本将重置选中箭头的起点和终点位置。箭头将指向连接框的中心,并与框保持 8px 的间距。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-normalize-selected-arrows.png)|[@1-2-3](https://github.com/1-2-3)|
|[OCR - 光学字符识别](../../../ea-scripts/OCR%20-%20Optical%20Character%20Recognition.md)|此脚本将 1) 把选中的图片文件发送到 [taskbone.com](https://taskbone.com) 提取图片中的文字,并 2) 将文字作为文本元素添加到你的绘图中。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-ocr.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[有机线条](../../../ea-scripts/Organic%20Line.md)|将选中的自由绘制线条转换为从开始到结束笔压逐渐减小的线条。转换后的线条会被放置在图层的最底层,位于所有其他元素之下。在绘制有机思维导图时很有帮助。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-organic-line.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[重复元素](../../../ea-scripts/Repeat%20Elements.md)|此脚本会检测两个选中元素之间的差异,包括位置、大小、角度、描边和背景颜色,并根据用户输入的重复次数创建多个具有相同差异的元素。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-repeat-elements.png)|[@1-2-3](https://github.com/1-2-3)|
|[重置 LaTeX 大小](../../../ea-scripts/Reset%20LaTeX%20Size.md)|将嵌入的 LaTeX 公式大小重置为默认大小或默认大小的倍数。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-reset-latex.jpg)|[@firai](https://github.com/firai)|
|[反转箭头](../../../ea-scripts/Reverse%20arrows.md)|反转选中元素范围内的**箭头**方向。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-reverse-arrow.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[手写助手](../../../ea-scripts/Scribble%20Helper.md)|iOS 手写助手,用于改善文本元素的手写体验。如果没有选中元素,则会在指针位置创建一个文本元素,你可以使用编辑框通过手写来修改文本。如果选中了文本元素,则会打开输入提示框,你可以在其中通过手写修改文本。|![]('https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-scribble-helper.jpg')|[@zsviczian](https://github.com/zsviczian)|
|[选择特定类型元素](../../../ea-scripts/Select%20Elements%20of%20Type.md)|显示当前图像中不同元素类型的列表供选择。只有选定类型的元素会在画布上被选中。如果运行脚本时没有选中任何元素,则脚本会处理画布上的所有元素。如果执行脚本时已选中某些元素,则脚本只会处理这些选中的元素。<br>此脚本在以下情况下很有用,例如,当你想要将所有箭头置于顶层,或想要更改所有文本元素的颜色等。|![]('https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-select-element-of-type.jpg')|[@zsviczian](https://github.com/zsviczian)|
|[通过添加阴影克隆为未闭合线条对象设置背景颜色](../../../ea-scripts/Set%20background%20color%20of%20unclosed%20line%20object%20by%20adding%20a%20shadow%20clone.md)|使用此脚本为未闭合(即开放)线条对象设置背景颜色,方法是创建对象的克隆。脚本会将克隆的描边颜色设置为透明,并添加一条直线来闭合对象。使用设置来定义默认背景颜色、填充样式和克隆的描边宽度。默认情况下,克隆会与原始对象组合在一起,你也可以在设置中禁用此功能。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-dimensions.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[设置尺寸](../../../ea-scripts/Set%20Dimensions.md)|目前在 Excalidraw 中无法指定对象的确切位置和大小。你可以使用这个简单的脚本来弥补这个不足。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-dimensions.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[设置字体](../../../ea-scripts/Set%20Font%20Family.md)|设置文本块的字体Virgil、Helvetica、Cascadia。如果你想为选择字体设置键盘快捷键这个脚本很有用。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-font-family.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[设置网格](../../../ea-scripts/Set%20Grid.md)|Excalidraw 中的默认网格大小是 20。目前无法通过用户界面更改网格大小。这个脚本提供了一种方法来弥补这个不足。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-grid.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[设置链接别名](../../../ea-scripts/Set20%Link20%Alias.md)|遍历选中文本元素中的所有链接,并提示用户为每个找到的链接设置或修改别名。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-set-link-alias.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[设置选中元素的描边宽度](../../../ea-scripts/Set%20Stroke%20Width%20of%20Selected%20Elements.md)|此脚本将设置选中元素的描边宽度。这在缩放自由绘制草图并想要减小或增加其线条宽度时很有用。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-stroke-width.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[按行分割文本](../../../ea-scripts/Split%20text%20by%20lines.md)|将文本行分割成单独的文本元素,以便更容易重新组织|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-split-lines.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[设置文本对齐方式](../../../ea-scripts/Set%20Text%20Alignment.md)|设置文本块的对齐方式(居中、右对齐、左对齐)。如果你想为选择文本对齐方式设置键盘快捷键,这个脚本很有用。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-text-align.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[分割椭圆](../../../ea-scripts/Split%20Ellipse.md)|此脚本会在线条与椭圆相交的任何点处分割椭圆。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-splitEllipse-demo1.png)|[@GColoy](https://github.com/GColoy)|
|[TheBrain导航](../../../ea-scripts/TheBrain-navigation.md)|基于Excalidraw的保险库Vault图形用户界面。需要[Dataview插件](https://github.com/blacksmithgu/obsidian-dataview)。生成类似于[TheBrain](https://TheBrain.com)的图形视图。在[YouTube](https://youtu.be/plYobK-VufM)上观看此脚本的介绍。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/TheBrain.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[移动端切换全屏](../../../ea-scripts/Toggle%20Fullscreen%20on%20Mobile.md)|隐藏Obsidian工作区叶片填充和标题基于设置中的选项默认"隐藏标题"=false这将使Excalidraw全屏显示。⚠ 注意,如果标题不可见,将很难调用命令面板来结束全屏。只有在你有键盘或已经练习过打开命令面板的情况下才隐藏标题!|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/ea-toggle-fullscreen.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[切换网格](../../../ea-scripts/Toggle%20Grid.md)|切换网格的显示与隐藏。||[@GColoy](https://github.com/GColoy)|
|[将文本元素转移到Excalidraw markdown元数据](../../../ea-scripts/Transfer%20TextElements%20to%20Excalidraw%20markdown%20metadata.md)|此脚本将从画布中删除选中的文本元素并将这些文本元素中的文本复制到Excalidraw markdown文件的元数据中。这意味着文本将不再在绘图中可见但你可以在Obsidian中搜索文本并找到包含此图像的绘图。|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-text-to-metadata.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[缩放以适应选中元素](../../../ea-scripts/Zoom%20to%20Fit%20Selected%20Elements.md)|类似于Excalidraw标准的<kbd>SHIFT+2</kbd>功能缩放以适应选中元素但可以缩放到1000%。灵感来源:[#272](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/272)||[@zsviczian](https://github.com/zsviczian)|
|[硬件橡皮擦支持](../../../ea-scripts/Hardware%20Eraser%20Support.md)|允许在支持的笔上使用笔反转/硬件橡皮擦。|[@threethan](https://github.com/threethan)|
|[笔的自动绘制](../../../ea-scripts/Auto%20Draw%20for%20Pen.md)|当悬停笔时自动从选择工具切换到绘制工具,然后再切换回来。|[@threethan](https://github.com/threethan)|

View File

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

View File

@@ -1,67 +0,0 @@
/*
Automatically switches between the select and draw tools, based on whether a pen is being used.
1. Choose the select tool
2. Hover/use the pen to draw, move it away to return to select mode
*This is based on pen hover status, so will only work if your pen supports hover!*
If you click draw with the mouse or press select with the pen, switching will be disabled until the opposite input method is used.
**Note:** This script will stay active until the *Obsidian* window is closed.
Compatible with my *Hardware Eraser Support* script
```javascript
*/
(function() {
'use strict';
let promise
let timeout
let disable
function handlePointer(e) {
ea.setView("active");
var activeTool = ea.getExcalidrawAPI().getAppState().activeTool;
function setActiveTool(t) {
ea.getExcalidrawAPI().setActiveTool(t)
}
if (e.pointerType === 'pen') {
if (disable) return
if (!promise && activeTool.type==='selection') {
setActiveTool({type:"freedraw"})
}
if (timeout) clearTimeout(timeout)
function setTimeoutX(a,b) {
timeout = setTimeout(a,b)
return timeout
}
function revert() {
activeTool = ea.getExcalidrawAPI().getAppState().activeTool;
disable = false
if (activeTool.type==='freedraw') {
setActiveTool({type:"selection"})
} else if (activeTool.type==='selection') {
disable = true
}
promise = false
}
promise = new Promise(resolve => setTimeoutX(resolve, 500))
promise.then(() => revert())
}
}
function handleClick(e) {
ea.setView("active");
if (e.pointerType !== 'pen') {
disable = false
}
}
window.addEventListener('pointermove', handlePointer, { capture: true })
window.addEventListener('pointerdown', handleClick, { capture: true })
})();

View File

@@ -1,50 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 448 512" style="enable-background:new 0 0 448 512;" xml:space="preserve">
<style type="text/css">
.st0{stroke:#000000;stroke-width:2;stroke-miterlimit:10;}
</style>
<g>
<g>
<path class="st0" d="M355.8,234.1"/>
</g>
<g>
<path d="M32.3,139.7l28.8,24.2l63.5-71.7L95.7,67c-7.2-6.3-18.2-5.6-24.5,1.6l-40.6,46.6C24.3,122.4,25,133.3,32.3,139.7z"/>
<path d="M61.2,165.3l-29.6-24.9c-3.7-3.3-5.9-7.8-6.3-12.7c-0.3-4.9,1.3-9.6,4.5-13.2L70.5,68c6.7-7.6,18.3-8.4,25.9-1.7L126,92.1
L61.2,165.3z M32.9,138.9l28,23.6l62.2-70.2l-28-24.6c-6.8-5.9-17.1-5.2-23.1,1.5l-40.6,46.6c-2.9,3.3-4.3,7.5-4,11.8
C27.6,132,29.6,136,32.9,138.9z"/>
</g>
<g>
<polygon points="218.7,240.1 212.3,168.6 197.2,155.4 133.7,228.1 148.9,241.3 "/>
<path d="M148.5,242.3l-16.2-14.1l64.8-74.2l16.2,14.1l6.5,73L148.5,242.3z M135.1,228l14.1,12.3l68.4-1.2l-6.2-70.1l-14.1-12.3
L135.1,228z"/>
</g>
<g>
<polygon points="192.6,151.6 129.1,224.3 66.2,168.4 129.6,96.7 "/>
<path d="M129.2,225.7l-64.5-57.2l64.8-73.2l64.5,56.2L129.2,225.7z M67.6,168.3l61.5,54.6l62.2-71.2l-61.5-53.6L67.6,168.3z"/>
</g>
<g>
<path d="M109.7,381.6c-23.7-22.2-40-49.3-48.9-78.2c8.2-0.9,22.4-3.6,30.1-12.3c-12.6-12.5-25.3-25-37.9-37.5c0-0.1,0-0.3,0-0.4
l-23.6-22c-6,60.7,15.5,123.7,63.7,168.8s112.5,62.4,172.7,52.4l-24.1-22.6C194.8,432.3,146.9,416.4,109.7,381.6z"/>
<path d="M232.6,456.1c-19.6,0-39.2-2.8-57.9-8.3c-30.9-9.1-58.6-24.9-82.3-47.1C68.7,378.6,51.1,352,40,321.8
c-10.6-28.8-14.6-60.2-11.6-90.7l0.2-2L54,252.8v0.4c6.2,6.1,12.4,12.3,18.6,18.4c6.3,6.3,12.7,12.5,19,18.8l0.7,0.7l-0.6,0.7
c-7.2,8.1-19.8,11.3-29.5,12.5c9.2,29.2,25.9,55.6,48.3,76.6l0,0c35.8,33.5,82.4,50.5,131.3,47.9l0.4,0l25.9,24.3l-2,0.3
C255,455.2,243.8,456.1,232.6,456.1z M30.2,233.3c-5.6,62.5,17.5,122.9,63.6,166c46,43.1,107.8,62.1,169.8,52.5l-22.3-20.9
c-49.2,2.5-96.2-14.7-132.3-48.5l0,0c-22.9-21.5-39.9-48.7-49.2-78.6l-0.4-1.2l1.2-0.1c9.3-1,21.6-3.8,28.8-11.3
c-6.1-6-12.2-12.1-18.3-18.1c-6.3-6.2-12.6-12.5-18.9-18.7L52,254v-0.4L30.2,233.3z"/>
</g>
<g>
<path d="M368.8,105.4c-56-52.4-133.5-67.2-201.3-45.5l21,19.6c56.1-13.2,117.7,1.1,163.1,43.7c33,30.9,51.7,71.2,55.9,112.7
c-0.2-0.4-0.3-0.6-0.3-0.6s-25.1-0.1-36.5,12.7c11.8,11.6,23.5,23.3,35.3,34.9v0.1l2.4,2.3c0.4,0.4,0.9,0.9,1.3,1.3l0,0l17.7,16.6
C444.7,234.1,424.8,157.7,368.8,105.4z"/>
<path d="M428,305.1L409,287.3l-1.3-1.3l-2.7-2.6v-0.1c-5.8-5.7-11.5-11.4-17.3-17.1c-5.9-5.8-11.8-11.7-17.7-17.5l-0.7-0.7
l0.6-0.7c10.5-11.8,31.7-12.9,36.4-13c-4.7-42.1-24.3-81.3-55.4-110.4c-43.5-40.9-104.2-57.1-162.2-43.5l-0.5,0.1l-22.6-21.1
l1.6-0.5c70.5-22.6,148-5,202.3,45.7c54.3,50.7,76.9,126.9,58.9,198.8L428,305.1z M407,282.6l3.4,3.3l16.4,15.4
c17.1-70.7-5.3-145.3-58.7-195.2l0,0c-53.3-49.9-129.3-67.4-198.7-45.8l19.4,18.1c58.5-13.6,119.6,2.9,163.5,44.1
c31.9,29.8,51.8,70.1,56.2,113.3l0.5,5.4l-2.5-4.9c-3.8,0.1-24.3,1.1-34.5,11.7c5.7,5.6,11.3,11.2,17,16.8
c5.9,5.8,11.7,11.6,17.6,17.4L407,282.6L407,282.6z"/>
</g>
<polygon points="425.2,382.2 302.7,283.9 299.4,437.6 340.9,383.8 382.3,456.5 398,447.5 359.4,379.8 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -8,20 +8,76 @@ if(lines.length !== 2) {
return;
}
// https://math.stackexchange.com/questions/2204520/how-do-i-rotate-a-line-segment-in-a-specific-point-on-the-line
const rotate = (point, element) => {
const [x1, y1] = point;
const x2 = element.x + element.width/2;
const y2 = element.y - element.height/2;
const angle = element.angle;
return [
(x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle) + x2,
(x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2,
];
//Same line but with angle=0
function getNormalizedLine(originalElement) {
if(originalElement.angle === 0) return originalElement;
// Get absolute coordinates for all points first
const pointRotateRads = (point, center, angle) => {
const [x, y] = point;
const [cx, cy] = center;
return [
(x - cx) * Math.cos(angle) - (y - cy) * Math.sin(angle) + cx,
(x - cx) * Math.sin(angle) + (y - cy) * Math.cos(angle) + cy
];
};
// Get element absolute coordinates (matching Excalidraw's approach)
const getElementAbsoluteCoords = (element) => {
const points = element.points;
let minX = Infinity;
let minY = Infinity;
let maxX = -Infinity;
let maxY = -Infinity;
for (const [x, y] of points) {
const absX = x + element.x;
const absY = y + element.y;
minX = Math.min(minX, absX);
minY = Math.min(minY, absY);
maxX = Math.max(maxX, absX);
maxY = Math.max(maxY, absY);
}
return [minX, minY, maxX, maxY];
};
// Calculate center point based on absolute coordinates
const [x1, y1, x2, y2] = getElementAbsoluteCoords(originalElement);
const centerX = (x1 + x2) / 2;
const centerY = (y1 + y2) / 2;
// Calculate absolute coordinates of all points
const absolutePoints = originalElement.points.map(([x, y]) => [
x + originalElement.x,
y + originalElement.y
]);
// Rotate all points around the center
const rotatedPoints = absolutePoints.map(point =>
pointRotateRads(point, [centerX, centerY], originalElement.angle)
);
// Convert back to relative coordinates
const newPoints = rotatedPoints.map(([x, y]) => [
x - rotatedPoints[0][0],
y - rotatedPoints[0][1]
]);
const newLineId = ea.addLine(newPoints);
// Set the position of the new line to the first rotated point
const newLine = ea.getElement(newLineId);
newLine.x = rotatedPoints[0][0];
newLine.y = rotatedPoints[0][1];
newLine.angle = 0;
delete ea.elementsDict[newLine.id];
return newLine;
}
const points = lines.map(
el=>el.points.map(p=>rotate([p[0]+el.x, p[1]+el.y],el))
const points = lines.map(getNormalizedLine).map(
el=>el.points.map(p=>[p[0]+el.x, p[1]+el.y])
);
const last = (p) => p[p.length-1];
@@ -99,4 +155,4 @@ switch (lineTypes) {
}
ea.addElementsToView();
await ea.addElementsToView();

View File

@@ -9,7 +9,7 @@ Select some elements in the scene. The script will take these elements and move
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.0.25")) {
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.7.3")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
@@ -79,15 +79,19 @@ 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) {
const hyperlink = img?.hyperlink;
if(img && (path || hyperlink)) {
const colorMap = ea.getColorMapForImageElement(el);
ea.imagesDict[el.fileId] = {
mimeType: img.mimeType,
id: el.fileId,
dataURL: img.img,
created: img.mtime,
file: path,
hyperlink,
hasSVGwithBitmap: img.isSVGwithBitmap,
latex: null,
colorMap,
};
return;
}

View File

@@ -0,0 +1,157 @@
/*
This script generates a complete calendar for a specified year, visually distinguishing weekends from weekdays through color coding.
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-full-year-calendar-exemple.excalidraw.png)
## Customizable Colors
You can personalize the calendars appearance by defining your own colors:
1. Create two rectangles in your design.
2. Select both rectangles before running the script:
• The **fill and stroke colors of the first rectangle** will be applied to weekdays.
• The **fill and stroke colors of the second rectangle** will be used for weekends.
If no rectangle are selected, the default color schema will be used (white and purple).
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-full-year-calendar-customize.excalidraw.png)
```javascript
*/
ea.reset();
// -------------------------------------
// Constants initiation
// -------------------------------------
const RECT_WIDTH = 300; // day width
const RECT_HEIGHT = 45; // day height
const START_X = 0; // X start position
const START_Y = 0; // PY start position
const MONTH_SPACING = 30; // space between months
const DAY_SPACING = 0; // space between days
const DAY_NAME_SPACING = 45; // space between day number and day letters
const DAY_NAME_AND_NUMBER_X_MARGIN = 5;
const MONTH_NAME_SPACING = -40;
const YEAR_X = (RECT_WIDTH + MONTH_SPACING) * 6 - 150;
const YEAR_Y = -200;
let COLOR_WEEKEND = "#c3abf3";
let COLOR_WEEKDAY = "#ffffff";
const COLOR_DAY_STROKE = "none";
let STROKE_DAY = 4;
let FILLSTYLE_DAY = "solid";
const FONT_SIZE_MONTH = 60;
const FONT_SIZE_DAY = 30;
const FONT_SIZE_YEAR = 100;
const LINE_STROKE_SIZE = 4;
let LINE_STROKE_COLOR_WEEKDAY = "black";
let LINE_STROKE_COLOR_WEEKEND = "black";
const SATURDAY = 6;
const SUNDAY = 0;
const JANUARY = 0;
const FIRST_DAY_OF_THE_MONTH = 1;
const DAY_NAME_AND_NUMBER_Y_MARGIN = (RECT_HEIGHT - FONT_SIZE_DAY) / 2;
// -------------------------------------
// ask for requested Year
// Default value is the current year
let requestedYear = parseFloat(new Date().getFullYear());
requestedYear = parseFloat(await utils.inputPrompt("Year ?", requestedYear, requestedYear));
if(isNaN(requestedYear)) {
new Notice("Invalid number");
return;
}
// -------------------------------------
// Use selected element for the calendar style
// -------------------------------------
let elements = ea.getViewSelectedElements();
if (elements.length>=1){
COLOR_WEEKDAY = elements[0].backgroundColor;
FILLSTYLE_DAY = elements[0].fillStyle;
STROKE_DAY = elements[0].strokeWidth;
LINE_STROKE_COLOR_WEEKDAY = elements[0].strokeColor;
}
if (elements.length>=2){
COLOR_WEEKEND = elements[1].backgroundColor;
LINE_STROKE_COLOR_WEEKEND = elements[1].strokeColor;
}
// get the first day of the current year (01/01)
var firstDayOfYear = new Date(requestedYear, JANUARY, FIRST_DAY_OF_THE_MONTH);
var currentDay = firstDayOfYear
// write year number
let calendarYear = firstDayOfYear.getFullYear();
ea.style.fontSize = FONT_SIZE_YEAR;
ea.addText(START_X + YEAR_X, START_Y + YEAR_Y, String(calendarYear));
// while we do not reach the end of the year iterate on all the day of the current year
do {
var curentDayOfTheMonth = currentDay.getDate();
var currentMonth = currentDay.getMonth();
var isWeekend = currentDay.getDay() == SATURDAY || currentDay.getDay() == SUNDAY;
// set background color if it's a weekend or weekday
ea.style.backgroundColor = isWeekend ? COLOR_WEEKEND : COLOR_WEEKDAY ;
ea.style.strokeColor = COLOR_DAY_STROKE;
ea.style.fillStyle = FILLSTYLE_DAY;
ea.style.strokeWidth = STROKE_DAY;
let x = START_X + currentMonth * (RECT_WIDTH + MONTH_SPACING);
let y = START_Y + curentDayOfTheMonth * (RECT_HEIGHT + DAY_SPACING);
// only one time per month
if(curentDayOfTheMonth == FIRST_DAY_OF_THE_MONTH) {
// add month name
ea.style.fontSize = FONT_SIZE_MONTH;
ea.addText(x + DAY_NAME_AND_NUMBER_X_MARGIN, START_Y+MONTH_NAME_SPACING, currentDay.toLocaleString('default', { month: 'long' }));
}
// Add day rectangle
ea.style.fontSize = FONT_SIZE_DAY;
ea.addRect(x, y, RECT_WIDTH, RECT_HEIGHT);
// set stroke color based on weekday
ea.style.strokeColor = isWeekend ? LINE_STROKE_COLOR_WEEKEND : LINE_STROKE_COLOR_WEEKDAY;
// add line between days
//ea.style.strokeColor = LINE_STROKE_COLOR_WEEKDAY;
ea.style.strokeWidth = LINE_STROKE_SIZE;
ea.addLine([[x,y],[x+RECT_WIDTH, y]]);
// add day number
ea.addText(x + DAY_NAME_AND_NUMBER_X_MARGIN, y + DAY_NAME_AND_NUMBER_Y_MARGIN, String(curentDayOfTheMonth));
// add day name
ea.addText(x + DAY_NAME_AND_NUMBER_X_MARGIN + DAY_NAME_SPACING, y + DAY_NAME_AND_NUMBER_Y_MARGIN, String(currentDay.toLocaleString('default', { weekday: 'narrow' })));
// go to the next day
currentDay.setDate(currentDay.getDate() + 1);
} while (!(currentDay.getMonth() == JANUARY && currentDay.getDate() == FIRST_DAY_OF_THE_MONTH)) // stop if we reach the 01/01 of the next year
await ea.addElementsToView(false, false, true);

View File

@@ -0,0 +1,10 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50.097575068473816 56.100231877975375" width="50.097575068473816" height="56.100231877975375" class="excalidraw-svg">
<!-- svg-source:excalidraw -->
<defs>
<style class="style-fonts">
@font-face { font-family: Nunito; src: url(data:font/woff2;base64,d09GMgABAAAAAAN4AA8AAAAABswAAAMeAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGhYbgQwcLgZgP1NUQVREAEQRCAqCMIIHCwoAATYCJAMQBCAFhCQHIBuUBcguChxjano4VFxM2vQ1QySkcTXuPsXzT/vRzn0z64qIV7yJJ/EmGSp5Q0EalEiqlgiVyv/L7uv1Qpy/pAfsqfk9Cx44ly4UHRVC2VW+YH51qZj/wAD1eZu/7e/zfzNtkm0LNEs8kV4W4FwgUb7BEox+1s0hcpt7B9o7UD9zWyypoLCgyLAoF3TIGFjCt/9zgmlAiWgimHRUDsvrVQ0d4PV6RpPA690oUcCLFQz/C/KW1hSwQxBdGRfjymFWuCiNpQ7DZwDDscLwyUUTpYlomhJRStJMOi7BBt/PBqzAjvfKyhfW1JZFD4AbvUueQVDCsDDk3yTfBN7BFQ3t838A/UISdgP6BED+1nvsZim+dJYbtD/zgeUIAj7ZZBGCWGbFzydiAggCGUFfAAoNyywFy6ycBsbpAlxRrmEgQGPIthJUmvOuGydAT4H3YPkMSmTbaOxSp3F7M1DceuB47Z4/v7F+08G4H9CV65T/cP2u2BPnHPNo1Njby9l9lqCzm7uyMRu86fNiz8HY2fFxdzcm+/lrt24Fx+vVjuOWhqzPHlHBaqZuP3PXoc7G406+6sZfeO7ufn05DaoJRR5e5HzVrTsP79AzLra5Dm2pRJjYVsGw41e6W67j9sQLnUPzqc0Fimnx1xZHPf0V1+aX3CiUZM329mTN07WNyR3+eb++hXAzh+fUMLjgc9WH8Z3yyaPxrSryLvf0qvAGgKB9DTasXLeHXQv+jTfLj/DT99Yu4E/arcz/tgqsErobMKpAeLRyF0C2LRAeQ88XaXVqd92A/IrQCaF7wtobVgIAumDKFlhKtwe+w49BxmkfyDLrB9lcN1numByxaYAYdVJSCoFpewlG+CpsjU9+k4kD7sNkoxSNaBN4OlktYpSEN64bjcfiEE10Ch6BVZpGaEY1oGqiArsLTWOeiiko6ZJkSZEm3HxNmjWpzFMZbutn6SSjtL1mKvApcDlMNUNDv0uTIlUGSgcOjVL8TAsNJqCNIyildAQH05hRYnAIQmWWJ1kyFl8W6cYkGYfJCxXDbbqExsAUhFky5ZIfyxKLDU8LAgAA); }
</style>
</defs>
<g stroke-linecap="round"><g transform="translate(17.077535418245503 32.086027259866626) rotate(0 -6.0775204356467825 0)"><path d="M0 0 C-2.03 0, -10.13 0, -12.16 0 M0 0 C-2.03 0, -10.13 0, -12.16 0" stroke="#1e1e1e" stroke-width="4" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(16.865850031647774 39.07185193521212) rotate(0 -6.077520435646779 0)"><path d="M0 0 C-2.03 0, -10.13 0, -12.16 0 M0 0 C-2.03 0, -10.13 0, -12.16 0" stroke="#1e1e1e" stroke-width="4" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(16.946763254178506 46.743160072731996) rotate(0 -6.0775204356467825 0)"><path d="M0 0 C-2.03 0, -10.13 0, -12.16 0 M0 0 C-2.03 0, -10.13 0, -12.16 0" stroke="#1e1e1e" stroke-width="4" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(16.680839244467208 53.831041680294504) rotate(0 -6.0775204356467825 0)"><path d="M0 0 C-2.03 0, -10.13 0, -12.16 0 M0 0 C-2.03 0, -10.13 0, -12.16 0" stroke="#1e1e1e" stroke-width="4" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(47.41441860670051 32.80666516147113) rotate(0 -6.077520435646779 0)"><path d="M0 0 C-2.03 0, -10.13 0, -12.16 0 M0 0 C-2.03 0, -10.13 0, -12.16 0" stroke="#1e1e1e" stroke-width="4" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(47.20273322010279 39.79248983681666) rotate(0 -6.077520435646779 0)"><path d="M0 0 C-2.03 0, -10.13 0, -12.16 0 M0 0 C-2.03 0, -10.13 0, -12.16 0" stroke="#1e1e1e" stroke-width="4" fill="none"></path></g></g><mask></mask><g transform="translate(0 0) rotate(0 25.048787534236908 17.010119812063635)"><text x="0" y="25.30097820935094" font-family="Nunito, Segoe UI Emoji" font-size="25.200177499353526px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">CAL</text></g><g stroke-linecap="round"><g transform="translate(26.079917947816256 27.03803518092093) rotate(0 0 14.531098348527223)"><path d="M0 0 C0 4.84, 0 24.22, 0 29.06 M0 0 C0 4.84, 0 24.22, 0 29.06" stroke="#1e1e1e" stroke-width="4" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(47.199941306131535 47.15190785557627) rotate(0 -6.077520435646779 0)"><path d="M0 0 C-2.03 0, -10.13 0, -12.16 0 M0 0 C-2.03 0, -10.13 0, -12.16 0" stroke="#1e1e1e" stroke-width="4" fill="none"></path></g></g><mask></mask></svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -1,75 +0,0 @@
/*
Adds support for pen inversion, a.k.a. the hardware eraser on the back of your pen.
Simply use the eraser on a supported pen, and it will erase. Your previous tool will be restored when the eraser leaves the screen.
(Tested with a surface pen, but should work with all windows ink devices, and probably others)
**Note:** This script will stay active until the *Obsidian* window is closed.
Compatible with my *Auto Draw for Pen* script
```javascript
*/
(function() {
'use strict';
let activated
let revert
function handlePointer(e) {
const activeTool = ea.getExcalidrawAPI().getAppState().activeTool;
const isEraser = e.pointerType === 'pen' && e.buttons & 32
function setActiveTool(t) {
ea.getExcalidrawAPI().setActiveTool(t)
}
if (!activated && isEraser) {
//Store previous tool
const btns = document.querySelectorAll('.App-toolbar input.ToolIcon_type_radio')
for (const i in btns) {
if (btns[i]?.checked) {
revert = btns[i]
}
}
revert = activeTool
// Activate eraser tool
setActiveTool({type: "eraser"})
activated = true
// Force Excalidraw to recognize this the same as pen tip
// https://github.com/excalidraw/excalidraw/blob/4a9fac2d1e5c4fac334201ef53c6f5d2b5f6f9f5/src/components/App.tsx#L2945-L2951
Object.defineProperty(e, 'button', {
value: 0,
writable: false
});
}
// Keep on eraser!
if (isEraser && activated) {
setActiveTool({type: "eraser"})
}
if (activated && !isEraser) {
// Revert tool on release
// revert.click()
setActiveTool(revert)
activated = false
// Force delete "limbo" elements
// This doesn't happen on the web app
// It's a bug caused by switching to eraser during a stroke
ea.setView("active");
var del = []
for (const i in ea.getViewElements()) {
const element = ea.getViewElements()[i];
if (element.opacity === 20) {
del.push(element)
}
}
ea.deleteViewElements(del)
setActiveTool(revert)
}
}
window.addEventListener('pointerdown', handlePointer, { capture: true })
window.addEventListener('pointermove', handlePointer, { capture: true })
})();

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 448 512" style="enable-background:new 0 0 448 512;" xml:space="preserve">
<style type="text/css">
.st0{stroke:#000000;stroke-width:2;stroke-miterlimit:10;}
</style>
<path class="st0" d="M355.8,234.1"/>
<g>
<path class="st0" d="M404.8,293.5L306.9,208l-120,137.4l97.9,85.5c13.6,11.9,34.4,10.5,46.3-3.1l76.8-88
C419.9,326.2,418.5,305.5,404.8,293.5z M389.4,322.2l-78.2,89.6c-3.8,4.3-10.4,4.8-14.8,1l-77.8-68l92-105.3l77.8,68
C392.8,311.2,393.2,317.8,389.4,322.2z"/>
<polygon class="st0" points="52.4,103.7 64.4,238.9 93,263.8 213,126.4 184.4,101.4 "/>
<rect x="108.3" y="185.1" transform="matrix(0.6578 -0.7532 0.7532 0.6578 -108.9276 230.7956)" class="st0" width="182.4" height="100.3"/>
<path class="st0" d="M109.7,381.6c-23.7-22.2-40-49.3-48.9-78.2c8.2-0.9,22.4-3.6,30.1-12.3c-12.6-12.5-25.3-25-37.9-37.5
c0-0.1,0-0.3,0-0.4l-23.6-22c-6,60.7,15.5,123.7,63.7,168.8s112.5,62.4,172.7,52.4l-24.1-22.6C194.8,432.3,146.9,416.4,109.7,381.6
z"/>
<path class="st0" d="M368.8,105.4c-56-52.4-133.5-67.2-201.3-45.5l21,19.6c56.1-13.2,117.7,1.1,163.1,43.7
c33,30.9,51.7,71.2,55.9,112.7c-0.2-0.4-0.3-0.6-0.3-0.6s-25.1-0.1-36.5,12.7c11.8,11.6,23.5,23.3,35.3,34.9c0,0,0,0.1,0,0.1
l2.4,2.3c0.4,0.4,0.9,0.9,1.3,1.3c0,0,0,0,0,0l17.7,16.6C444.7,234.1,424.8,157.7,368.8,105.4z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<!-- Blue star background -->
<path
d="M50 5 L61 40 L98 40 L68 62 L79 95 L50 75 L21 95 L32 62 L2 40 L39 40 Z"
fill="#4a9eff"
stroke="#1e1e1e"
stroke-width="2"
/>
<!-- White "A" text -->
<text
x="50"
y="65"
font-family="Arial"
font-size="40"
fill="white"
text-anchor="middle"
dominant-baseline="middle"
>A</text>
</svg>

After

Width:  |  Height:  |  Size: 517 B

View File

@@ -1,4 +1,7 @@
# Excalidraw Script Engine scripts library
【English | [简体中文](../docs/zh-cn/ea-scripts/README.md)】
Click to watch the intro video:
[![Script Engine](https://user-images.githubusercontent.com/14358394/145684531-8d9c2992-59ac-4ebc-804a-4cce1777ded2.jpg)](https://youtu.be/hePJcObHIso)

View File

@@ -7,29 +7,58 @@ This script enables the selection of elements based on matching properties. Sele
```js */
let config = window.ExcalidrawSelectConfig;
config = Boolean(config) && (Date.now() - config.timestamp < 60000) ? config : null;
const isValidConfig = config && (Date.now() - config.timestamp < 60000);
config = isValidConfig ? config : null;
let elements = ea.getViewSelectedElements();
if(!config && (elements.length !==1)) {
new Notice("Select a single element");
return;
} else {
if(elements.length === 0) {
elements = ea.getViewElements();
if(!config) {
async function shouldAbort() {
if(elements.length === 1) return false;
if(elements.length !== 2) return true;
//maybe container?
const textEl = elements.find(el=>el.type==="text");
if(!textEl || !textEl.containerId) return true;
const containerEl = elements.find(el=>el.id === textEl.containerId);
if(!containerEl) return true;
const id = await utils.suggester(
elements.map(el=>el.type),
elements.map(el=>el.id),
"Select container component"
);
if(!id) return true;
elements = elements.filter(el=>el.id === id);
return false;
}
if(await shouldAbort()) {
new Notice("Select a single element");
return;
}
}
if(Boolean(config) && elements.length === 0) {
elements = ea.getViewElements();
}
const {angle, backgroundColor, fillStyle, fontFamily, fontSize, height, width, opacity, roughness, roundness, strokeColor, strokeStyle, strokeWidth, type, startArrowhead, endArrowhead, fileId} = ea.getViewSelectedElement();
const fragWithHTML = (html) => createFragment((frag) => (frag.createDiv().innerHTML = html));
function lc(x) {
return x?.toLocaleLowerCase();
}
//--------------------------
// RUN
//--------------------------
const run = () => {
selectedElements = elements.filter(el=>
((typeof config.angle === "undefined") || (el.angle === config.angle)) &&
((typeof config.backgroundColor === "undefined") || (el.backgroundColor === config.backgroundColor)) &&
((typeof config.backgroundColor === "undefined") || (lc(el.backgroundColor) === lc(config.backgroundColor))) &&
((typeof config.fillStyle === "undefined") || (el.fillStyle === config.fillStyle)) &&
((typeof config.fontFamily === "undefined") || (el.fontFamily === config.fontFamily)) &&
((typeof config.fontSize === "undefined") || (el.fontSize === config.fontSize)) &&
@@ -38,7 +67,7 @@ const run = () => {
((typeof config.opacity === "undefined") || (el.opacity === config.opacity)) &&
((typeof config.roughness === "undefined") || (el.roughness === config.roughness)) &&
((typeof config.roundness === "undefined") || (el.roundness === config.roundness)) &&
((typeof config.strokeColor === "undefined") || (el.strokeColor === config.strokeColor)) &&
((typeof config.strokeColor === "undefined") || (lc(el.strokeColor) === lc(config.strokeColor))) &&
((typeof config.strokeStyle === "undefined") || (el.strokeStyle === config.strokeStyle)) &&
((typeof config.strokeWidth === "undefined") || (el.strokeWidth === config.strokeWidth)) &&
((typeof config.type === "undefined") || (el.type === config.type)) &&

View File

@@ -17,4 +17,5 @@ if(isNaN(width)) {
const elements=ea.getViewSelectedElements();
ea.copyViewElementsToEAforEditing(elements);
ea.getElements().forEach((el)=>el.strokeWidth=width);
ea.addElementsToView(false,false);
await ea.addElementsToView(false,false);
ea.viewUpdateScene({appState: {currentItemStrokeWidth: width}});

729
ea-scripts/Shade Master.md Normal file
View File

@@ -0,0 +1,729 @@
/*
This is an experimental script. If you find bugs, please consider debugging yourself then submitting a PR on github with the fix, instead of raising an issue. Thank you!
This script modifies the color lightness/hue/saturation/transparency of selected Excalidraw elements and SVG and nested Excalidraw drawings. Select eligible elements in the scene, then run the script.
- The color of Excalidraw elements (lines, ellipses, rectangles, etc.) will be changed by the script.
- The color of SVG elements and nested Excalidraw drawings will only be mapped. When mapping colors, the original image remains unchanged, only a mapping table is created and the image is recolored during rendering of your Excalidraw screen. In case you want to make manual changes you can also edit the mapping in Markdown View Mode under `## Embedded Files`
If you select only a single SVG or nested Excalidraw element, then the script offers an additional feature. You can map colors one by one in the image.
```js*/
const HELP_TEXT = `
<ul>
<li dir="auto">Select SVG images, nested Excalidraw drawings and/or regular Excalidraw elements</li>
<li dir="auto">For a single selected image, you can map colors individually in the color mapping section</li>
<li dir="auto">For Excalidraw elements: stroke and background colors are modified permanently</li>
<li dir="auto">For SVG/nested drawings: original files stay unchanged, color mapping is stored under <code>## Embedded Files</code></li>
<li dir="auto">Using color maps helps maintain links between drawings while allowing different color themes</li>
<li dir="auto">Sliders work on relative scale - the amount of change is applied to current values</li>
<li dir="auto">Unlike Excalidraw's opacity setting which affects the whole element:
<ul>
<li dir="auto">Shade Master can set different opacity for stroke vs background</li>
<li dir="auto">Note: SVG/nested drawing colors are mapped at color name level, thus "black" is different from "#000000"</li>
<li dir="auto">Additionally if the same color is used as fill and stroke the color can only be mapped once</li>
</ul>
</li>
<li dir="auto">This is an experimental script - contributions welcome on GitHub via PRs</li>
</ul>
<div class="excalidraw-videoWrapper"><div>
<iframe src="https://www.youtube.com/embed/ISuORbVKyhQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div></div>
`;
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.7.2")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
/*
SVGColorInfo is returned by ea.getSVGColorInfoForImgElement. Color info will all the color strings in the SVG file plus "fill" which represents the default fill color for SVG icons set at the SVG root element level. Fill if not set defaults to black:
type SVGColorInfo = Map<string, {
mappedTo: string;
fill: boolean;
stroke: boolean;
}>;
In the Excalidraw file under `## Embedded Files` the color map is included after the file. That color map implements ColorMap. ea.updateViewSVGImageColorMap takes a ColorMap as input.
interface ColorMap {
[color: string]: string;
};
*/
// Main script execution
const allElements = ea.getViewSelectedElements();
const svgImageElements = allElements.filter(el => {
if(el.type !== "image") return false;
const file = ea.getViewFileForImageElement(el);
if(!file) return false;
return el.type === "image" && (
file.extension === "svg" ||
ea.isExcalidrawFile(file)
);
});
if(allElements.length === 0) {
new Notice("Select at least one rectangle, ellipse, diamond, line, arrow, freedraw, text or SVG image elment");
return;
}
const originalColors = new Map();
const currentColors = new Map();
const colorInputs = new Map();
const sliderResetters = [];
let terminate = false;
const FORMAT = "Color Format";
const STROKE = "Modify Stroke Color";
const BACKGROUND = "Modify Background Color"
const ACTIONS = ["Hue", "Lightness", "Saturation", "Transparency"];
const precision = [1,2,2,3];
const minLigtness = 1/Math.pow(10,precision[2]);
const maxLightness = 100 - minLigtness;
const minSaturation = 1/Math.pow(10,precision[2]);
let settings = ea.getScriptSettings();
//set default values on first run
if(!settings[STROKE]) {
settings = {};
settings[FORMAT] = {
value: "HEX",
valueset: ["HSL", "RGB", "HEX"],
description: "Output color format."
};
settings[STROKE] = { value: true }
settings[BACKGROUND] = {value: true }
ea.setScriptSettings(settings);
}
function getRegularElements() {
ea.clear();
//loading view elements again as element objects change when colors are updated
const allElements = ea.getViewSelectedElements();
return allElements.filter(el =>
["rectangle", "ellipse", "diamond", "line", "arrow", "freedraw", "text"].includes(el.type)
);
}
const updatedImageElementColorMaps = new Map();
let isWaitingForSVGUpdate = false;
function updateViewImageColors() {
if(terminate || isWaitingForSVGUpdate || updatedImageElementColorMaps.size === 0) {
return;
}
isWaitingForSVGUpdate = true;
elementArray = Array.from(updatedImageElementColorMaps.keys());
colorMapArray = Array.from(updatedImageElementColorMaps.values());
updatedImageElementColorMaps.clear();
ea.updateViewSVGImageColorMap(elementArray, colorMapArray).then(()=>{
isWaitingForSVGUpdate = false;
updateViewImageColors();
});
}
async function storeOriginalColors() {
// Store colors for regular elements
for (const el of getRegularElements()) {
const key = el.id;
const colorData = {
type: "regular",
strokeColor: el.strokeColor,
backgroundColor: el.backgroundColor
};
originalColors.set(key, colorData);
}
// Store colors for SVG elements
for (const el of svgImageElements) {
const colorInfo = await ea.getSVGColorInfoForImgElement(el);
const svgColors = new Map();
for (const [color, info] of colorInfo.entries()) {
svgColors.set(color, {...info});
}
originalColors.set(el.id, {type: "svg",colors: svgColors});
}
copyOriginalsToCurrent();
}
function copyOriginalsToCurrent() {
for (const [key, value] of originalColors.entries()) {
if(value.type === "regular") {
currentColors.set(key, {...value});
} else {
const newColorMap = new Map();
for (const [color, info] of value.colors.entries()) {
newColorMap.set(color, {...info});
}
currentColors.set(key, {type: "svg", colors: newColorMap});
}
}
}
function clearSVGMapping() {
for (const resetter of sliderResetters) {
resetter();
}
// Reset SVG elements
if (svgImageElements.length === 1) {
const el = svgImageElements[0];
const original = originalColors.get(el.id);
const current = currentColors.get(el.id);
if (original && original.type === "svg") {
for (const color of original.colors.keys()) {
current.colors.get(color).mappedTo = color;
}
}
} else {
for (const el of svgImageElements) {
const original = originalColors.get(el.id);
const current = currentColors.get(el.id);
if (original && original.type === "svg") {
for (const color of original.colors.keys()) {
current.colors.get(color).mappedTo = color;
}
}
}
}
run("clear");
}
// Set colors
async function setColors(colors) {
debounceColorPicker = true;
const regularElements = getRegularElements();
if (regularElements.length > 0) {
ea.copyViewElementsToEAforEditing(regularElements);
for (const el of ea.getElements()) {
const original = colors.get(el.id);
if (original && original.type === "regular") {
if (original.strokeColor) el.strokeColor = original.strokeColor;
if (original.backgroundColor) el.backgroundColor = original.backgroundColor;
}
}
await ea.addElementsToView(false, false);
}
// Reset SVG elements
if (svgImageElements.length === 1) {
const el = svgImageElements[0];
const original = colors.get(el.id);
if (original && original.type === "svg") {
const newColorMap = {};
for (const [color, info] of original.colors.entries()) {
newColorMap[color] = info.mappedTo;
// Update UI components
const inputs = colorInputs.get(color);
if (inputs) {
if(info.mappedTo === "fill") {
info.mappedTo = "black";
//"fill" is a special value in case the SVG has no fill color defined (i.e black)
inputs.textInput.setValue("black");
inputs.colorPicker.setValue("#000000");
} else {
const cm = ea.getCM(info.mappedTo);
inputs.textInput.setValue(info.mappedTo);
inputs.colorPicker.setValue(cm.stringHEX({alpha: false}).toLowerCase());
}
}
}
updatedImageElementColorMaps.set(el, newColorMap);
}
} else {
for (const el of svgImageElements) {
const original = colors.get(el.id);
if (original && original.type === "svg") {
const newColorMap = {};
for (const [color, info] of original.colors.entries()) {
newColorMap[color] = info.mappedTo;
}
updatedImageElementColorMaps.set(el, newColorMap);
}
}
}
updateViewImageColors();
}
function modifyColor(color, isDecrease, step, action) {
if (!color) return null;
const cm = ea.getCM(color);
if (!cm) return color;
let modified = cm;
if (modified.lightness === 0) modified = modified.lightnessTo(minLigtness);
if (modified.lightness === 100) modified = modified.lightnessTo(maxLightness);
if (modified.saturation === 0) modified = modified.saturationTo(minSaturation);
switch(action) {
case "Lightness":
// handles edge cases where lightness is 0 or 100 would convert saturation and hue to 0
let lightness = cm.lightness;
const shouldRoundLight = (lightness === minLigtness || lightness === maxLightness);
if (shouldRoundLight) lightness = Math.round(lightness);
lightness += isDecrease ? -step : step;
if (lightness <= 0) lightness = minLigtness;
if (lightness >= 100) lightness = maxLightness;
modified = modified.lightnessTo(lightness);
break;
case "Hue":
modified = isDecrease ? modified.hueBy(-step) : modified.hueBy(step);
break;
case "Transparency":
modified = isDecrease ? modified.alphaBy(-step) : modified.alphaBy(step);
break;
default:
let saturation = cm.saturation;
const shouldRoundSat = saturation === minSaturation;
if (shouldRoundSat) saturation = Math.round(saturation);
saturation += isDecrease ? -step : step;
if (saturation <= 0) saturation = minSaturation;
modified = modified.saturationTo(saturation);
}
const hasAlpha = modified.alpha < 1;
const opts = { alpha: hasAlpha, precision };
const format = settings[FORMAT].value;
switch(format) {
case "RGB": return modified.stringRGB(opts).toLowerCase();
case "HEX": return modified.stringHEX(opts).toLowerCase();
default: return modified.stringHSL(opts).toLowerCase();
}
}
function slider(contentEl, action, min, max, step, invert) {
let prevValue = (max-min)/2;
let debounce = false;
let sliderControl;
new ea.obsidian.Setting(contentEl)
.setName(action)
.addSlider(slider => {
sliderControl = slider;
slider
.setLimits(min, max, step)
.setValue(prevValue)
.onChange(async (value) => {
if (debounce) return;
const isDecrease = invert ? value > prevValue : value < prevValue;
const step = Math.abs(value-prevValue);
prevValue = value;
if(step>0) {
run(action, isDecrease, step);
}
});
}
);
return () => {
debounce = true;
prevValue = (max-min)/2;
sliderControl.setValue(prevValue);
debounce = false;
}
}
function showModal() {
let debounceColorPicker = true;
const modal = new ea.obsidian.Modal(app);
let dirty = false;
modal.onOpen = async () => {
const { contentEl, modalEl } = modal;
const { width, height } = ea.getExcalidrawAPI().getAppState();
modal.bgOpacity = 0;
contentEl.createEl('h2', { text: 'Shade Master' });
const helpDiv = contentEl.createEl("details", {
attr: { style: "margin-bottom: 1em;background: var(--background-secondary); padding: 1em; border-radius: 4px;" }});
helpDiv.createEl("summary", { text: "Help & Usage Guide", attr: { style: "cursor: pointer; color: var(--text-accent);" } });
const helpDetailsDiv = helpDiv.createEl("div", {
attr: { style: "margin-top: 0em; " }
});
helpDetailsDiv.innerHTML = HELP_TEXT;
const component = new ea.obsidian.Setting(contentEl)
.setName(FORMAT)
.setDesc("Output color format")
.addDropdown(dropdown => dropdown
.addOptions({
"HSL": "HSL",
"RGB": "RGB",
"HEX": "HEX"
})
.setValue(settings[FORMAT].value)
.onChange(value => {
settings[FORMAT].value = value;
run();
dirty = true;
})
);
new ea.obsidian.Setting(contentEl)
.setName(STROKE)
.addToggle(toggle => toggle
.setValue(settings[STROKE].value)
.onChange(value => {
settings[STROKE].value = value;
dirty = true;
})
);
new ea.obsidian.Setting(contentEl)
.setName(BACKGROUND)
.addToggle(toggle => toggle
.setValue(settings[BACKGROUND].value)
.onChange(value => {
settings[BACKGROUND].value = value;
dirty = true;
})
);
// lightness and saturation are on a scale of 0%-100%
// Hue is in degrees, 360 for the full circle
// transparency is on a range between 0 and 1 (equivalent to 0%-100%)
// The range for lightness, saturation and transparency are double since
// the input could be at either end of the scale
// The range for Hue is 360 since regarless of the position on the circle moving
// the slider to the two extremes will travel the entire circle
// To modify blacks and whites, lightness first needs to be changed to value between 1% and 99%
sliderResetters.push(slider(contentEl, "Hue", 0, 360, 1, false));
sliderResetters.push(slider(contentEl, "Saturation", 0, 200, 1, false));
sliderResetters.push(slider(contentEl, "Lightness", 0, 200, 1, false));
sliderResetters.push(slider(contentEl, "Transparency", 0, 2, 0.05, true));
// Add color pickers if a single SVG image is selected
if (svgImageElements.length === 1) {
const svgElement = svgImageElements[0];
//note that the objects in currentColors might get replaced when
//colors are reset, thus in the onChange functions I will always
//read currentColorInfo from currentColors based on svgElement.id
const initialColorInfo = currentColors.get(svgElement.id).colors;
const colorSection = contentEl.createDiv();
colorSection.createEl('h3', { text: 'SVG Colors' });
for (const [color, info] of initialColorInfo.entries()) {
const row = new ea.obsidian.Setting(colorSection)
.setName(color === "fill" ? "SVG default" : color)
.setDesc(`${info.fill ? "Fill" : ""}${info.fill && info.stroke ? " & " : ""}${info.stroke ? "Stroke" : ""}`);
row.descEl.style.width = "100px";
row.nameEl.style.width = "100px";
// Create color preview div
const previewDiv = row.controlEl.createDiv();
previewDiv.style.width = "50px";
previewDiv.style.height = "20px";
previewDiv.style.border = "1px solid var(--background-modifier-border)";
if (color === "transparent") {
previewDiv.style.backgroundImage = "linear-gradient(45deg, #808080 25%, transparent 25%), linear-gradient(-45deg, #808080 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #808080 75%), linear-gradient(-45deg, transparent 75%, #808080 75%)";
previewDiv.style.backgroundSize = "10px 10px";
previewDiv.style.backgroundPosition = "0 0, 0 5px, 5px -5px, -5px 0px";
} else {
previewDiv.style.backgroundColor = ea.getCM(color).stringHEX({alpha: false}).toLowerCase();
}
const resetButton = new ea.obsidian.Setting(row.controlEl)
.addButton(button => button
.setButtonText(">>")
.setClass("reset-color-button")
.onClick(async () => {
const original = originalColors.get(svgElement.id);
const current = currentColors.get(svgElement.id);
if (original?.type === "svg") {
const originalInfo = original.colors.get(color);
const currentInfo = current.colors.get(color);
if (originalInfo) {
currentInfo.mappedTo = color;
run("reset single color");
}
}
}))
resetButton.settingEl.style.padding = "0";
resetButton.settingEl.style.border = "0";
// Add text input for color value
const textInput = new ea.obsidian.TextComponent(row.controlEl)
.setValue(info.mappedTo)
.setPlaceholder("Color value");
textInput.inputEl.style.width = "100%";
textInput.onChange(value => {
const lower = value.toLowerCase();
if (lower === color) return;
textInput.setValue(lower);
})
const applyButtonComponent = new ea.obsidian.Setting(row.controlEl)
.addButton(button => button
.setIcon("check")
.setTooltip("Apply")
.onClick(async () => {
const value = textInput.getValue();
try {
if(!CSS.supports("color",value)) {
new Notice (`${value} is not a valid color string`);
return;
}
const cm = ea.getCM(value);
if (cm) {
const format = settings[FORMAT].value;
const alpha = cm.alpha < 1 ? true : false;
const newColor = format === "RGB"
? cm.stringRGB({alpha , precision }).toLowerCase()
: format === "HEX"
? cm.stringHEX({alpha}).toLowerCase()
: cm.stringHSL({alpha, precision }).toLowerCase();
textInput.setValue(newColor);
const currentInfo = currentColors.get(svgElement.id).colors;
currentInfo.get(color).mappedTo = newColor;
run("Update SVG color");
debounceColorPicker = true;
colorPicker.setValue(cm.stringHEX({alpha: false}).toLowerCase());
}
} catch (e) {
console.error("Invalid color value:", e);
}
}));
applyButtonComponent.settingEl.style.padding = "0";
applyButtonComponent.settingEl.style.border = "0";
// Add color picker
const colorPicker = new ea.obsidian.ColorComponent(row.controlEl)
.setValue(ea.getCM(info.mappedTo).stringHEX({alpha: false}).toLowerCase());
colorPicker.colorPickerEl.style.maxWidth = "2.5rem";
// Store references to the components
colorInputs.set(color, {
textInput,
colorPicker,
previewDiv,
resetButton
});
colorPicker.colorPickerEl.addEventListener('click', () => {
debounceColorPicker = false;
});
colorPicker.onChange(async (value) => {
try {
if(!debounceColorPicker) {
const currentInfo = currentColors.get(svgElement.id).colors.get(color);
// Preserve alpha from original color
const originalAlpha = ea.getCM(currentInfo.mappedTo).alpha;
const cm = ea.getCM(value);
cm.alphaTo(originalAlpha);
const alpha = originalAlpha < 1 ? true : false;
const format = settings[FORMAT].value;
const newColor = format === "RGB"
? cm.stringRGB({alpha, precision }).toLowerCase()
: format === "HEX"
? cm.stringHEX({alpha}).toLowerCase()
: cm.stringHSL({alpha, precision }).toLowerCase();
// Update text input
textInput.setValue(newColor);
// Update SVG
currentInfo.mappedTo = newColor;
run("Update SVG color");
}
} catch (e) {
console.error("Invalid color value:", e);
} finally {
debounceColorPicker = true;
}
});
}
}
const buttons = new ea.obsidian.Setting(contentEl);
if(svgImageElements.length > 0) {
buttons.addButton(button => button
.setButtonText("Initialize SVG Colors")
.onClick(() => {
debounceColorPicker = true;
clearSVGMapping();
})
);
}
buttons
.addButton(button => button
.setButtonText("Reset")
.onClick(() => {
for (const resetter of sliderResetters) {
resetter();
}
copyOriginalsToCurrent();
setColors(originalColors);
}))
.addButton(button => button
.setButtonText("Close")
.setCta(true)
.onClick(() => modal.close()));
makeModalDraggable(modalEl);
const maxHeight = Math.round(height * 0.6);
const maxWidth = Math.round(width * 0.9);
modalEl.style.maxHeight = `${maxHeight}px`;
modalEl.style.maxWidth = `${maxWidth}px`;
};
modal.onClose = () => {
terminate = true;
if (dirty) {
ea.setScriptSettings(settings);
}
if(ea.targetView.isDirty()) {
ea.targetView.save(false);
}
};
modal.open();
}
/**
* Add draggable functionality to the modal element.
* @param {HTMLElement} modalEl - The modal element to make draggable.
*/
function makeModalDraggable(modalEl) {
let isDragging = false;
let startX, startY, initialX, initialY;
const header = modalEl.querySelector('.modal-titlebar') || modalEl; // Default to modalEl if no titlebar
header.style.cursor = 'move';
const onPointerDown = (e) => {
// Ensure the event target isn't an interactive element like slider, button, or input
if (e.target.tagName === 'INPUT' || e.target.tagName === 'BUTTON') return;
isDragging = true;
startX = e.clientX;
startY = e.clientY;
const rect = modalEl.getBoundingClientRect();
initialX = rect.left;
initialY = rect.top;
modalEl.style.position = 'absolute';
modalEl.style.margin = '0';
modalEl.style.left = `${initialX}px`;
modalEl.style.top = `${initialY}px`;
};
const onPointerMove = (e) => {
if (!isDragging) return;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
modalEl.style.left = `${initialX + dx}px`;
modalEl.style.top = `${initialY + dy}px`;
};
const onPointerUp = () => {
isDragging = false;
};
header.addEventListener('pointerdown', onPointerDown);
document.addEventListener('pointermove', onPointerMove);
document.addEventListener('pointerup', onPointerUp);
// Clean up event listeners on modal close
modalEl.addEventListener('remove', () => {
header.removeEventListener('pointerdown', onPointerDown);
document.removeEventListener('pointermove', onPointerMove);
document.removeEventListener('pointerup', onPointerUp);
});
}
function executeChange(isDecrease, step, action) {
const modifyStroke = settings[STROKE].value;
const modifyBackground = settings[BACKGROUND].value;
const regularElements = getRegularElements();
// Process regular elements
if (regularElements.length > 0) {
for (const el of regularElements) {
const currentColor = currentColors.get(el.id);
if (modifyStroke && currentColor.strokeColor) {
currentColor.strokeColor = modifyColor(el.strokeColor, isDecrease, step, action);
}
if (modifyBackground && currentColor.backgroundColor) {
currentColor.backgroundColor = modifyColor(el.backgroundColor, isDecrease, step, action);
}
}
}
// Process SVG image elements
if (svgImageElements.length === 1) { // Only update UI for single SVG
const el = svgImageElements[0];
colorInfo = currentColors.get(el.id).colors;
// Process each color in the SVG
for (const [color, info] of colorInfo.entries()) {
let shouldModify = (modifyBackground && info.fill) || (modifyStroke && info.stroke);
if (shouldModify) {
const modifiedColor = modifyColor(info.mappedTo, isDecrease, step, action);
colorInfo.get(color).mappedTo = modifiedColor;
// Update UI components if they exist
const inputs = colorInputs.get(color);
if (inputs) {
const cm = ea.getCM(modifiedColor);
inputs.textInput.setValue(modifiedColor);
inputs.colorPicker.setValue(cm.stringHEX({alpha: false}).toLowerCase());
}
}
}
} else {
if (svgImageElements.length > 0) {
for (const el of svgImageElements) {
const colorInfo = currentColors.get(el.id).colors;
// Process each color in the SVG
for (const [color, info] of colorInfo.entries()) {
let shouldModify = (modifyBackground && info.fill) || (modifyStroke && info.stroke);
if (shouldModify) {
const modifiedColor = modifyColor(info.mappedTo, isDecrease, step, action);
colorInfo.get(color).mappedTo = modifiedColor;
}
}
}
}
}
}
let isRunning = false;
let queue = false;
function processQueue() {
if (!terminate && !isRunning && queue) {
queue = false;
isRunning = true;
setColors(currentColors).then(() => {
isRunning = false;
if (queue) processQueue();
});
}
}
function run(action="Hue", isDecrease=true, step=0) {
// passing invalid action (such as "clear") will bypass rewriting of colors using CM
// this is useful when resetting colors to original values
if(ACTIONS.includes(action)) {
executeChange(isDecrease, step, action);
}
queue = true;
if (!isRunning) processQueue();
}
await storeOriginalColors();
showModal();
processQueue();

View File

@@ -0,0 +1 @@
<svg class="skip" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 17a4 4 0 0 1-8 0V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2Z"/><path d="M16.7 13H19a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H7"/><path d="M 7 17h.01"/><path d="m11 8 2.3-2.3a2.4 2.4 0 0 1 3.404.004L18.6 7.6a2.4 2.4 0 0 1 .026 3.434L9.9 19.8"/></svg>

After

Width:  |  Height:  |  Size: 434 B

View File

@@ -21,11 +21,15 @@ The script will convert your drawing into a slideshow presentation.
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.1.7")) {
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.8.0")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
if(ea.targetView.isDirty()) {
ea.targetView.forceSave(true);
}
const hostLeaf = ea.targetView.leaf;
const hostView = hostLeaf.view;
const statusBarElement = document.querySelector("div.status-bar");
@@ -33,7 +37,7 @@ const ctrlKey = ea.targetView.modifierKeyDown.ctrlKey || ea.targetView.modifierK
const altKey = ea.targetView.modifierKeyDown.altKey || ctrlKey;
const shiftKey = ea.targetView.modifierKeyDown.shiftKey;
const shouldStartWithLastSlide = shiftKey && window.ExcalidrawSlideshow &&
(window.ExcalidrawSlideshow.script === utils.scriptFile.path) && (typeof window.ExcalidrawSlideshow.slide === "number")
(window.ExcalidrawSlideshow.script === utils.scriptFile.path) && (typeof window.ExcalidrawSlideshow.slide?.[ea.targetView.file.path] === "number")
//-------------------------------
//constants
//-------------------------------
@@ -42,6 +46,8 @@ const TRANSITION_DELAY = 1000; //maximum time for transition between slides in m
const FRAME_SLEEP = 1; //milliseconds
const EDIT_ZOOMOUT = 0.7; //70% of original slide zoom, set to a value between 1 and 0
const FADE_LEVEL = 0.1; //opacity of the slideshow controls after fade delay (value between 0 and 1)
const PRINT_SLIDE_WIDTH = 1920;
const PRINT_SLIDE_HEIGHT = 1080;
//using outerHTML because the SVG object returned by Obsidin is in the main workspace window
//but excalidraw might be open in a popout window which has a different document object
const SVG_COG = ea.obsidian.getIcon("lucide-settings").outerHTML;
@@ -53,12 +59,14 @@ const SVG_MAXIMIZE = ea.obsidian.getIcon("lucide-maximize").outerHTML;
const SVG_MINIMIZE = ea.obsidian.getIcon("lucide-minimize").outerHTML;
const SVG_LASER_ON = ea.obsidian.getIcon("lucide-hand").outerHTML;
const SVG_LASER_OFF = ea.obsidian.getIcon("lucide-wand").outerHTML;
const SVG_PRINTER = ea.obsidian.getIcon("lucide-printer").outerHTML;
//-------------------------------
//utility & convenience functions
//-------------------------------
let shouldSaveAfterThePresentation = false;
let isLaserOn = false;
let slide = shouldStartWithLastSlide ? window.ExcalidrawSlideshow.slide : 0;
let slide = shouldStartWithLastSlide ? window.ExcalidrawSlideshow.slide?.[ea.targetView.file.path] : 0;
let isFullscreen = false;
const ownerDocument = ea.targetView.ownerDocument;
const startFullscreen = !altKey;
@@ -197,7 +205,7 @@ const gotoFullscreen = async () => {
}
await waitForExcalidrawResize();
const layerUIWrapper = contentEl.querySelector(".layer-ui__wrapper");
if(!layerUIWrapper.hasClass("excalidraw-hidden")) layerUIWrapper.addClass("excalidraw-hidden");
if(!layerUIWrapper?.hasClass("excalidraw-hidden")) layerUIWrapper.addClass("excalidraw-hidden");
if(toggleFullscreenButton) toggleFullscreenButton.innerHTML = SVG_MINIMIZE;
resetControlPanelElPosition();
isFullscreen = true;
@@ -266,8 +274,8 @@ if(presentationPathType==="line") {
//-----------------------------
// scroll-to-location functions
//-----------------------------
const getNavigationRect = ({ x1, y1, x2, y2 }) => {
const { width, height } = excalidrawAPI.getAppState();
const getNavigationRect = ({ x1, y1, x2, y2, printDimensions }) => {
const { width, height } = printDimensions ? printDimensions : excalidrawAPI.getAppState();
const ratioX = width / Math.abs(x1 - x2);
const ratioY = height / Math.abs(y1 - y2);
let ratio = Math.min(Math.max(ratioX, ratioY), 30);
@@ -350,8 +358,8 @@ const navigate = async (dir) => {
}
if(selectSlideDropdown) selectSlideDropdown.value = slide+1;
await scrollToNextRect(nextRect);
if(window.ExcalidrawSlideshow && (typeof window.ExcalidrawSlideshow.slide === "number")) {
window.ExcalidrawSlideshow.slide = slide;
if(window.ExcalidrawSlideshow && (typeof window.ExcalidrawSlideshow.slide?.[ea.targetView.file.path] === "number")) {
window.ExcalidrawSlideshow.slide[ea.targetView.file.path] = slide;
}
}
@@ -505,6 +513,7 @@ const createPresentationNavigationPanel = () => {
new ea.obsidian.ToggleComponent(el)
.setValue(isHidden)
.onChange(value => {
shouldSaveAfterThePresentation = true;
if(value) {
excalidrawAPI.setToast({
message:"The presentation path remain hidden after the presentation. No need to select the line again. Just click the slideshow button to start the next presentation.",
@@ -528,6 +537,20 @@ const createPresentationNavigationPanel = () => {
}
});
}
if(ea.DEVICE.isDesktop) {
el.createEl("button",{
attr: {
style: `
margin-right: calc(var(--default-button-size)*0.25);`,
title: `Print to PDF\nClick to print slides at ${PRINT_SLIDE_WIDTH}x${
PRINT_SLIDE_HEIGHT}\nHold SHIFT to print the presentation as displayed`
//${!presentationPathLineEl ? "\nHold ALT/OPT to clip frames":""}`
}
}, button => {
button.innerHTML = SVG_PRINTER;
button.onclick = (e) => printToPDF(e);
});
}
el.createEl("button",{
attr: {
style: `
@@ -536,7 +559,7 @@ const createPresentationNavigationPanel = () => {
}
}, button => {
button.innerHTML = SVG_FINISH;
button.onclick = () => exitPresentation()
button.onclick = () => exitPresentation();
});
});
}
@@ -730,6 +753,99 @@ const exitPresentation = async (openForEdit = false) => {
hostView.refreshCanvasOffset();
excalidrawAPI.setActiveTool({type: "selection"});
})
if(!shouldSaveAfterThePresentation) {
ea.targetView.clearDirty();
}
}
//--------------------------
// Print to PDF
//--------------------------
let notice;
let noticeEl;
function setSingleNotice(message) {
if(noticeEl?.parentElement) {
notice.setMessage(message);
return;
}
notice = new Notice(message, 0);
noticeEl = notice.containerEl ?? notice.noticeEl;
}
function hideSingleNotice() {
if(noticeEl?.parentElement) {
notice.hide();
}
}
const translateToZero = ({ top, left, bottom, right }, padding) => {
const {topX, topY, width, height} = ea.getBoundingBox(ea.getViewElements());
const newTop = top - (topY - padding);
const newLeft = left - (topX - padding);
const newBottom = bottom - (topY - padding);
const newRight = right - (topX - padding);
return {
top: newTop,
left: newLeft,
bottom: newBottom,
right: newRight,
};
}
const printToPDF = async (e) => {
const slideWidth = e.shiftKey ? excalidrawAPI.getAppState().width : PRINT_SLIDE_WIDTH;
const slideHeight = e.shiftKey ? excalidrawAPI.getAppState().height : PRINT_SLIDE_HEIGHT;
//const shouldClipFrames = !presentationPathLineEl && e.altKey;
const shouldClipFrames = false;
//huge padding to ensure the HD window always fits the width
//no padding if frames are clipped
const padding = shouldClipFrames ? 0 : Math.round(Math.max(slideWidth,slideHeight)/2)+10;
const st = ea.getExcalidrawAPI().getAppState();
setSingleNotice("Generating image. This can take a longer time depending on the size of the image and speed of your device");
const svg = await ea.createViewSVG({
withBackground: true,
theme: st.theme,
frameRendering: { enabled: shouldClipFrames, name: false, outline: false, clip: shouldClipFrames },
padding,
selectedOnly: false,
skipInliningFonts: false,
embedScene: false,
});
const pages = [];
for(i=0;i<slides.length;i++) {
setSingleNotice(`Generating slide ${i+1}`);
const s = slides[i];
const { top, left, bottom, right } = translateToZero(
getNavigationRect({
...s,
printDimensions: {width: slideWidth, height: slideHeight}
}), padding
);
//always create the new SVG in the main Obsidian workspace (not the popout window, if present)
const host = window.createDiv();
host.innerHTML = svg.outerHTML;
const clonedSVG = host.firstElementChild;
const width = Math.abs(left-right);
const height = Math.abs(top-bottom);
clonedSVG.setAttribute("viewBox", `${left} ${top} ${width} ${height}`);
clonedSVG.setAttribute("width", `${width}`);
clonedSVG.setAttribute("height", `${height}`);
pages.push(clonedSVG);
}
const bgColor = ea.getExcalidrawAPI().getAppState().viewBackgroundColor;
setSingleNotice("Creating PDF Document");
ea.createPDF({
SVG: pages,
scale: { fitToPage: true },
pageProps: {
dimensions: { width: slideWidth, height: slideHeight },
backgroundColor: bgColor,
margin: { left: 0, right: 0, top: 0, bottom: 0 },
alignment: "center"
},
filename: ea.targetView.file.basename + ".pdf",
}).then(()=>hideSingleNotice());
}
//--------------------------
@@ -755,10 +871,15 @@ const start = async () => {
resetControlPanelElPosition();
}
if(presentationPathType === "line") await toggleArrowVisibility(isHidden);
ea.targetView.clearDirty();
}
const timestamp = Date.now();
if(window.ExcalidrawSlideshow && (window.ExcalidrawSlideshow.script === utils.scriptFile.path) && (timestamp - window.ExcalidrawSlideshow.timestamp <400) ) {
if(
window.ExcalidrawSlideshow &&
(window.ExcalidrawSlideshow.script === utils.scriptFile.path) &&
(timestamp - window.ExcalidrawSlideshow.timestamp <400)
) {
if(window.ExcalidrawSlideshowStartTimer) {
window.clearTimeout(window.ExcalidrawSlideshowStartTimer);
delete window.ExcalidrawSlideshowStartTimer;
@@ -769,10 +890,14 @@ if(window.ExcalidrawSlideshow && (window.ExcalidrawSlideshow.script === utils.sc
window.clearTimeout(window.ExcalidrawSlideshowStartTimer);
delete window.ExcalidrawSlideshowStartTimer;
}
window.ExcalidrawSlideshow = {
script: utils.scriptFile.path,
timestamp,
slide: 0
};
if(!window.ExcalidrawSlideshow) {
window.ExcalidrawSlideshow = {
script: utils.scriptFile.path,
slide: {},
};
}
window.ExcalidrawSlideshow.timestamp = timestamp;
window.ExcalidrawSlideshow.slide[ea.targetView.file.path] = 0;
window.ExcalidrawSlideshowStartTimer = window.setTimeout(start,500);
}
}

View File

@@ -18,6 +18,7 @@ if (!ellipse) return;
let lines = elements.filter(el => el.type == "line" || el.type == "arrow");
if (lines.length == 0) lines = ea.getViewElements().filter(el => el.type == "line" || el.type == "arrow");
lines = lines.map(getNormalizedLine);
const subLines = getSubLines(lines);
const angles = subLines.flatMap(line => {
@@ -206,3 +207,70 @@ function isBetween(num, min, max) {
function clamp(number, min, max) {
return Math.max(min, Math.min(number, max));
}
//Same line but with angle=0
function getNormalizedLine(originalElement) {
if(originalElement.angle === 0) return originalElement;
// Get absolute coordinates for all points first
const pointRotateRads = (point, center, angle) => {
const [x, y] = point;
const [cx, cy] = center;
return [
(x - cx) * Math.cos(angle) - (y - cy) * Math.sin(angle) + cx,
(x - cx) * Math.sin(angle) + (y - cy) * Math.cos(angle) + cy
];
};
// Get element absolute coordinates (matching Excalidraw's approach)
const getElementAbsoluteCoords = (element) => {
const points = element.points;
let minX = Infinity;
let minY = Infinity;
let maxX = -Infinity;
let maxY = -Infinity;
for (const [x, y] of points) {
const absX = x + element.x;
const absY = y + element.y;
minX = Math.min(minX, absX);
minY = Math.min(minY, absY);
maxX = Math.max(maxX, absX);
maxY = Math.max(maxY, absY);
}
return [minX, minY, maxX, maxY];
};
// Calculate center point based on absolute coordinates
const [x1, y1, x2, y2] = getElementAbsoluteCoords(originalElement);
const centerX = (x1 + x2) / 2;
const centerY = (y1 + y2) / 2;
// Calculate absolute coordinates of all points
const absolutePoints = originalElement.points.map(([x, y]) => [
x + originalElement.x,
y + originalElement.y
]);
// Rotate all points around the center
const rotatedPoints = absolutePoints.map(point =>
pointRotateRads(point, [centerX, centerY], originalElement.angle)
);
// Convert back to relative coordinates
const newPoints = rotatedPoints.map(([x, y]) => [
x - rotatedPoints[0][0],
y - rotatedPoints[0][1]
]);
const newLineId = ea.addLine(newPoints);
// Set the position of the new line to the first rotated point
const newLine = ea.getElement(newLineId);
newLine.x = rotatedPoints[0][0];
newLine.y = rotatedPoints[0][1];
newLine.angle = 0;
delete ea.elementsDict[newLine.id];
return newLine;
}

File diff suppressed because one or more lines are too long

View File

@@ -94,6 +94,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/Set%20Dimensions.svg"/></div>|[[#Set Dimensions]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Grid.svg"/></div>|[[#Set Grid]]|
|<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/Shade%20Master.svg"/></div>|[[#Shade Master]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Toggle%20Grid.svg"/></div>|[[#Toggle Grid]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Uniform%20size.svg"/></div>|[[#Uniform Size]]|
@@ -115,14 +116,12 @@ 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/Auto%20Draw%20for%20Pen.svg"/></div>|[[#Auto Draw for Pen]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Boolean%20Operations.svg"/></div>|[[#Boolean Operations]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Custom%20Zoom.svg"/></div>|[[#Custom Zoom]]|
|<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/ExcaliAI.svg"/></div>|[[#ExcaliAI]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Excalidraw%20Writing%20Machine.svg"/></div>|[[#Excalidraw Writing Machine]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/GPT-Draw-a-UI.svg"/></div>|[[#GPT Draw-a-UI]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Hardware%20Eraser%20Support.svg"/></div>|[[#Hardware Eraser Support]]|
|<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/PDF%20Page%20Text%20to%20Clipboard.svg"/></div>|[[#PDF Page Text to Clipboard]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Rename%20Image.svg"/></div>|[[#Rename Image]]|
@@ -132,6 +131,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/Select%20Similar%20Elements.svg"/></div>|[[#Select Similar Elements]]|
|<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%20Ellipse.svg"/></div>|[[#Split Ellipse]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Image%20Occlusion.svg"/></div>|[[#Image Occlusion]]|
## Collaboration and Export
**Keywords**: Sharing, Teamwork, Exporting, Distribution, Cooperative, Publish
@@ -148,6 +148,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/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/Convert%20freedraw%20to%20line.svg"/></div>|[[#Convert freedraw to line]]|
|<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/Full-Year%20Calendar%20Generator.svg"/></div>|[[#Full-Year Calendar Generator]]|
## Masking and cropping
**Keywords**: Crop, Mask, Transform images
@@ -156,6 +157,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/Crop%20Vintage%20Mask.svg"/></div>|[[#Crop Vintage Mask]]|
---
# Description and Installation
@@ -184,12 +186,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/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>
## Auto Draw for Pen
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Auto%20Draw%20for%20Pen.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/threethan'>@threethan</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%20Draw%20for%20Pen.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Automatically switches from select mode to drawing mode when hovering a pen, and then back.</td></tr></table>
## Auto Layout
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Auto%20Layout.md
@@ -275,6 +271,8 @@ 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/Crop%20Vintage%20Mask.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Adds a rounded mask to the image by adding a full cover black mask and a rounded rectangle white mask. The script is also useful for adding just a black mask. In this case, run the script, then delete the white mask and add your custom white mask.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-crop-vintage.jpg'></td></tr></table>
## Custom Zoom
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Custom%20Zoom.md
@@ -397,18 +395,23 @@ 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/Excalidraw%20Writing%20Machine.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Creates a hierarchical Markdown document out of a visual layout of an article that can be fed to Templater and converted into an article using AI for Templater.<br>Watch this video to understand how the script is intended to work:<br><iframe width="400" height="225" src="https://www.youtube.com/embed/zvRpCOZAUSs" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><br>You can download the sample Obsidian Templater file from <a href="https://gist.github.com/zsviczian/bf49d4b2d401f5749aaf8c2fa8a513d9">here</a>. You can download the demo PDF document showcased in the video from <a href="https://zsviczian.github.io/DemoArticle-AtomicHabits.pdf">here</a>.</td></tr></table>
## Full-Year Calendar Generator
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Full-Year%20Calendar%20Generator.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/simonperet'>@simonperet</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/Full-Year%20Calendar%20Generator.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Generates a complete calendar for a specified year.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-full-year-calendar-exemple.excalidraw.png'></td></tr></table>
## GPT Draw-a-UI
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/GPT-Draw-a-UI.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/GPT-Draw-a-UI.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script was discontinued in favor of ExcaliAI. Draw a UI and let GPT create the code for you.<br><iframe width="400" height="225" src="https://www.youtube.com/embed/y3kHl_6Ll4w" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-draw-a-ui.jpg'></td></tr></table>
## Hardware Eraser Support
## Image Occlusion
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Hardware%20Eraser%20Support.md
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Image%20Occlusion.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/threethan'>@threethan</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/Hardware%20Eraser%20Support.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Allows you to use inversion, aka hardware eraser, on supported pens.</td></tr></table>
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/TrillStones'>@TrillStones</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/Image%20Occlusion.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">An Excalidraw script for creating Anki image occlusion cards in Obsidian, similar to Anki's Image Occlusion Enhanced add-on but integrated into your Obsidian workflow.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-image-occlusion.png'></td></tr></table>
## Invert colors
```excalidraw-script-install
@@ -568,6 +571,13 @@ 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>
## Shade Master
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Shade%20Master.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/Shade%20Master.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">You can modify the colors of SVG images, embedded files, and Excalidraw elements in a drawing by changing Hue, Saturation, Lightness and Transparency; and if only a single SVG or nested Excalidraw drawing is selected, then you can remap image colors.<br><iframe width="560" height="315" src="https://www.youtube.com/embed/ISuORbVKyhQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></td></tr></table>
## Slideshow
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Slideshow.md

1
foo
View File

@@ -1 +0,0 @@

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

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

View File

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

9212
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -8,24 +8,28 @@
"lib/**/*"
],
"scripts": {
"dev": "cross-env NODE_ENV=development rollup --config rollup.config.js -w",
"dev": "cross-env NODE_ENV=development rollup --config rollup.config.js",
"build": "cross-env NODE_ENV=production rollup --config rollup.config.js",
"lib": "cross-env NODE_ENV=lib rollup --config rollup.config.js",
"code:fix": "eslint --max-warnings=0 --ext .ts,.tsx ./src --fix",
"madge": "madge --circular ."
"madge": "madge --circular .",
"build:mathjax": "cd MathjaxToSVG && npm run build",
"build:all": "npm run build:mathjax && npm run build",
"dev:mathjax": "cd MathjaxToSVG && npm run dev",
"dev:all": "npm run dev:mathjax && npm run dev"
},
"keywords": [],
"author": "",
"license": "MIT",
"dependencies": {
"dependencies": {
"@popperjs/core": "^2.11.8",
"@zsviczian/excalidraw": "0.17.1-obsidian-48",
"@zsviczian/excalidraw": "0.18.0-6",
"chroma-js": "^2.4.2",
"clsx": "^2.0.0",
"@zsviczian/colormaster": "^1.2.2",
"gl-matrix": "^3.4.3",
"js-yaml": "^4.1.0",
"lucide-react": "^0.263.1",
"lucide-react": "^0.479.0",
"mathjax-full": "^3.2.2",
"monkey-around": "^2.3.0",
"nanoid": "^4.0.2",
@@ -34,9 +38,11 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"roughjs": "^4.5.2",
"woff2sfnt-sfnt2woff": "^1.0.0"
"woff2sfnt-sfnt2woff": "^1.0.0",
"es6-promise-pool": "2.5.0"
},
"devDependencies": {
"jsesc": "^3.0.2",
"@babel/core": "^7.22.9",
"@babel/preset-env": "^7.22.10",
"@babel/preset-react": "^7.22.5",
@@ -51,7 +57,8 @@
"@rollup/plugin-commonjs": "^26.0.1",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-replace": "^5.0.2",
"@rollup/plugin-typescript": "^11.1.6",
"@rollup/plugin-typescript": "^12.1.2",
"@rollup/plugin-json": "^6.1.0",
"@types/chroma-js": "^2.4.0",
"@types/js-beautify": "^1.14.0",
"@types/js-yaml": "^4.0.9",
@@ -66,16 +73,18 @@
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"lz-string": "^1.5.0",
"obsidian": "1.5.7-1",
"obsidian": "^1.7.2",
"prettier": "^3.0.1",
"rollup": "^2.70.1",
"rollup-plugin-copy": "^3.5.0",
"@zsviczian/rollup-plugin-postprocess": "^1.0.3",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.34.1",
"tslib": "^2.6.1",
"rollup-plugin-typescript2": "^0.36.0",
"tslib": "^2.8.1",
"ttypescript": "^1.5.15",
"typescript": "^5.2.2"
"typescript": "^5.7.3",
"fs-extra": "^11.2.0",
"uglify-js": "^3.19.3"
},
"resolutions": {
"@typescript-eslint/typescript-estree": "5.3.0"

View File

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

View File

@@ -5,28 +5,95 @@ import { terser } from "rollup-plugin-terser";
import copy from "rollup-plugin-copy";
import typescript2 from "rollup-plugin-typescript2";
import fs from 'fs';
import path from 'path';
import LZString from 'lz-string';
import postprocess from '@zsviczian/rollup-plugin-postprocess';
import cssnano from 'cssnano';
import jsesc from 'jsesc';
import { minify } from 'uglify-js';
import json from '@rollup/plugin-json';
// Load environment variables
import dotenv from 'dotenv';
dotenv.config();
const DIST_FOLDER = 'dist';
const absolutePath = path.resolve(DIST_FOLDER);
fs.mkdirSync(absolutePath, { recursive: true });
const isProd = (process.env.NODE_ENV === "production");
const isLib = (process.env.NODE_ENV === "lib");
console.log(`Running: ${process.env.NODE_ENV}`);
console.log(`Running: ${process.env.NODE_ENV}; isProd: ${isProd}; isLib: ${isLib}`);
const excalidraw_pkg = isLib ? "" : isProd
// Excalidraw React 19 compatiblity shim
// Create JSX runtime compatibility layer
const jsxRuntimeShim = `
const jsx = (type, props, key) => {
return React.createElement(type, props);
};
const jsxs = (type, props, key) => {
return React.createElement(type, props);
};
const Fragment = React.Fragment;
React.jsx = jsx;
React.jsxs = jsxs;
React.Fragment = Fragment;
React.jsxRuntime = { jsx, jsxs, Fragment };
window.__WEBPACK_EXTERNAL_MODULE_react_jsx_runtime__ = { jsx, jsxs, Fragment };
window.__WEBPACK_EXTERNAL_MODULE_react_jsx_dev_runtime__ = { jsx, jsxs, Fragment, jsxDEV: jsx };
window['react/jsx-runtime'] = { jsx, jsxs, Fragment };
window['react/jsx-dev-runtime'] = { jsx, jsxs, Fragment, jsxDEV: jsx };
`;
const mathjaxtosvg_pkg = isLib ? "" : fs.readFileSync("./MathjaxToSVG/dist/index.js", "utf8");
const LANGUAGES = ['ru', 'zh-cn']; //english is not compressed as it is always loaded by default
function trimLastSemicolon(input) {
if (input.endsWith(";")) {
return input.slice(0, -1);
}
return input;
}
function minifyCode(code) {
const minified = minify(code, {
compress: {
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2170
reduce_vars: false,
},
mangle: true,
output: {
comments: false,
beautify: false,
}
});
if (minified.error) {
throw new Error(minified.error);
}
return minified.code;
}
function compressLanguageFile(lang) {
const inputDir = "./src/lang/locale";
const filePath = `${inputDir}/${lang}.ts`;
let content = fs.readFileSync(filePath, "utf-8");
content = trimLastSemicolon(content.split("export default")[1].trim());
return LZString.compressToBase64(minifyCode(`x = ${content};`));
}
const excalidraw_pkg = isLib ? "" : minifyCode(isProd
? fs.readFileSync("./node_modules/@zsviczian/excalidraw/dist/excalidraw.production.min.js", "utf8")
: fs.readFileSync("./node_modules/@zsviczian/excalidraw/dist/excalidraw.development.js", "utf8");
const react_pkg = isLib ? "" : isProd
: fs.readFileSync("./node_modules/@zsviczian/excalidraw/dist/excalidraw.development.js", "utf8"));
const react_pkg = isLib ? "" : minifyCode(isProd
? fs.readFileSync("./node_modules/react/umd/react.production.min.js", "utf8")
: fs.readFileSync("./node_modules/react/umd/react.development.js", "utf8");
const reactdom_pkg = isLib ? "" : isProd
: fs.readFileSync("./node_modules/react/umd/react.development.js", "utf8"));
const reactdom_pkg = isLib ? "" : minifyCode(isProd
? fs.readFileSync("./node_modules/react-dom/umd/react-dom.production.min.js", "utf8")
: fs.readFileSync("./node_modules/react-dom/umd/react-dom.development.js", "utf8");
: fs.readFileSync("./node_modules/react-dom/umd/react-dom.development.js", "utf8"));
const lzstring_pkg = isLib ? "" : fs.readFileSync("./node_modules/lz-string/libs/lz-string.min.js", "utf8");
if (!isLib) {
@@ -47,19 +114,25 @@ if (!isLib) {
const manifestStr = isLib ? "" : fs.readFileSync("manifest.json", "utf-8");
const manifest = isLib ? {} : JSON.parse(manifestStr);
if (!isLib) console.log(manifest.version);
if (!isLib) {
console.log(manifest.version);
}
const packageString = isLib
? ""
: ';' + lzstring_pkg +
'\nlet EXCALIDRAW_PACKAGES = LZString.decompressFromBase64("' + LZString.compressToBase64(react_pkg + reactdom_pkg + excalidraw_pkg) + '");\n' +
'let {react, reactDOM, excalidrawLib} = window.eval.call(window, `(function() {' +
'${EXCALIDRAW_PACKAGES};' +
'return {react: React, reactDOM: ReactDOM, excalidrawLib: ExcalidrawLib};})();`);\n' +
'let PLUGIN_VERSION="' + manifest.version + '";';
: ';const INITIAL_TIMESTAMP=Date.now();' + lzstring_pkg +
'\nlet REACT_PACKAGES = `' +
jsesc(react_pkg + reactdom_pkg + jsxRuntimeShim, { quotes: 'backtick' }) +
'`;\n' +
'const unpackExcalidraw = () => LZString.decompressFromBase64("' + LZString.compressToBase64(excalidraw_pkg) + '");\n' +
'let {react, reactDOM } = new Function(`${REACT_PACKAGES}; return {react: React, reactDOM: ReactDOM};`)();\n' +
'let excalidrawLib = {};\n' +
'const loadMathjaxToSVG = () => new Function(`${LZString.decompressFromBase64("' + LZString.compressToBase64(mathjaxtosvg_pkg) + '")}; return MathjaxToSVG;`)();\n' +
`const PLUGIN_LANGUAGES = {${LANGUAGES.map(lang => `"${lang}": "${compressLanguageFile(lang)}"`).join(",")}};\n` +
'const PLUGIN_VERSION="' + manifest.version + '";';
const BASE_CONFIG = {
input: 'src/main.ts',
input: 'src/core/main.ts',
external: [
'@codemirror/autocomplete',
'@codemirror/collab',
@@ -81,6 +154,7 @@ const BASE_CONFIG = {
const getRollupPlugins = (tsconfig, ...plugins) => [
typescript2(tsconfig),
json(),
replace({
preventAssignment: true,
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
@@ -96,9 +170,15 @@ const BUILD_CONFIG = {
entryFileNames: 'main.js',
format: 'cjs',
exports: 'default',
inlineDynamicImports: true, // Add this line only
},
plugins: getRollupPlugins(
{tsconfig: isProd ? "tsconfig.json" : "tsconfig.dev.json"},
{
tsconfig: isProd ? "tsconfig.json" : "tsconfig.dev.json",
sourcemap: !isProd,
clean: true,
//verbosity: isProd ? 1 : 2,
},
...(isProd ? [
terser({
toplevel: false,
@@ -123,10 +203,10 @@ const BUILD_CONFIG = {
const LIB_CONFIG = {
...BASE_CONFIG,
input: "src/index.ts",
input: "src/core/index.ts",
output: {
dir: "lib",
sourcemap: true,
sourcemap: false,
format: "cjs",
name: "Excalidraw (Library)",
},

View File

@@ -1,368 +0,0 @@
import ExcalidrawPlugin from "./main";
export class OneOffs {
private plugin: ExcalidrawPlugin;
constructor(plugin: ExcalidrawPlugin) {
this.plugin = plugin;
}
/*
public patchCommentBlock() {
//This is a once off cleanup process to remediate incorrectly placed comment %% before # Text Elements
if (!this.plugin.settings.patchCommentBlock) {
return;
}
const plugin = this.plugin;
log(
`${window
.moment()
.format("HH:mm:ss")}: Excalidraw will patch drawings in 5 minutes`,
);
setTimeout(async () => {
await plugin.loadSettings();
if (!plugin.settings.patchCommentBlock) {
log(
`${window
.moment()
.format(
"HH:mm:ss",
)}: Excalidraw patching aborted because synched data.json is already patched`,
);
return;
}
log(
`${window
.moment()
.format("HH:mm:ss")}: Excalidraw is starting the patching process`,
);
let i = 0;
const excalidrawFiles = plugin.app.vault.getFiles();
for (const f of (excalidrawFiles || []).filter((f: TFile) =>
plugin.isExcalidrawFile(f),
)) {
if (
f.extension !== "excalidraw" && //legacy files do not need to be touched
plugin.app.workspace.getActiveFile() !== f
) {
//file is currently being edited
let drawing = await plugin.app.vault.read(f);
const orig_drawing = drawing;
drawing = drawing.replaceAll("\r\n", "\n").replaceAll("\r", "\n"); //Win, Mac, Linux compatibility
drawing = drawing.replace(
"\n%%\n# Text Elements\n",
"\n# Text Elements\n",
);
if (drawing.search("\n%%\n# Drawing\n") === -1) {
const sceneJSONandPOS = getJSON(drawing);
drawing = `${drawing.substr(
0,
sceneJSONandPOS.pos,
)}\n%%\n# Drawing\n\`\`\`json\n${sceneJSONandPOS.scene}\n\`\`\`%%`;
}
if (drawing !== orig_drawing) {
i++;
log(`Excalidraw patched: ${f.path}`);
await plugin.app.vault.modify(f, drawing);
}
}
}
plugin.settings.patchCommentBlock = false;
plugin.saveSettings();
log(
`${window
.moment()
.format("HH:mm:ss")}: Excalidraw patched in total ${i} files`,
);
}, 300000); //5 minutes
}
public migrationNotice() {
if (this.plugin.settings.loadCount > 0) {
return;
}
const plugin = this.plugin;
plugin.app.workspace.onLayoutReady(async () => {
plugin.settings.loadCount++;
plugin.saveSettings();
const files = plugin.app.vault
.getFiles()
.filter((f) => f.extension === "excalidraw");
if (files.length > 0) {
const prompt = new MigrationPrompt(plugin.app, plugin);
prompt.open();
}
});
}
public imageElementLaunchNotice() {
if (!this.plugin.settings.imageElementNotice) {
return;
}
const plugin = this.plugin;
plugin.app.workspace.onLayoutReady(async () => {
const prompt = new ImageElementNotice(plugin.app, plugin);
prompt.open();
});
}
public wysiwygPatch() {
if (this.plugin.settings.patchCommentBlock) {
return;
} //the comment block patch needs to happen first (unlikely that someone has waited this long with the update...)
//This is a once off process to patch excalidraw files remediate incorrectly placed comment %% before # Text Elements
if (
!(
this.plugin.settings.runWYSIWYGpatch ||
this.plugin.settings.fixInfinitePreviewLoop
)
) {
return;
}
const plugin = this.plugin;
log(
`${window
.moment()
.format(
"HH:mm:ss",
)}: Excalidraw will patch drawings to support WYSIWYG in 7 minutes`,
);
setTimeout(async () => {
await plugin.loadSettings();
if (
!(
this.plugin.settings.runWYSIWYGpatch ||
this.plugin.settings.fixInfinitePreviewLoop
)
) {
log(
`${window
.moment()
.format(
"HH:mm:ss",
)}: Excalidraw patching aborted because synched data.json is already patched`,
);
return;
}
log(
`${window
.moment()
.format("HH:mm:ss")}: Excalidraw is starting the patching process`,
);
let i = 0;
const excalidrawFiles = plugin.app.vault.getFiles();
for (const f of (excalidrawFiles || []).filter((f: TFile) =>
plugin.isExcalidrawFile(f),
)) {
if (
f.extension !== "excalidraw" && //legacy files do not need to be touched
plugin.app.workspace.getActiveFile() !== f
) {
//file is currently being edited
try {
const excalidrawData = new ExcalidrawData(plugin);
const data = await plugin.app.vault.read(f);
const textMode = getTextMode(data);
await excalidrawData.loadData(data, f, textMode);
let trimLocation = data.search(/(^%%\n)?# Text Elements\n/m);
if (trimLocation == -1) {
trimLocation = data.search(/(%%\n)?# Drawing\n/);
}
if (trimLocation > -1) {
let header = data
.substring(0, trimLocation)
.replace(
/excalidraw-plugin:\s.*\n/,
`${FRONTMATTER_KEY}: ${
textMode == TextMode.raw ? "raw\n" : "parsed\n"
}`,
);
header = header.replace(
/cssclass:[\s]*excalidraw-hide-preview-text[\s]*\n/,
"",
);
const REG_IMG = /(^---[\w\W]*?---\n)(!\[\[.*?]]\n(%%\n)?)/m; //(%%\n)? because of 1.4.8-beta... to be backward compatible with anyone who installed that version
if (header.match(REG_IMG)) {
header = header.replace(REG_IMG, "$1");
}
const newData = header + excalidrawData.generateMD();
if (data !== newData) {
i++;
log(`Excalidraw patched: ${f.path}`);
await plugin.app.vault.modify(f, newData);
}
}
} catch (e) {
errorlog({
where: "OneOffs.wysiwygPatch",
message: `Unable to process: ${f.path}`,
error: e,
});
}
}
}
plugin.settings.runWYSIWYGpatch = false;
plugin.settings.fixInfinitePreviewLoop = false;
plugin.saveSettings();
log(
`${window
.moment()
.format("HH:mm:ss")}: Excalidraw patched in total ${i} files`,
);
}, 420000); //7 minutes
}
}
class MigrationPrompt extends Modal {
private plugin: ExcalidrawPlugin;
constructor(app: App, plugin: ExcalidrawPlugin) {
super(app);
this.plugin = plugin;
}
onOpen(): void {
this.titleEl.setText("Welcome to Excalidraw 1.2");
this.createForm();
}
onClose(): void {
this.contentEl.empty();
}
createForm(): void {
const div = this.contentEl.createDiv();
// div.addClass("excalidraw-prompt-div");
// div.style.maxWidth = "600px";
div.createEl("p", {
text: "This version comes with tons of new features and possibilities. Please read the description in Community Plugins to find out more.",
});
div.createEl("p", { text: "" }, (el) => {
el.innerHTML =
"Drawings you've created with version 1.1.x need to be converted to take advantage of the new features. You can also continue to use them in compatibility mode. " +
"During conversion your old *.excalidraw files will be replaced with new *.excalidraw.md files.";
});
div.createEl("p", { text: "" }, (el) => {
//files manually follow one of two options:
el.innerHTML =
"To convert your drawings you have the following options:<br><ul>" +
"<li>Click <code>CONVERT FILES</code> now to convert all of your *.excalidraw files, or if you prefer to make a backup first, then click <code>CANCEL</code>.</li>" +
"<li>In the Command Palette select <code>Excalidraw: Convert *.excalidraw files to *.excalidraw.md files</code></li>" +
"<li>Right click an <code>*.excalidraw</code> file in File Explorer and select one of the following options to convert files one by one: <ul>" +
"<li><code>*.excalidraw => *.excalidraw.md</code></li>" +
"<li><code>*.excalidraw => *.md (Logseq compatibility)</code>. This option will retain the original *.excalidraw file next to the new Obsidian format. " +
"Make sure you also enable <code>Compatibility features</code> in Settings for a full solution.</li></ul></li>" +
"<li>Open a drawing in compatibility mode and select <code>Convert to new format</code> from the <code>Options Menu</code></li></ul>";
});
div.createEl("p", {
text: "This message will only appear maximum 3 times in case you have *.excalidraw files in your Vault.",
});
const bConvert = div.createEl("button", { text: "CONVERT FILES" });
bConvert.onclick = () => {
this.plugin.convertExcalidrawToMD();
this.close();
};
const bCancel = div.createEl("button", { text: "CANCEL" });
bCancel.onclick = () => {
this.close();
};
}
}
class ImageElementNotice extends Modal {
private plugin: ExcalidrawPlugin;
private saveChanges: boolean = false;
constructor(app: App, plugin: ExcalidrawPlugin) {
super(app);
this.plugin = plugin;
}
onOpen(): void {
this.titleEl.setText("Image Elements have arrived!");
this.createForm();
}
async onClose() {
this.contentEl.empty();
if (!this.saveChanges) {
return;
}
await this.plugin.loadSettings();
this.plugin.settings.imageElementNotice = false;
this.plugin.saveSettings();
}
createForm(): void {
const div = this.contentEl.createDiv();
//div.addClass("excalidraw-prompt-div");
//div.style.maxWidth = "600px";
div.createEl("p", { text: "" }, (el) => {
el.innerHTML =
"Welcome to Obsidian-Excalidraw 1.4! I've added Image Elements. " +
"Please watch the video below to learn how to use this new feature.";
});
div.createEl("p", { text: "" }, (el) => {
el.innerHTML =
"<u>⚠ WARNING:</u> Opening new drawings with an older version of the plugin will lead to loss of images. " +
"Update the plugin on all your devices.";
});
div.createEl("p", { text: "" }, (el) => {
el.innerHTML =
"Since March, I have spent most of my free time building this plugin. Close to 75 workdays worth of my time (assuming 8-hour days). " +
"Some of you have already bought me a coffee. THANK YOU! Your support really means a lot to me! If you have not yet done so, please consider clicking the button below.";
});
const coffeeDiv = div.createDiv("coffee");
coffeeDiv.addClass("ex-coffee-div");
const coffeeLink = coffeeDiv.createEl("a", {
href: "https://ko-fi.com/zsolt",
});
const coffeeImg = coffeeLink.createEl("img", {
attr: {
src: "https://cdn.ko-fi.com/cdn/kofi3.png?v=3",
},
});
coffeeImg.height = 45;
div.createEl("p", { text: "" }, (el) => {
//files manually follow one of two options:
el.style.textAlign = "center";
el.innerHTML =
'<iframe width="560" height="315" src="https://www.youtube.com/embed/_c_0zpBJ4Xc?start=20" title="YouTube video player" ' +
'frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" ' +
"allowfullscreen></iframe>";
});
div.createEl("p", { text: "" }, (el) => {
//files manually follow one of two options:
el.style.textAlign = "right";
const bOk = el.createEl("button", { text: "OK - Don't show this again" });
bOk.onclick = () => {
this.saveChanges = true;
this.close();
};
const bCancel = el.createEl("button", {
text: "CANCEL - Read next time",
});
bCancel.onclick = () => {
this.saveChanges = false;
this.close();
};
});
}
*/
}

View File

@@ -1,6 +1,6 @@
import { Copy, Crop, Globe, RotateCcw, Scan, Settings, TextSelect } from "lucide-react";
import * as React from "react";
import { PenStyle } from "src/PenTypes";
import { PenStyle } from "src/types/penTypes";
export const ICONS = {
ExportImage: (

View File

@@ -105,6 +105,41 @@
*/
//ea.onFileCreateHook = (data) => {};
/**
* If set, this callback is triggered when a image is being saved in Excalidraw.
* You can use this callback to customize the naming and path of pasted images to avoid
* default names like "Pasted image 123147170.png" being saved in the attachments folder,
* and instead use more meaningful names based on the Excalidraw file or other criteria,
* plus save the image in a different folder.
*
* If the function returns null or undefined, the normal Excalidraw operation will continue
* with the excalidraw generated name and default path.
* If a filepath is returned, that will be used. Include the full Vault filepath and filename
* with the file extension.
* The currentImageName is the name of the image generated by excalidraw or provided during paste.
*
* @param data - An object containing the following properties:
* @property {string} [currentImageName] - Default name for the image.
* @property {string} drawingFilePath - The file path of the Excalidraw file where the image is being used.
*
* @returns {string} - The new filepath for the image including full vault path and extension.
*
* Example usage:
* ```
* onImageFilePathHook: (data) => {
* const { currentImageName, drawingFilePath } = data;
* const ext = currentImageName.split('.').pop();
* // Generate a new filepath based on the drawing file name and other criteria
* return `${drawingFileName} - ${currentImageName || 'image'}.${ext}`;
* }
* ```
* onImageFilePathHook: (data: {
* currentImageName: string; // Excalidraw generated name of the image, or the name received from the file system.
* drawingFilePath: string; // The full filepath of the Excalidraw file where the image is being used.
* }) => string = null;
*/
//ea.onImageFilePathHook = (data) => {};
/**
* If set, this callback is triggered whenever the active canvas color changes
* onCanvasColorChangeHook: (

View File

@@ -1,8 +1,8 @@
import { customAlphabet } from "nanoid";
import { DeviceType } from "../types/types";
import { ExcalidrawLib } from "../ExcalidrawLib";
import { ExcalidrawLib } from "../types/excalidrawLib";
import { moment } from "obsidian";
import ExcalidrawPlugin from "src/main";
import ExcalidrawPlugin from "src/core/main";
import { DeviceType } from "src/types/types";
//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 let EXCALIDRAW_PLUGIN: ExcalidrawPlugin = null;
@@ -26,7 +26,8 @@ export const ERROR_IFRAME_CONVERSION_CANCELED = "iframe conversion canceled";
declare const excalidrawLib: typeof ExcalidrawLib;
export const LOCALE = moment.locale();
export const LOCALE = localStorage.getItem("language")?.toLowerCase() || "en";
export const CJK_FONTS = "CJK Fonts";
export const obsidianToExcalidrawMap: { [key: string]: string } = {
'en': 'en-US',
@@ -82,7 +83,7 @@ export const obsidianToExcalidrawMap: { [key: string]: string } = {
};
export const {
export let {
sceneCoordsToViewportCoords,
viewportCoordsToSceneCoords,
determineFocusDistance,
@@ -103,9 +104,39 @@ export const {
getContainerElement,
refreshTextDimensions,
getCSSFontDefinition,
loadSceneFonts,
loadMermaid,
} = excalidrawLib;
export function updateExcalidrawLib() {
({
sceneCoordsToViewportCoords,
viewportCoordsToSceneCoords,
determineFocusDistance,
intersectElementWithLine,
getCommonBoundingBox,
getMaximumGroups,
measureText,
getLineHeight,
wrapText,
getFontString,
getBoundTextMaxWidth,
exportToSvg,
exportToBlob,
mutateElement,
restore,
mermaidToExcalidraw,
getFontFamilyString,
getContainerElement,
refreshTextDimensions,
getCSSFontDefinition,
loadSceneFonts,
loadMermaid,
} = excalidrawLib);
}
export const FONTS_STYLE_ID = "excalidraw-custom-fonts";
export const CJK_STYLE_ID = "excalidraw-cjk-fonts";
export function JSON_parse(x: string): any {
return JSON.parse(x.replaceAll("&#91;", "["));
@@ -124,7 +155,12 @@ export const DEVICE: DeviceType = {
isAndroid: document.body.hasClass("is-android"),
};
export const ROOTELEMENTSIZE = (() => {
export let ROOTELEMENTSIZE: number = 16;
export function setRootElementSize(size?:number) {
if(size) {
ROOTELEMENTSIZE = size;
return;
}
const tempElement = document.createElement('div');
tempElement.style.fontSize = '1rem';
tempElement.style.display = 'none'; // Hide the element
@@ -133,7 +169,7 @@ export const ROOTELEMENTSIZE = (() => {
const pixelSize = parseFloat(computedStyle.fontSize);
document.body.removeChild(tempElement);
return pixelSize;
})();
};
export const nanoid = customAlphabet(
"1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
@@ -162,11 +198,15 @@ export const REG_BLOCK_REF_CLEAN = /[!"#$%&()*+,.:;<=>?@^`{|}~\/\[\]\\\r\n]/g;
// /[!"#$%&()*+,.:;<=>?@^`{|}~\/\[\]\\]/g;
// https://discord.com/channels/686053708261228577/989603365606531104/1000128926619816048
// /\+|\/|~|=|%|\(|\)|{|}|,|&|\.|\$|!|\?|;|\[|]|\^|#|\*|<|>|&|@|\||\\|"|:|\s/g;
export const IMAGE_TYPES = ["jpeg", "jpg", "png", "gif", "svg", "webp", "bmp", "ico", "jtif", "tif"];
export const IMAGE_TYPES = ["jpeg", "jpg", "png", "gif", "svg", "webp", "bmp", "ico", "jtif", "tif", "jfif", "avif"];
export const ANIMATED_IMAGE_TYPES = ["gif", "webp", "apng", "svg"];
export const EXPORT_TYPES = ["svg", "dark.svg", "light.svg", "png", "dark.png", "light.png"];
export const MAX_IMAGE_SIZE = 500;
export const VIDEO_TYPES = ["mp4", "webm", "ogv", "mov", "mkv"];
export const AUDIO_TYPES = ["mp3", "wav", "m4a", "3gp", "flac", "ogg", "oga", "opus"];
export const CODE_TYPES = ["json", "css", "js"];
export const FRONTMATTER_KEYS:{[key:string]: {name: string, type: string, depricated?:boolean}} = {
"plugin": {name: "excalidraw-plugin", type: "text"},
"export-transparent": {name: "excalidraw-export-transparent", type: "checkbox"},
@@ -192,8 +232,42 @@ export const FRONTMATTER_KEYS:{[key:string]: {name: string, type: string, depric
"open-as-markdown": {name: "excalidraw-open-md", type: "checkbox"},
};
export const CaptureUpdateAction = {
/**
* Immediately undoable.
*
* Use for updates which should be captured.
* Should be used for most of the local updates.
*
* These updates will _immediately_ make it to the local undo / redo stacks.
*/
IMMEDIATELY: "IMMEDIATELY",
/**
* Never undoable.
*
* Use for updates which should never be recorded, such as remote updates
* or scene initialization.
*
* These updates will _never_ make it to the local undo / redo stacks.
*/
NEVER: "NEVER",
/**
* Eventually undoable.
*
* Use for updates which should not be captured immediately - likely
* exceptions which are part of some async multi-step process. Otherwise, all
* such updates would end up being captured with the next
* `CaptureUpdateAction.IMMEDIATELY` - triggered either by the next `updateScene`
* or internally by the editor.
*
* These updates will _eventually_ make it to the local undo / redo stacks.
*/
EVENTUALLY: "EVENTUALLY",
} as const;
export const EMBEDDABLE_THEME_FRONTMATTER_VALUES = ["light", "dark", "auto", "dafault"];
export const VIEW_TYPE_EXCALIDRAW = "excalidraw";
export const VIEW_TYPE_EXCALIDRAW_LOADING = "excalidraw-loading";
export const ICON_NAME = "excalidraw-icon";
export const MAX_COLORS = 5;
export const COLOR_FREQ = 6;
@@ -405,4 +479,4 @@ export const SCRIPTENGINE_ICON = `<g transform="translate(-8,-8)"><path d="M24.3
export const DISK_ICON_NAME = "save";
export const EXPORT_IMG_ICON = ` <g transform="scale(4.166)" strokeWidth="1.25" fill="none" stroke="currentColor"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M15 8h.01"></path><path d="M12 20h-5a3 3 0 0 1 -3 -3v-10a3 3 0 0 1 3 -3h10a3 3 0 0 1 3 3v5"></path><path d="M4 15l4 -4c.928 -.893 2.072 -.893 3 0l4 4"></path><path d="M14 14l1 -1c.617 -.593 1.328 -.793 2.009 -.598"></path><path d="M19 16v6"></path><path d="M22 19l-3 3l-3 -3"></path></g>`;
export const EXPORT_IMG_ICON_NAME = `export-img`;
export const EXCALIDRAW_ICON = `<path d="M24 17h121v121H24z" style="fill:none" transform="matrix(.8843 0 0 .83471 -21.223 -14.19)"/><path d="M119.81 105.98a.549.549 0 0 0-.53-.12c-4.19-6.19-9.52-12.06-14.68-17.73l-.85-.93c0-.11-.05-.21-.12-.3a.548.548 0 0 0-.34-.2l-.17-.18-.12-.09c-.15-.32-.53-.56-.95-.35-1.58.81-3 1.97-4.4 3.04-1.87 1.43-3.7 2.92-5.42 4.52-.7.65-1.39 1.33-1.97 2.09-.28.37-.07.72.27.87-1.22 1.2-2.45 2.45-3.68 3.74-.11.12-.17.28-.16.44.01.16.09.31.22.41l2.16 1.65s.01.03.03.04c3.09 3.05 8.51 7.28 14.25 11.76.85.67 1.71 1.34 2.57 2.01.39.47.76.94 1.12 1.4.19.25.55.3.8.11.13.1.26.21.39.31a.57.57 0 0 0 .8-.1c.07-.09.1-.2.11-.31.04 0 .07.03.1.03.15 0 .31-.06.42-.18l10.18-11.12a.56.56 0 0 0-.04-.8l.01-.01Zm-29.23-3.85c.07.09.14.17.21.25 1.16.98 2.4 2.04 3.66 3.12l-5.12-3.91s-.32-.22-.52-.36c-.11-.08-.21-.16-.31-.24l-.38-.32s.07-.07.1-.11l.35-.35c1.72-1.74 4.67-4.64 6.19-6.06-1.61 1.62-4.87 6.37-4.17 7.98h-.01Zm17.53 13.81-4.22-3.22c-1.65-1.71-3.43-3.4-5.24-5.03 2.28 1.76 4.23 3.25 4.52 3.51 2.21 1.97 2.11 1.61 3.63 2.91l1.83 1.33c-.18.16-.36.33-.53.49l.01.01Zm1.06.81-.08-.06c.16-.13.33-.25.49-.38l-.4.44h-.01Zm-66.93-65.3c.14.72.27 1.43.4 2.11.69 3.7 1.33 7.03 2.55 9.56l.48 1.92c.19.73.46 1.64.71 1.83 2.85 2.52 7.22 6.28 11.89 9.82.21.16.5.15.7-.01.01.02.03.03.04.04.11.1.24.15.38.15.16 0 .31-.06.42-.19 5.98-6.65 10.43-12.12 13.6-16.7.2-.25.3-.54.29-.84.2-.24.41-.48.6-.68a.558.558 0 0 0-.1-.86.578.578 0 0 0-.17-.36c-1.39-1.34-2.42-2.31-3.46-3.28-1.84-1.72-3.74-3.5-7.77-7.51-.02-.02-.05-.04-.07-.06a.555.555 0 0 0-.22-.14c-1.11-.39-3.39-.78-6.26-1.28-4.22-.72-10-1.72-15.2-3.27h-.04v-.01s-.02 0-.03.02h-.01l.04-.02s-.31.01-.37.04c-.08.04-.14.09-.19.15-.05.06-.09.12-.47.2-.38.08.08 0 .11 0h-.11v.03c.07.34.05.58.16.97-.02.1.21 1.02.24 1.11l1.83 7.26h.03Zm30.95 6.54s-.03.04-.04.05l-.64-.71c.22.21.44.42.68.66Zm-7.09 9.39s-.07.08-.1.12l-.02-.02c.04-.03.08-.07.13-.1h-.01Zm-7.07 8.47Zm3.02-28.57c.35.35 1.74 1.65 2.06 1.97-1.45-.66-5.06-2.34-6.74-2.88 1.65.29 3.93.66 4.68.91Zm-19.18-2.77c.84 1.44 1.5 6.49 2.16 11.4-.37-1.58-.69-3.12-.99-4.6-.52-2.56-1-4.85-1.67-6.88.14.01.31.03.49.05 0 .01 0 .02.02.03h-.01Zm-.29-1.21c-.23-.02-.44-.04-.62-.05-.02-.04-.03-.08-.04-.12l.66.18v-.01Zm-2.22.45v-.02.02Zm78.54-1.18c.04-.23-1.1-1.24-.74-1.26.85-.04.86-1.35 0-1.31-1.13.06-2.27.32-3.37.53-1.98.37-3.95.78-5.92 1.21-4.39.94-8.77 1.93-13.1 3.11-1.36.37-2.86.7-4.11 1.36-.42.22-.4.67-.17.95-.09.05-.18.08-.28.09-.37.07-.74.13-1.11.19a.566.566 0 0 0-.39.86c-2.32 3.1-4.96 6.44-7.82 9.95-2.81 3.21-5.73 6.63-8.72 10.14-9.41 11.06-20.08 23.6-31.9 34.64-.23.21-.24.57-.03.8.05.06.12.1.19.13-.16.15-.32.3-.48.44-.1.09-.14.2-.16.32-.08.08-.16.17-.23.25-.21.23-.2.59.03.8.23.21.59.2.8-.03.04-.04.08-.09.12-.13a.84.84 0 0 1 1.22 0c.69.74 1.34 1.44 1.95 2.09l-1.38-1.15a.57.57 0 0 0-.8.07c-.2.24-.17.6.07.8l14.82 12.43c.11.09.24.13.37.13.15 0 .29-.06.4-.17l.36-.36a.56.56 0 0 0 .63-.12c20.09-20.18 36.27-35.43 54.8-49.06.17-.12.25-.32.23-.51a.57.57 0 0 0 .48-.39c3.42-10.46 4.08-19.72 4.28-24.27 0-.03.01-.05.02-.07.02-.05.03-.1.04-.14.03-.11.05-.19.05-.19.26-.78.17-1.53-.15-2.15v.02ZM82.98 58.94c.9-1.03 1.79-2.04 2.67-3.02-5.76 7.58-15.3 19.26-28.81 33.14 9.2-10.18 18.47-20.73 26.14-30.12Zm-32.55 52.81-.03-.03c.11.02.19.04.2.04a.47.47 0 0 0-.17 0v-.01Zm6.9 6.42-.05-.04.03-.03c.02 0 .03.02.04.02 0 .02-.02.03-.03.05h.01Zm8.36-7.21 1.38-1.44c.01.01.02.03.03.05-.47.46-.94.93-1.42 1.39h.01Zm2.24-2.21c.26-.3.56-.65.87-1.02.01-.01.02-.03.04-.04 3.29-3.39 6.68-6.82 10.18-10.25.02-.02.05-.04.07-.06.86-.66 1.82-1.39 2.72-2.08-4.52 4.32-9.11 8.78-13.88 13.46v-.01Zm21.65-55.88c-1.86 2.42-3.9 5.56-5.63 8.07-5.46 7.91-23.04 27.28-23.43 27.65-2.71 2.62-10.88 10.46-16.09 15.37-.14.13-.25.24-.34.35a.794.794 0 0 1 .03-1.13c24.82-23.4 39.88-42.89 46-51.38-.13.33-.24.69-.55 1.09l.01-.02Zm16.51 7.1-.01.02c0-.02-.02-.07.01-.02Zm-.91-5.13Zm-5.89 9.45c-2.26-1.31-3.32-3.27-2.71-5.25l.19-.66c.08-.19.17-.38.28-.57.59-.98 1.49-1.85 2.52-2.36.05-.02.1-.03.15-.04a.795.795 0 0 1-.04-.43c.05-.31.25-.58.66-.58.67 0 2.75.62 3.54 1.3.24.19.47.4.68.63.3.35.74.92.96 1.33.13.06.23.62.38.91.14.46.2.93.18 1.4 0 .02 0 .02.01.03-.03.07 0 .37-.04.4-.1.72-.36 1.43-.75 2.05-.04.05-.07.11-.11.16 0 .01-.02.02-.03.04-.3.43-.65.83-1.08 1.13-1.26.89-2.73 1.16-4.2.79a6.33 6.33 0 0 1-.57-.25l-.02-.03Zm16.27-1.63c-.49 2.05-1.09 4.19-1.8 6.38-.03.08-.03.16-.03.23-.1.01-.19.05-.27.11-4.44 3.26-8.73 6.62-12.98 10.11 3.67-3.32 7.39-6.62 11.23-9.95a6.409 6.409 0 0 0 2.11-3.74l.56-3.37.03-.1c.25-.71 1.34-.4 1.17.33h-.02Z" style="fill:currentColor;fill-rule:nonzero" transform="translate(-26.41 -29.49)"/>`;
export const EXCALIDRAW_ICON = `<path d="M24 17h121v121H24z" style="fill:none" transform="matrix(.8843 0 0 .83471 -21.223 -14.19)"/><path d="M119.81 105.98a.549.549 0 0 0-.53-.12c-4.19-6.19-9.52-12.06-14.68-17.73l-.85-.93c0-.11-.05-.21-.12-.3a.548.548 0 0 0-.34-.2l-.17-.18-.12-.09c-.15-.32-.53-.56-.95-.35-1.58.81-3 1.97-4.4 3.04-1.87 1.43-3.7 2.92-5.42 4.52-.7.65-1.39 1.33-1.97 2.09-.28.37-.07.72.27.87-1.22 1.2-2.45 2.45-3.68 3.74-.11.12-.17.28-.16.44.01.16.09.31.22.41l2.16 1.65s.01.03.03.04c3.09 3.05 8.51 7.28 14.25 11.76.85.67 1.71 1.34 2.57 2.01.39.47.76.94 1.12 1.4.19.25.55.3.8.11.13.1.26.21.39.31a.57.57 0 0 0 .8-.1c.07-.09.1-.2.11-.31.04 0 .07.03.1.03.15 0 .31-.06.42-.18l10.18-11.12a.56.56 0 0 0-.04-.8l.01-.01Zm-29.23-3.85c.07.09.14.17.21.25 1.16.98 2.4 2.04 3.66 3.12l-5.12-3.91s-.32-.22-.52-.36c-.11-.08-.21-.16-.31-.24l-.38-.32s.07-.07.1-.11l.35-.35c1.72-1.74 4.67-4.64 6.19-6.06-1.61 1.62-4.87 6.37-4.17 7.98h-.01Zm17.53 13.81-4.22-3.22c-1.65-1.71-3.43-3.4-5.24-5.03 2.28 1.76 4.23 3.25 4.52 3.51 2.21 1.97 2.11 1.61 3.63 2.91l1.83 1.33c-.18.16-.36.33-.53.49l.01.01Zm1.06.81-.08-.06c.16-.13.33-.25.49-.38l-.4.44h-.01Zm-66.93-65.3c.14.72.27 1.43.4 2.11.69 3.7 1.33 7.03 2.55 9.56l.48 1.92c.19.73.46 1.64.71 1.83 2.85 2.52 7.22 6.28 11.89 9.82.21.16.5.15.7-.01.01.02.03.03.04.04.11.1.24.15.38.15.16 0 .31-.06.42-.19 5.98-6.65 10.43-12.12 13.6-16.7.2-.25.3-.54.29-.84.2-.24.41-.48.6-.68a.558.558 0 0 0-.1-.86.578.578 0 0 0-.17-.36c-1.39-1.34-2.42-2.31-3.46-3.28-1.84-1.72-3.74-3.5-7.77-7.51-.02-.02-.05-.04-.07-.06a.555.555 0 0 0-.22-.14c-1.11-.39-3.39-.78-6.26-1.28-4.22-.72-10-1.72-15.2-3.27h-.04v-.01s-.02 0-.03.02h-.01l.04-.02s-.31.01-.37.04c-.08.04-.14.09-.19.15-.05.06-.09.12-.47.2-.38.08.08 0 .11 0h-.11v.03c.07.34.05.58.16.97-.02.1.21 1.02.24 1.11l1.83 7.26h.03Zm30.95 6.54s-.03.04-.04.05l-.64-.71c.22.21.44.42.68.66Zm-7.09 9.39s-.07.08-.1.12l-.02-.02c.04-.03.08-.07.13-.1h-.01Zm-7.07 8.47Zm3.02-28.57c.35.35 1.74 1.65 2.06 1.97-1.45-.66-5.06-2.34-6.74-2.88 1.65.29 3.93.66 4.68.91Zm-19.18-2.77c.84 1.44 1.5 6.49 2.16 11.4-.37-1.58-.69-3.12-.99-4.6-.52-2.56-1-4.85-1.67-6.88.14.01.31.03.49.05 0 .01 0 .02.02.03h-.01Zm-.29-1.21c-.23-.02-.44-.04-.62-.05-.02-.04-.03-.08-.04-.12l.66.18v-.01Zm-2.22.45v-.02.02Zm78.54-1.18c.04-.23-1.1-1.24-.74-1.26.85-.04.86-1.35 0-1.31-1.13.06-2.27.32-3.37.53-1.98.37-3.95.78-5.92 1.21-4.39.94-8.77 1.93-13.1 3.11-1.36.37-2.86.7-4.11 1.36-.42.22-.4.67-.17.95-.09.05-.18.08-.28.09-.37.07-.74.13-1.11.19a.566.566 0 0 0-.39.86c-2.32 3.1-4.96 6.44-7.82 9.95-2.81 3.21-5.73 6.63-8.72 10.14-9.41 11.06-20.08 23.6-31.9 34.64-.23.21-.24.57-.03.8.05.06.12.1.19.13-.16.15-.32.3-.48.44-.1.09-.14.2-.16.32-.08.08-.16.17-.23.25-.21.23-.2.59.03.8.23.21.59.2.8-.03.04-.04.08-.09.12-.13a.84.84 0 0 1 1.22 0c.69.74 1.34 1.44 1.95 2.09l-1.38-1.15a.57.57 0 0 0-.8.07c-.2.24-.17.6.07.8l14.82 12.43c.11.09.24.13.37.13.15 0 .29-.06.4-.17l.36-.36a.56.56 0 0 0 .63-.12c20.09-20.18 36.27-35.43 54.8-49.06.17-.12.25-.32.23-.51a.57.57 0 0 0 .48-.39c3.42-10.46 4.08-19.72 4.28-24.27 0-.03.01-.05.02-.07.02-.05.03-.1.04-.14.03-.11.05-.19.05-.19.26-.78.17-1.53-.15-2.15v.02ZM82.98 58.94c.9-1.03 1.79-2.04 2.67-3.02-5.76 7.58-15.3 19.26-28.81 33.14 9.2-10.18 18.47-20.73 26.14-30.12Zm-32.55 52.81-.03-.03c.11.02.19.04.2.04a.47.47 0 0 0-.17 0v-.01Zm6.9 6.42-.05-.04.03-.03c.02 0 .03.02.04.02 0 .02-.02.03-.03.05h.01Zm8.36-7.21 1.38-1.44c.01.01.02.03.03.05-.47.46-.94.93-1.42 1.39h.01Zm2.24-2.21c.26-.3.56-.65.87-1.02.01-.01.02-.03.04-.04 3.29-3.39 6.68-6.82 10.18-10.25.02-.02.05-.04.07-.06.86-.66 1.82-1.39 2.72-2.08-4.52 4.32-9.11 8.78-13.88 13.46v-.01Zm21.65-55.88c-1.86 2.42-3.9 5.56-5.63 8.07-5.46 7.91-23.04 27.28-23.43 27.65-2.71 2.62-10.88 10.46-16.09 15.37-.14.13-.25.24-.34.35a.794.794 0 0 1 .03-1.13c24.82-23.4 39.88-42.89 46-51.38-.13.33-.24.69-.55 1.09l.01-.02Zm16.51 7.1-.01.02c0-.02-.02-.07.01-.02Zm-.91-5.13Zm-5.89 9.45c-2.26-1.31-3.32-3.27-2.71-5.25l.19-.66c.08-.19.17-.38.28-.57.59-.98 1.49-1.85 2.52-2.36.05-.02.1-.03.15-.04a.795.795 0 0 1-.04-.43c.05-.31.25-.58.66-.58.67 0 2.75.62 3.54 1.3.24.19.47.4.68.63.3.35.74.92.96 1.33.13.06.23.62.38.91.14.46.2.93.18 1.4 0 .02 0 .02.01.03-.03.07 0 .37-.04.4-.1.72-.36 1.43-.75 2.05-.04.05-.07.11-.11.16 0 .01-.02.02-.03.04-.3.43-.65.83-1.08 1.13-1.26.89-2.73 1.16-4.2.79a6.33 6.33 0 0 1-.57-.25l-.02-.03Zm16.27-1.63c-.49 2.05-1.09 4.19-1.8 6.38-.03.08-.03.16-.03.23-.1.01-.19.05-.27.11-4.44 3.26-8.73 6.62-12.98 10.11 3.67-3.32 7.39-6.62 11.23-9.95a6.409 6.409 0 0 0 2.11-3.74l.56-3.37.03-.1c.25-.71 1.34-.4 1.17.33h-.02Z" style="fill:currentColor;fill-rule:nonzero" transform="translate(-26.41 -29.49)"/>`;

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,7 @@
import { Extension } from "@codemirror/state";
import ExcalidrawPlugin from "src/main";
import ExcalidrawPlugin from "src/core/main";
import { HideTextBetweenCommentsExtension } from "./Fadeout";
import { debug, DEBUGGING } from "src/utils/debugHelper";
export const EDITOR_FADEOUT = "fadeOutExcalidrawMarkup";
const editorExtensions: {[key:string]:Extension}= {
@@ -10,13 +11,16 @@ const editorExtensions: {[key:string]:Extension}= {
export class EditorHandler {
private activeEditorExtensions: Extension[] = [];
constructor(private plugin: ExcalidrawPlugin) {}
constructor(private plugin: ExcalidrawPlugin) {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(EditorHandler, `ExcalidrawPlugin.construct EditorHandler`);
}
destroy(): void {
this.plugin = null;
}
setup(): void {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.setup, `ExcalidrawPlugin.construct EditorHandler.setup`);
this.plugin.registerEditorExtension(this.activeEditorExtensions);
this.updateCMExtensionState(EDITOR_FADEOUT, this.plugin.settings.fadeOutExcalidrawMarkup);
}

View File

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

1450
src/core/main.ts Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,353 @@
import { WorkspaceLeaf, TFile, Editor, MarkdownView, MarkdownFileInfo, MetadataCache, App, EventRef, Menu, FileView } from "obsidian";
import { ExcalidrawElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
import { getLink } from "../../utils/fileUtils";
import { editorInsertText, getExcalidrawViews, getParentOfClass, isUnwantedLeaf, setExcalidrawView } from "../../utils/obsidianUtils";
import ExcalidrawPlugin from "src/core/main";
import { DEBUGGING, debug } from "src/utils/debugHelper";
import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
import { DEVICE, FRONTMATTER_KEYS, ICON_NAME, VIEW_TYPE_EXCALIDRAW } from "src/constants/constants";
import ExcalidrawView from "src/view/ExcalidrawView";
import { t } from "src/lang/helpers";
/**
* Registers event listeners for the plugin
* Must be constructed after the workspace is ready (onLayoutReady)
* Intended to be called from onLayoutReady in onload()
*/
export class EventManager {
private plugin: ExcalidrawPlugin;
private app: App;
public leafChangeTimeout: number|null = null;
private removeEventLisnters:(()=>void)[] = []; //only used if I register an event directly, not via Obsidian's registerEvent
private previouslyActiveLeaf: WorkspaceLeaf;
private splitViewLeafSwitchTimestamp: number = 0;
get settings() {
return this.plugin.settings;
}
get ea():ExcalidrawAutomate {
return this.plugin.ea;
}
get activeExcalidrawView() {
return this.plugin.activeExcalidrawView;
}
set activeExcalidrawView(view: ExcalidrawView) {
this.plugin.activeExcalidrawView = view;
}
private registerEvent(eventRef: EventRef): void {
this.plugin.registerEvent(eventRef);
}
constructor(plugin: ExcalidrawPlugin) {
this.plugin = plugin;
this.app = plugin.app;
}
destroy() {
if(this.leafChangeTimeout) {
window.clearTimeout(this.leafChangeTimeout);
this.leafChangeTimeout = null;
}
this.removeEventLisnters.forEach((removeEventListener) =>
removeEventListener(),
);
this.removeEventLisnters = [];
}
public async initialize() {
try {
await this.registerEvents();
} catch (e) {
console.error("Error registering event listeners", e);
}
this.plugin.logStartupEvent("Event listeners registered");
}
public isRecentSplitViewSwitch():boolean {
return (Date.now() - this.splitViewLeafSwitchTimestamp) < 3000;
}
public async registerEvents() {
await this.plugin.awaitInit();
this.registerEvent(this.app.workspace.on("editor-paste", this.onPasteHandler.bind(this)));
this.registerEvent(this.app.vault.on("rename", this.onRenameHandler.bind(this)));
this.registerEvent(this.app.vault.on("modify", this.onModifyHandler.bind(this)));
this.registerEvent(this.app.vault.on("delete", this.onDeleteHandler.bind(this)));
//save Excalidraw leaf and update embeds when switching to another leaf
this.registerEvent(this.plugin.app.workspace.on("active-leaf-change", this.onActiveLeafChangeHandler.bind(this)));
this.registerEvent(this.app.workspace.on("layout-change", this.onLayoutChangeHandler.bind(this)));
//File Save Trigger Handlers
//Save the drawing if the user clicks outside the Excalidraw Canvas
const onClickEventSaveActiveDrawing = this.onClickSaveActiveDrawing.bind(this);
this.app.workspace.containerEl.addEventListener("click", onClickEventSaveActiveDrawing);
this.removeEventLisnters.push(() => {
this.app.workspace.containerEl.removeEventListener("click", onClickEventSaveActiveDrawing)
});
this.registerEvent(this.app.workspace.on("file-menu", this.onFileMenuSaveActiveDrawing.bind(this)));
const metaCache: MetadataCache = this.app.metadataCache;
this.registerEvent(
metaCache.on("changed", (file, _, cache) =>
this.plugin.updateFileCache(file, cache?.frontmatter),
),
);
this.registerEvent(this.app.workspace.on("file-menu", this.onFileMenuHandler.bind(this)));
this.plugin.registerEvent(this.plugin.app.workspace.on("editor-menu", this.onEditorMenuHandler.bind(this)));
}
private onLayoutChangeHandler() {
getExcalidrawViews(this.app).forEach(excalidrawView=>excalidrawView.refresh());
}
private onPasteHandler (evt: ClipboardEvent, editor: Editor, info: MarkdownView | MarkdownFileInfo ) {
if(evt.defaultPrevented) return
const data = evt.clipboardData.getData("text/plain");
if (!data) return;
if (data.startsWith(`{"type":"excalidraw/clipboard"`)) {
evt.preventDefault();
try {
const drawing = JSON.parse(data);
const hasOneTextElement = drawing.elements.filter((el:ExcalidrawElement)=>el.type==="text").length === 1;
if (!(hasOneTextElement || drawing.elements?.length === 1)) {
return;
}
const element = hasOneTextElement
? drawing.elements.filter((el:ExcalidrawElement)=>el.type==="text")[0]
: drawing.elements[0];
if (element.type === "image") {
const fileinfo = this.plugin.filesMaster.get(element.fileId);
if(fileinfo && fileinfo.path) {
let path = fileinfo.path;
const sourceFile = info.file;
const imageFile = this.app.vault.getAbstractFileByPath(path);
if(sourceFile && imageFile && imageFile instanceof TFile) {
path = this.app.metadataCache.fileToLinktext(imageFile,sourceFile.path);
}
editorInsertText(editor, getLink(this.plugin, {path}));
}
return;
}
if (element.type === "text") {
editorInsertText(editor, element.rawText);
return;
}
if (element.link) {
editorInsertText(editor, `${element.link}`);
return;
}
} catch (e) {
}
}
};
private onRenameHandler(file: TFile, oldPath: string) {
this.plugin.renameEventHandler(file, oldPath);
}
private onModifyHandler(file: TFile) {
this.plugin.modifyEventHandler(file);
}
private onDeleteHandler(file: TFile) {
this.plugin.deleteEventHandler(file);
}
public async onActiveLeafChangeHandler (leaf: WorkspaceLeaf) {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onActiveLeafChangeHandler,`onActiveLeafChangeEventHandler`, leaf);
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/723
//In Obsidian 1.8.x the active excalidraw leaf is obscured by an empty leaf without a parent
//This hack resolves it
if(this.app.workspace.activeLeaf === leaf && isUnwantedLeaf(leaf)) {
leaf.detach();
return;
}
if (leaf.view && leaf.view.getViewType() === "pdf") {
this.plugin.lastPDFLeafID = leaf.id;
}
if(this.leafChangeTimeout) {
window.clearTimeout(this.leafChangeTimeout);
}
this.leafChangeTimeout = window.setTimeout(()=>{this.leafChangeTimeout = null;},1000);
if(this.settings.overrideObsidianFontSize) {
if(leaf.view && (leaf.view.getViewType() === VIEW_TYPE_EXCALIDRAW)) {
document.documentElement.style.fontSize = "";
}
}
const previouslyActiveEV = this.activeExcalidrawView;
const newActiveviewEV: ExcalidrawView =
leaf.view instanceof ExcalidrawView ? leaf.view : null;
this.activeExcalidrawView = newActiveviewEV;
const previousFile = (this.previouslyActiveLeaf?.view as FileView)?.file;
const currentFile = (leaf?.view as FileView).file;
//editing the same file in a different leaf
if(currentFile && (previousFile === currentFile)) {
if((this.previouslyActiveLeaf.view instanceof MarkdownView && leaf.view instanceof ExcalidrawView)) {
this.splitViewLeafSwitchTimestamp = Date.now();
}
}
this.previouslyActiveLeaf = leaf;
if (newActiveviewEV) {
this.plugin.addModalContainerObserver();
this.plugin.lastActiveExcalidrawFilePath = newActiveviewEV.file?.path;
} else {
this.plugin.removeModalContainerObserver();
}
//!Temporary hack
//https://discord.com/channels/686053708261228577/817515900349448202/1031101635784613968
if (DEVICE.isMobile && newActiveviewEV && !previouslyActiveEV) {
const navbar = document.querySelector("body>.app-container>.mobile-navbar");
if(navbar && navbar instanceof HTMLDivElement) {
navbar.style.position="relative";
}
}
if (DEVICE.isMobile && !newActiveviewEV && previouslyActiveEV) {
const navbar = document.querySelector("body>.app-container>.mobile-navbar");
if(navbar && navbar instanceof HTMLDivElement) {
navbar.style.position="";
}
}
//----------------------
//----------------------
if (previouslyActiveEV && previouslyActiveEV !== newActiveviewEV) {
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?.isDirty() && !previouslyActiveEV.semaphores?.viewunload) {
await previouslyActiveEV.save(true); //this will update transclusions in the drawing
}
}
if (previouslyActiveEV.file) {
this.plugin.triggerEmbedUpdates(previouslyActiveEV.file.path);
}
}
if (
newActiveviewEV &&
(!previouslyActiveEV || previouslyActiveEV.leaf !== leaf)
) {
//the user switched to a new leaf
//timeout gives time to the view being exited to finish saving
const f = newActiveviewEV.file;
if (newActiveviewEV.file) {
setTimeout(() => {
if (!newActiveviewEV || !newActiveviewEV._loaded) {
return;
}
if (newActiveviewEV.file?.path !== f?.path) {
return;
}
if (newActiveviewEV.activeLoader) {
return;
}
newActiveviewEV.loadSceneFiles();
}, 2000);
} //refresh embedded files
}
if (
newActiveviewEV && newActiveviewEV._loaded &&
newActiveviewEV.isLoaded && newActiveviewEV.excalidrawAPI &&
this.ea.onCanvasColorChangeHook
) {
this.ea.onCanvasColorChangeHook(
this.ea,
newActiveviewEV,
newActiveviewEV.excalidrawAPI.getAppState().viewBackgroundColor
);
}
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/300
if (this.plugin.popScope) {
this.plugin.popScope();
this.plugin.popScope = null;
}
if (newActiveviewEV) {
this.plugin.registerHotkeyOverrides();
}
}
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/551
private onClickSaveActiveDrawing(e: PointerEvent) {
if (
!this.activeExcalidrawView ||
!this.activeExcalidrawView?.isDirty() ||
e.target && ((e.target as Element).className === "excalidraw__canvas" ||
getParentOfClass((e.target as Element),"excalidraw-wrapper"))
) {
return;
}
this.activeExcalidrawView.save();
}
private onFileMenuSaveActiveDrawing () {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onFileMenuSaveActiveDrawing,`onFileMenuSaveActiveDrawing`);
if (
!this.activeExcalidrawView ||
!this.activeExcalidrawView?.isDirty()
) {
return;
}
this.activeExcalidrawView.save();
};
private onFileMenuHandler(menu: Menu, file: TFile, source: string, leaf: WorkspaceLeaf) {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onFileMenuHandler, `EventManager.onFileMenuHandler`, file, source, leaf);
if (!leaf) return;
const view = leaf.view;
if(!view || !(view instanceof MarkdownView)) return;
if (!(file instanceof TFile)) return;
const cache = this.app.metadataCache.getFileCache(file);
if (!cache?.frontmatter || !cache.frontmatter[FRONTMATTER_KEYS["plugin"].name]) return;
menu.addItem(item => {
item
.setTitle(t("OPEN_AS_EXCALIDRAW"))
.setIcon(ICON_NAME)
.setSection("pane")
.onClick(async () => {
await view.save();
this.plugin.excalidrawFileModes[leaf.id || file.path] = VIEW_TYPE_EXCALIDRAW;
setExcalidrawView(leaf);
})});
menu.items.unshift(menu.items.pop());
}
private onEditorMenuHandler(menu: Menu, editor: Editor, view: MarkdownView) {
if(!view || !(view instanceof MarkdownView)) return;
const file = view.file;
const leaf = view.leaf;
if (!view.file) return;
const cache = this.app.metadataCache.getFileCache(file);
if (!cache?.frontmatter || !cache.frontmatter[FRONTMATTER_KEYS["plugin"].name]) return;
menu.addItem(item => item
.setTitle(t("OPEN_AS_EXCALIDRAW"))
.setIcon(ICON_NAME)
.setSection("excalidraw")
.onClick(async () => {
await view.save();
this.plugin.excalidrawFileModes[leaf.id || file.path] = VIEW_TYPE_EXCALIDRAW;
setExcalidrawView(leaf);
})
);
}
}

View File

@@ -0,0 +1,500 @@
import { debug } from "src/utils/debugHelper";
import { App, FrontMatterCache, MarkdownView, MetadataCache, normalizePath, Notice, TAbstractFile, TFile, WorkspaceLeaf } from "obsidian";
import { BLANK_DRAWING, DARK_BLANK_DRAWING, DEVICE, EXPORT_TYPES, FRONTMATTER, FRONTMATTER_KEYS, JSON_parse, nanoid, VIEW_TYPE_EXCALIDRAW } from "src/constants/constants";
import { Prompt, templatePromt } from "src/shared/Dialogs/Prompt";
import { changeThemeOfExcalidrawMD, ExcalidrawData, getMarkdownDrawingSection } from "../../shared/ExcalidrawData";
import ExcalidrawView, { getTextMode } from "src/view/ExcalidrawView";
import ExcalidrawPlugin from "src/core/main";
import { DEBUGGING } from "src/utils/debugHelper";
import { checkAndCreateFolder, download, getIMGFilename, getLink, getListOfTemplateFiles, getNewUniqueFilepath } from "src/utils/fileUtils";
import { PaneTarget } from "src/utils/modifierkeyHelper";
import { getExcalidrawViews, getNewOrAdjacentLeaf, isObsidianThemeDark, openLeaf } from "src/utils/obsidianUtils";
import { errorlog, getExportTheme } from "src/utils/utils";
export class PluginFileManager {
private plugin: ExcalidrawPlugin;
private app: App;
private excalidrawFiles: Set<TFile> = new Set<TFile>();
get settings() {
return this.plugin.settings;
}
constructor(plugin: ExcalidrawPlugin) {
this.plugin = plugin;
this.app = plugin.app;
}
public async initialize() {
await this.plugin.awaitInit();
const metaCache: MetadataCache = this.app.metadataCache;
metaCache.getCachedFiles().forEach((filename: string) => {
const fm = metaCache.getCache(filename)?.frontmatter;
if (
(fm && typeof fm[FRONTMATTER_KEYS["plugin"].name] !== "undefined") ||
filename.match(/\.excalidraw$/)
) {
this.updateFileCache(
this.app.vault.getAbstractFileByPath(filename) as TFile,
fm,
);
}
});
}
public isExcalidrawFile(f: TFile): boolean {
if(!f) return false;
if (f.extension === "excalidraw") {
return true;
}
const fileCache = f ? this.plugin.app.metadataCache.getFileCache(f) : null;
return !!fileCache?.frontmatter && !!fileCache.frontmatter[FRONTMATTER_KEYS["plugin"].name];
}
//managing my own list of Excalidraw files because in the onDelete event handler
//the file object is already gone from metadataCache, thus I can't check if it was an Excalidraw file
public updateFileCache(
file: TFile,
frontmatter?: FrontMatterCache,
deleted: boolean = false,
) {
if (frontmatter && typeof frontmatter[FRONTMATTER_KEYS["plugin"].name] !== "undefined") {
this.excalidrawFiles.add(file);
return;
}
if (!deleted && file.extension === "excalidraw") {
this.excalidrawFiles.add(file);
return;
}
this.excalidrawFiles.delete(file);
}
public getExcalidrawFiles(): Set<TFile> {
return this.excalidrawFiles;
}
public destroy() {
this.excalidrawFiles.clear();
}
public async createDrawing(
filename: string,
foldername?: string,
initData?: string,
): Promise<TFile> {
const folderpath = normalizePath(
foldername ? foldername : this.settings.folder,
);
await checkAndCreateFolder(folderpath); //create folder if it does not exist
const fname = getNewUniqueFilepath(this.app.vault, filename, folderpath);
const file = await this.app.vault.create(
fname,
initData ?? (await this.plugin.getBlankDrawing()),
);
//wait for metadata cache
let counter = 0;
while(file instanceof TFile && !this.isExcalidrawFile(file) && counter++<10) {
await sleep(50);
}
if(counter > 10) {
errorlog({file, error: "new drawing not recognized as an excalidraw file", fn: this.createDrawing});
}
return file;
}
public async getBlankDrawing(): Promise<string> {
const templates = getListOfTemplateFiles(this.plugin);
if(templates) {
const template = await templatePromt(templates, this.app);
if (template && template instanceof TFile) {
if (
(template.extension == "md" && !this.settings.compatibilityMode) ||
(template.extension == "excalidraw" && this.settings.compatibilityMode)
) {
const data = await this.app.vault.read(template);
if (data) {
return this.settings.matchTheme
? changeThemeOfExcalidrawMD(data)
: data;
}
}
}
}
if (this.settings.compatibilityMode) {
return this.settings.matchTheme && isObsidianThemeDark()
? DARK_BLANK_DRAWING
: BLANK_DRAWING;
}
const blank =
this.settings.matchTheme && isObsidianThemeDark()
? DARK_BLANK_DRAWING
: BLANK_DRAWING;
return `${FRONTMATTER}\n${getMarkdownDrawingSection(
blank,
this.settings.compress,
)}`;
}
public async embedDrawing(file: TFile) {
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
if (activeView && activeView.file) {
const excalidrawRelativePath = this.app.metadataCache.fileToLinktext(
file,
activeView.file.path,
this.settings.embedType === "excalidraw",
);
const editor = activeView.editor;
//embed Excalidraw
if (this.settings.embedType === "excalidraw") {
editor.replaceSelection(
getLink(this.plugin, {path: excalidrawRelativePath}),
);
editor.focus();
return;
}
//embed image
let theme = this.settings.autoExportLightAndDark
? getExportTheme (
this.plugin,
file,
this.settings.exportWithTheme
? isObsidianThemeDark() ? "dark":"light"
: "light"
)
: "";
theme = (theme === "")
? ""
: theme + ".";
const imageRelativePath = getIMGFilename(
excalidrawRelativePath,
theme+this.settings.embedType.toLowerCase(),
);
const imageFullpath = getIMGFilename(
file.path,
theme+this.settings.embedType.toLowerCase(),
);
//will hold incorrect value if theme==="", however in that case it won't be used
const otherTheme = theme === "dark." ? "light." : "dark.";
const otherImageRelativePath = theme === ""
? null
: getIMGFilename(
excalidrawRelativePath,
otherTheme+this.settings.embedType.toLowerCase(),
);
const imgFile = this.app.vault.getAbstractFileByPath(imageFullpath);
if (!imgFile) {
await this.app.vault.create(imageFullpath, "");
await sleep(200); //wait for metadata cache to update
}
const inclCom = this.settings.embedMarkdownCommentLinks;
editor.replaceSelection(
this.settings.embedWikiLink
? `![[${imageRelativePath}]]\n` +
(inclCom
? `%%[[${excalidrawRelativePath}|🖋 Edit in Excalidraw]]${
otherImageRelativePath
? ", and the [["+otherImageRelativePath+"|"+otherTheme.split(".")[0]+" exported image]]"
: ""
}%%`
: "")
: `![](${encodeURI(imageRelativePath)})\n` +
(inclCom ? `%%[🖋 Edit in Excalidraw](${encodeURI(excalidrawRelativePath,
)})${otherImageRelativePath?", and the ["+otherTheme.split(".")[0]+" exported image]("+encodeURI(otherImageRelativePath)+")":""}%%` : ""),
);
editor.focus();
}
}
public async exportLibrary() {
if (DEVICE.isMobile) {
const prompt = new Prompt(
this.app,
"Please provide a filename",
"my-library",
"filename, leave blank to cancel action",
);
prompt.openAndGetValue(async (filename: string) => {
if (!filename) {
return;
}
filename = `${filename}.excalidrawlib`;
const folderpath = normalizePath(this.settings.folder);
await checkAndCreateFolder(folderpath); //create folder if it does not exist
const fname = getNewUniqueFilepath(
this.app.vault,
filename,
folderpath,
);
this.app.vault.create(fname, this.settings.library);
new Notice(`Exported library to ${fname}`, 6000);
});
return;
}
download(
"data:text/plain;charset=utf-8",
encodeURIComponent(JSON.stringify(this.settings.library2, null, "\t")),
"my-obsidian-library.excalidrawlib",
);
}
/**
* Opens a drawing file
* @param drawingFile
* @param location
* @param active
* @param subpath
* @param justCreated
* @param popoutLocation
*/
public openDrawing(
drawingFile: TFile,
location: PaneTarget,
active: boolean = false,
subpath?: string,
justCreated: boolean = false,
popoutLocation?: {x?: number, y?: number, width?: number, height?: number},
) {
const fnGetLeaf = ():WorkspaceLeaf => {
if(location === "md-properties") {
location = "new-tab";
}
let leaf: WorkspaceLeaf;
if(location === "popout-window") {
leaf = this.app.workspace.openPopoutLeaf(popoutLocation);
}
if(location === "new-tab") {
leaf = this.app.workspace.getLeaf('tab');
}
if(!leaf) {
leaf = this.app.workspace.getLeaf(false);
if ((leaf.view.getViewType() !== 'empty') && (location === "new-pane")) {
leaf = getNewOrAdjacentLeaf(this.plugin, leaf)
}
}
return leaf;
}
const {leaf, promise} = openLeaf({
plugin: this.plugin,
fnGetLeaf: () => fnGetLeaf(),
file: drawingFile,
openState:!subpath || subpath === ""
? {active}
: { active, eState: { subpath } }
});
promise.then(()=>{
const ea = this.plugin.ea;
if(justCreated && ea.onFileCreateHook) {
try {
ea.onFileCreateHook({
ea,
excalidrawFile: drawingFile,
view: leaf.view as ExcalidrawView,
});
} catch(e) {
console.error(e);
}
}
})
}
/**
* Extracts the text elements from an Excalidraw scene into a string of ids as headers followed by the text contents
* @param {string} data - Excalidraw scene JSON string
* @returns {string} - Text starting with the "# Text Elements" header and followed by each "## id-value" and text
*/
public async exportSceneToMD(data: string, compressOverride?: boolean): Promise<string> {
if (!data) {
return "";
}
const excalidrawData = JSON_parse(data);
const textElements = excalidrawData.elements?.filter(
(el: any) => el.type == "text",
);
let outString = `# Excalidraw Data\n\n## Text Elements\n`;
let id: string;
for (const te of textElements) {
id = te.id;
//replacing Excalidraw text IDs with my own, because default IDs may contain
//characters not recognized by Obsidian block references
//also Excalidraw IDs are inconveniently long
if (te.id.length > 8) {
id = nanoid();
data = data.replaceAll(te.id, id); //brute force approach to replace all occurrences.
}
outString += `${te.originalText ?? te.text} ^${id}\n\n`;
}
return (
outString +
getMarkdownDrawingSection(
JSON.stringify(JSON_parse(data), null, "\t"),
typeof compressOverride === "undefined"
? this.settings.compress
: compressOverride,
)
);
}
// -------------------------------------------------------
// ------------------ Event Handlers ---------------------
// -------------------------------------------------------
/**
* watch filename change to rename .svg, .png; to sync to .md; to update links
* @param file
* @param oldPath
* @returns
*/
public async renameEventHandler (file: TAbstractFile, oldPath: string) {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.renameEventHandler, `ExcalidrawPlugin.renameEventHandler`, file, oldPath);
if (!(file instanceof TFile)) {
return;
}
if (!this.isExcalidrawFile(file)) {
return;
}
if (!this.settings.keepInSync) {
return;
}
[EXPORT_TYPES, "excalidraw"].flat().forEach(async (ext: string) => {
const oldIMGpath = getIMGFilename(oldPath, ext);
const imgFile = this.app.vault.getAbstractFileByPath(
normalizePath(oldIMGpath),
);
if (imgFile && imgFile instanceof TFile) {
const newIMGpath = getIMGFilename(file.path, ext);
await this.app.fileManager.renameFile(imgFile, newIMGpath);
}
});
}
public async modifyEventHandler (file: TFile) {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.modifyEventHandler,`FileManager.modifyEventHandler`, file);
const excalidrawViews = getExcalidrawViews(this.app);
excalidrawViews.forEach(async (excalidrawView) => {
if(excalidrawView.semaphores?.viewunload) {
return;
}
if (
excalidrawView.file &&
(excalidrawView.file.path === file.path ||
(file.extension === "excalidraw" &&
`${file.path.substring(
0,
file.path.lastIndexOf(".excalidraw"),
)}.md` === excalidrawView.file.path))
) {
if(excalidrawView.semaphores?.preventReload) {
excalidrawView.semaphores.preventReload = false;
return;
}
// Avoid synchronizing or reloading if the user hasn't interacted with the file for 5 minutes.
// This prevents complex sync issues when multiple remote changes occur outside an active collaboration session.
// The following logic handles a rare edge case where:
// 1. The user opens an Excalidraw file.
// 2. Immediately splits the view without saving Excalidraw (since no changes were made).
// 3. Switches the new split view to Markdown, edits the file, and quickly returns to Excalidraw.
// 4. The "modify" event may fire while Excalidraw is active, triggering an unwanted reload and zoom reset.
// To address this:
// - We check if the user is currently editing the Markdown version of the Excalidraw file in a split view.
// - As a heuristic, we also check for recent leaf switches.
// This is not perfectly accurate (e.g., rapid switching between views within a few seconds),
// but it is sufficient to avoid most edge cases without introducing complexity.
// Edge case impact:
// - In extremely rare situations, an update arriving within the "recent switch" timeframe (e.g., from Obsidian Sync)
// might not trigger a reload. This is unlikely and an acceptable trade-off for better user experience.
const activeView = this.app.workspace.activeLeaf.view;
const isEditingMarkdownSideInSplitView = ((activeView !== excalidrawView) &&
activeView instanceof MarkdownView && activeView.file === excalidrawView.file) ||
(activeView === excalidrawView && this.plugin.isRecentSplitViewSwitch());
if(!isEditingMarkdownSideInSplitView && (excalidrawView.lastSaveTimestamp + 300000 < Date.now())) {
excalidrawView.reload(true, excalidrawView.file);
return;
}
if(file.extension==="md") {
if(excalidrawView.semaphores?.embeddableIsEditingSelf) return;
const inData = new ExcalidrawData(this.plugin);
const data = await this.app.vault.read(file);
await inData.loadData(data,file,getTextMode(data));
excalidrawView.synchronizeWithData(inData);
inData.destroy();
if(excalidrawView?.isDirty()) {
if(excalidrawView.autosaveTimer && excalidrawView.autosaveFunction) {
clearTimeout(excalidrawView.autosaveTimer);
}
if(excalidrawView.autosaveFunction) {
excalidrawView.autosaveFunction();
}
}
} else {
excalidrawView.reload(true, excalidrawView.file);
}
}
});
}
/**
* watch file delete and delete corresponding .svg and .png
* @param file
* @returns
*/
public async deleteEventHandler (file: TFile) {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.deleteEventHandler,`ExcalidrawPlugin.deleteEventHandler`, file);
if (!(file instanceof TFile)) {
return;
}
const isExcalidarwFile = this.getExcalidrawFiles().has(file);
this.updateFileCache(file, undefined, true);
if (!isExcalidarwFile) {
return;
}
//close excalidraw view where this file is open
const excalidrawViews = getExcalidrawViews(this.app);
for (const excalidrawView of excalidrawViews) {
if (excalidrawView.file.path === file.path) {
await excalidrawView.leaf.setViewState({
type: VIEW_TYPE_EXCALIDRAW,
state: { file: null },
});
}
}
//delete PNG and SVG files as well
if (this.settings.keepInSync) {
window.setTimeout(() => {
[EXPORT_TYPES, "excalidraw"].flat().forEach(async (ext: string) => {
const imgPath = getIMGFilename(file.path, ext);
const imgFile = this.app.vault.getAbstractFileByPath(
normalizePath(imgPath),
);
if (imgFile && imgFile instanceof TFile) {
await this.app.vault.delete(imgFile);
}
});
}, 500);
}
};
}

View File

@@ -0,0 +1,257 @@
import { debug, DEBUGGING } from "src/utils/debugHelper";
import ExcalidrawPlugin from "src/core/main";
import { CustomMutationObserver } from "src/utils/debugHelper";
import { getExcalidrawViews, isObsidianThemeDark } from "src/utils/obsidianUtils";
import { App, Notice, TFile } from "obsidian";
export class ObserverManager {
private plugin: ExcalidrawPlugin;
private app: App;
private themeObserver: MutationObserver | CustomMutationObserver;
private fileExplorerObserver: MutationObserver | CustomMutationObserver;
private modalContainerObserver: MutationObserver | CustomMutationObserver;
private workspaceDrawerLeftObserver: MutationObserver | CustomMutationObserver;
private workspaceDrawerRightObserver: MutationObserver | CustomMutationObserver;
private activeViewDoc: Document;
get settings() {
return this.plugin.settings;
}
constructor(plugin: ExcalidrawPlugin) {
this.plugin = plugin;
this.app = plugin.app;
}
public initialize() {
try {
if(this.settings.matchThemeTrigger) this.addThemeObserver();
this.experimentalFileTypeDisplayToggle(this.settings.experimentalFileType);
this.addModalContainerObserver();
} catch (e) {
new Notice("Error adding ObserverManager", 6000);
console.error("Error adding ObserverManager", e);
}
this.plugin.logStartupEvent("ObserverManager added");
}
public destroy() {
this.removeThemeObserver();
this.removeModalContainerObserver();
if (this.workspaceDrawerLeftObserver) {
this.workspaceDrawerLeftObserver.disconnect();
}
if (this.workspaceDrawerRightObserver) {
this.workspaceDrawerRightObserver.disconnect();
}
if (this.fileExplorerObserver) {
this.fileExplorerObserver.disconnect();
}
if (this.workspaceDrawerRightObserver) {
this.workspaceDrawerRightObserver.disconnect();
}
if (this.workspaceDrawerLeftObserver) {
this.workspaceDrawerLeftObserver.disconnect();
}
}
public addThemeObserver() {
if(this.themeObserver) return;
const { matchThemeTrigger } = this.settings;
if (!matchThemeTrigger) return;
const themeObserverFn:MutationCallback = async (mutations: MutationRecord[]) => {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(themeObserverFn, `ExcalidrawPlugin.addThemeObserver`, mutations);
const { matchThemeTrigger } = this.settings;
if (!matchThemeTrigger) return;
const bodyClassList = document.body.classList;
const mutation = mutations[0];
if (mutation?.oldValue === bodyClassList.value) return;
const darkClass = bodyClassList.contains('theme-dark');
if (mutation?.oldValue?.includes('theme-dark') === darkClass) return;
setTimeout(()=>{ //run async to avoid blocking the UI
const theme = isObsidianThemeDark() ? "dark" : "light";
const excalidrawViews = getExcalidrawViews(this.app);
excalidrawViews.forEach(excalidrawView => {
if (excalidrawView.file && excalidrawView.excalidrawAPI) {
excalidrawView.setTheme(theme);
}
});
});
};
this.themeObserver = DEBUGGING
? new CustomMutationObserver(themeObserverFn, "themeObserver")
: new MutationObserver(themeObserverFn);
this.themeObserver.observe(document.body, {
attributeOldValue: true,
attributeFilter: ["class"],
});
}
public removeThemeObserver() {
if(!this.themeObserver) return;
this.themeObserver.disconnect();
this.themeObserver = null;
}
public experimentalFileTypeDisplayToggle(enabled: boolean) {
if (enabled) {
this.experimentalFileTypeDisplay();
return;
}
if (this.fileExplorerObserver) {
this.fileExplorerObserver.disconnect();
}
this.fileExplorerObserver = null;
}
/**
* Display characters configured in settings, in front of the filename, if the markdown file is an excalidraw drawing
* Must be called after the workspace is ready
* The function is called from onload()
*/
private async experimentalFileTypeDisplay() {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.experimentalFileTypeDisplay, `ExcalidrawPlugin.experimentalFileTypeDisplay`);
const insertFiletype = (el: HTMLElement) => {
if (el.childElementCount !== 1) {
return;
}
const filename = el.getAttribute("data-path");
if (!filename) {
return;
}
const f = this.app.vault.getAbstractFileByPath(filename);
if (!f || !(f instanceof TFile)) {
return;
}
if (this.plugin.isExcalidrawFile(f)) {
el.insertBefore(
createDiv({
cls: "nav-file-tag",
text: this.settings.experimentalFileTag,
}),
el.firstChild,
);
}
};
const fileExplorerObserverFn:MutationCallback = (mutationsList) => {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(fileExplorerObserverFn, `ExcalidrawPlugin.experimentalFileTypeDisplay > fileExplorerObserverFn`, mutationsList);
const mutationsWithNodes = mutationsList.filter((mutation) => mutation.addedNodes.length > 0);
mutationsWithNodes.forEach((mutationNode) => {
mutationNode.addedNodes.forEach((node) => {
if (!(node instanceof Element)) {
return;
}
node.querySelectorAll(".nav-file-title").forEach(insertFiletype);
});
});
};
this.fileExplorerObserver = DEBUGGING
? new CustomMutationObserver(fileExplorerObserverFn, "fileExplorerObserver")
: new MutationObserver(fileExplorerObserverFn);
//the part that should only run after onLayoutReady
document.querySelectorAll(".nav-file-title").forEach(insertFiletype); //apply filetype to files already displayed
const container = document.querySelector(".nav-files-container");
if (container) {
this.fileExplorerObserver.observe(container, {
childList: true,
subtree: true,
});
}
}
/**
* Monitors if the user clicks outside the Excalidraw view, and saves the drawing if it's dirty
* @returns
*/
public addModalContainerObserver() {
if(!this.plugin.activeExcalidrawView) return;
if(this.modalContainerObserver) {
if(this.activeViewDoc === this.plugin.activeExcalidrawView.ownerDocument) {
return;
}
this.removeModalContainerObserver();
}
//The user clicks settings, or "open another vault", or the command palette
const modalContainerObserverFn: MutationCallback = async (m: MutationRecord[]) => {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(modalContainerObserverFn,`ExcalidrawPlugin.modalContainerObserverFn`, m);
if (
(m.length !== 1) ||
(m[0].type !== "childList") ||
(m[0].addedNodes.length !== 1) ||
(!this.plugin.activeExcalidrawView) ||
this.plugin.activeExcalidrawView?.semaphores?.viewunload ||
(!this.plugin.activeExcalidrawView?.isDirty())
) {
return;
}
this.plugin.activeExcalidrawView.save();
};
this.modalContainerObserver = DEBUGGING
? new CustomMutationObserver(modalContainerObserverFn, "modalContainerObserver")
: new MutationObserver(modalContainerObserverFn);
this.activeViewDoc = this.plugin.activeExcalidrawView.ownerDocument;
this.modalContainerObserver.observe(this.activeViewDoc.body, {
childList: true,
});
}
public removeModalContainerObserver() {
if(!this.modalContainerObserver) return;
this.modalContainerObserver.disconnect();
this.activeViewDoc = null;
this.modalContainerObserver = null;
}
private addWorkspaceDrawerObserver() {
//when the user activates the sliding drawers on Obsidian Mobile
const leftWorkspaceDrawer = document.querySelector(
".workspace-drawer.mod-left",
);
const rightWorkspaceDrawer = document.querySelector(
".workspace-drawer.mod-right",
);
if (leftWorkspaceDrawer || rightWorkspaceDrawer) {
const action = async (m: MutationRecord[]) => {
if (
m[0].oldValue !== "display: none;" ||
!this.plugin.activeExcalidrawView ||
!this.plugin.activeExcalidrawView?.isDirty()
) {
return;
}
this.plugin.activeExcalidrawView.save();
};
const options = {
attributeOldValue: true,
attributeFilter: ["style"],
};
if (leftWorkspaceDrawer) {
this.workspaceDrawerLeftObserver = DEBUGGING
? new CustomMutationObserver(action, "slidingDrawerLeftObserver")
: new MutationObserver(action);
this.workspaceDrawerLeftObserver.observe(leftWorkspaceDrawer, options);
}
if (rightWorkspaceDrawer) {
this.workspaceDrawerRightObserver = DEBUGGING
? new CustomMutationObserver(action, "slidingDrawerRightObserver")
: new MutationObserver(action);
this.workspaceDrawerRightObserver.observe(
rightWorkspaceDrawer,
options,
);
}
}
}
}

View File

@@ -0,0 +1,97 @@
import { updateExcalidrawLib } from "src/constants/constants";
import { ExcalidrawLib } from "../../types/excalidrawLib";
import { Packages } from "../../types/types";
import { debug, DEBUGGING } from "../../utils/debugHelper";
import { Notice } from "obsidian";
import ExcalidrawPlugin from "src/core/main";
declare let REACT_PACKAGES:string;
declare let react:any;
declare let reactDOM:any;
declare let excalidrawLib: typeof ExcalidrawLib;
declare const unpackExcalidraw: Function;
export class PackageManager {
private packageMap: Map<Window, Packages> = new Map<Window, Packages>();
private EXCALIDRAW_PACKAGE: string;
constructor(plugin: ExcalidrawPlugin) {
try {
this.EXCALIDRAW_PACKAGE = unpackExcalidraw();
excalidrawLib = window.eval.call(window,`(function() {${this.EXCALIDRAW_PACKAGE};return ExcalidrawLib;})()`);
updateExcalidrawLib();
this.setPackage(window,{react, reactDOM, excalidrawLib});
} catch (e) {
new Notice("Error loading the Excalidraw package", 6000);
console.error("Error loading the Excalidraw package", e);
}
plugin.logStartupEvent("Excalidraw package unpacked");
}
public setPackage(window: Window, pkg: Packages) {
this.packageMap.set(window, pkg);
}
public getPackageMap() {
return this.packageMap;
}
public getPackage(win:Window):Packages {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.getPackage, `ExcalidrawPlugin.getPackage`, win);
if(this.packageMap.has(win)) {
return this.packageMap.get(win);
}
const {react:r, reactDOM:rd, excalidrawLib:e} = win.eval.call(win,
`(function() {
${REACT_PACKAGES + this.EXCALIDRAW_PACKAGE};
return {react:React,reactDOM:ReactDOM,excalidrawLib:ExcalidrawLib};
})()`);
this.packageMap.set(win,{react:r, reactDOM:rd, excalidrawLib:e});
return {react:r, reactDOM:rd, excalidrawLib:e};
}
public deletePackage(win: Window) {
const { react, reactDOM, excalidrawLib } = this.getPackage(win);
if (win.ExcalidrawLib === excalidrawLib) {
excalidrawLib.destroyObsidianUtils();
delete win.ExcalidrawLib;
}
if (win.React === react) {
Object.keys(win.React).forEach((key) => {
delete win.React[key];
});
delete win.React;
}
if (win.ReactDOM === reactDOM) {
Object.keys(win.ReactDOM).forEach((key) => {
delete win.ReactDOM[key];
});
delete win.ReactDOM;
}
this.packageMap.delete(win);
}
public setExcalidrawPackage(pkg: string) {
this.EXCALIDRAW_PACKAGE = pkg;
}
public destroy() {
REACT_PACKAGES = "";
Object.values(this.packageMap).forEach((p: Packages) => {
delete p.excalidrawLib;
delete p.reactDOM;
delete p.react;
});
this.packageMap.clear();
this.EXCALIDRAW_PACKAGE = "";
react = null;
reactDOM = null;
excalidrawLib = null;
}
}

View File

@@ -1,7 +1,9 @@
import { WorkspaceWindow } from "obsidian";
import ExcalidrawPlugin from "src/main";
import { getAllWindowDocuments } from "./ObsidianUtils";
import { DEBUGGING, debug } from "./DebugHelper";
import ExcalidrawPlugin from "src/core/main";
import { getAllWindowDocuments } from "../../utils/obsidianUtils";
import { DEBUGGING, debug } from "../../utils/debugHelper";
export let REM_VALUE = 16;
const STYLE_VARIABLES = [
"--background-modifier-cover",
@@ -39,6 +41,7 @@ export class StylesManager {
this.plugin = plugin;
plugin.app.workspace.onLayoutReady(async () => {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(undefined, "StylesManager.constructor > app.workspace.onLayoutReady", this);
await plugin.awaitInit();
await this.harvestStyles();
getAllWindowDocuments(plugin.app).forEach(doc => this.copyPropertiesToTheme(doc));
@@ -77,6 +80,11 @@ export class StylesManager {
}
private async harvestStyles() {
REM_VALUE = parseInt(window.getComputedStyle(document.body).getPropertyValue('--font-text-size').trim());
if (isNaN(REM_VALUE)) {
REM_VALUE = 16;
}
const body = document.body;
const iframe:HTMLIFrameElement = document.createElement("iframe");
iframe.style.display = "none";

View File

@@ -10,37 +10,39 @@ import {
TextComponent,
TFile,
} from "obsidian";
import { GITHUB_RELEASES, VIEW_TYPE_EXCALIDRAW } from "./constants/constants";
import ExcalidrawView from "./ExcalidrawView";
import { t } from "./lang/helpers";
import type ExcalidrawPlugin from "./main";
import { PenStyle } from "./PenTypes";
import { DynamicStyle } from "./types/types";
import { PreviewImageType } from "./utils/UtilTypes";
import { setDynamicStyle } from "./utils/DynamicStyling";
import { GITHUB_RELEASES, setRootElementSize } from "src/constants/constants";
import { t } from "src/lang/helpers";
import type ExcalidrawPlugin from "src/core/main";
import { PenStyle } from "src/types/penTypes";
import { DynamicStyle, GridSettings } from "src/types/types";
import { PreviewImageType } from "src/types/utilTypes";
import { setDynamicStyle } from "src/utils/dynamicStyling";
import {
getDrawingFilename,
getEmbedFilename,
} from "./utils/FileUtils";
import { PENS } from "./utils/Pens";
} from "src/utils/fileUtils";
import { PENS } from "src/utils/pens";
import {
addIframe,
fragWithHTML,
setLeftHandedMode,
} from "./utils/Utils";
import { imageCache } from "./utils/ImageCache";
import { ConfirmationPrompt } from "./dialogs/Prompt";
import { EmbeddableMDCustomProps } from "./dialogs/EmbeddableSettings";
import { EmbeddalbeMDFileCustomDataSettingsComponent } from "./dialogs/EmbeddableMDFileCustomDataSettingsComponent";
import { startupScript } from "./constants/starutpscript";
import { ModifierKeySet, ModifierSetType } from "./utils/ModifierkeyHelper";
import { ModifierKeySettingsComponent } from "./dialogs/ModifierKeySettings";
import { ANNOTATED_PREFIX, CROPPED_PREFIX } from "./utils/CarveOut";
import { EDITOR_FADEOUT } from "./CodeMirrorExtension/EditorHandler";
import { setDebugging } from "./utils/DebugHelper";
import { Rank } from "./menu/ActionIcons";
} from "src/utils/utils";
import { imageCache } from "src/shared/ImageCache";
import { ConfirmationPrompt } from "src/shared/Dialogs/Prompt";
import { EmbeddableMDCustomProps } from "src/shared/Dialogs/EmbeddableSettings";
import { EmbeddalbeMDFileCustomDataSettingsComponent } from "src/shared/Dialogs/EmbeddableMDFileCustomDataSettingsComponent";
import { startupScript } from "src/constants/starutpscript";
import { ModifierKeySet, ModifierSetType } from "src/utils/modifierkeyHelper";
import { ModifierKeySettingsComponent } from "src/shared/Dialogs/ModifierKeySettings";
import { ANNOTATED_PREFIX, CROPPED_PREFIX } from "src/utils/carveout";
import { EDITOR_FADEOUT } from "src/core/editor/EditorHandler";
import { setDebugging } from "src/utils/debugHelper";
import { Rank } from "src/constants/actionIcons";
import { TAG_AUTOEXPORT, TAG_MDREADINGMODE, TAG_PDFEXPORT } from "src/constants/constSettingsTags";
import { HotkeyEditor } from "./dialogs/HotkeyEditor";
import { HotkeyEditor } from "src/shared/Dialogs/HotkeyEditor";
import { getExcalidrawViews } from "src/utils/obsidianUtils";
import { createSliderWithText } from "src/utils/sliderUtils";
import { PDFExportSettingsComponent, PDFExportSettings } from "src/shared/Dialogs/PDFExportSettingsComponent";
export interface ExcalidrawSettings {
folder: string;
@@ -49,6 +51,10 @@ export interface ExcalidrawSettings {
embedUseExcalidrawFolder: boolean;
templateFilePath: string;
scriptFolderPath: string;
fontAssetsPath: string;
loadChineseFonts: boolean;
loadJapaneseFonts: boolean;
loadKoreanFonts: boolean;
compress: boolean;
decompressForMDView: boolean;
onceOffCompressFlagReset: boolean; //used to reset compress to true in 2.2.0
@@ -66,12 +72,14 @@ export interface ExcalidrawSettings {
annotatePreserveSize: boolean;
displaySVGInPreview: boolean; //No longer used since 1.9.13
previewImageType: PreviewImageType; //Introduced with 1.9.13
renderingConcurrency: number;
allowImageCache: boolean;
allowImageCacheInScene: boolean;
displayExportedImageIfAvailable: boolean;
previewMatchObsidianTheme: boolean;
width: string;
height: string;
overrideObsidianFontSize: boolean;
dynamicStyling: DynamicStyle;
isLeftHanded: boolean;
iframeMatchExcalidrawTheme: boolean;
@@ -81,6 +89,7 @@ export interface ExcalidrawSettings {
defaultMode: string;
defaultPenMode: "never" | "mobile" | "always";
penModeDoubleTapEraser: boolean;
penModeSingleFingerPanning: boolean;
penModeCrosshairVisible: boolean;
renderImageInMarkdownReadingMode: boolean,
renderImageInHoverPreviewForMDNotes: boolean,
@@ -164,6 +173,7 @@ export interface ExcalidrawSettings {
showNewVersionNotification: boolean;
//mathjaxSourceURL: string;
latexBoilerplate: string;
latexPreambleLocation: string;
taskboneEnabled: boolean;
taskboneAPIkey: string;
pinnedScripts: string[];
@@ -179,6 +189,7 @@ export interface ExcalidrawSettings {
pdfNumRows: number;
pdfDirection: "down" | "right";
pdfImportScale: number;
gridSettings: GridSettings;
laserSettings: {
DECAY_TIME: number,
DECAY_LENGTH: number,
@@ -204,10 +215,12 @@ export interface ExcalidrawSettings {
areaZoomLimit: number;
longPressDesktop: number;
longPressMobile: number;
doubleClickLinkOpenViewMode: boolean;
isDebugMode: boolean;
rank: Rank;
modifierKeyOverrides: {modifiers: Modifier[], key: string}[];
showSplashscreen: boolean;
pdfSettings: PDFExportSettings;
}
declare const PLUGIN_VERSION:string;
@@ -219,13 +232,17 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
embedUseExcalidrawFolder: false,
templateFilePath: "Excalidraw/Template.excalidraw",
scriptFolderPath: "Excalidraw/Scripts",
fontAssetsPath: "Excalidraw/CJK Fonts",
loadChineseFonts: false,
loadJapaneseFonts: false,
loadKoreanFonts: false,
compress: true,
decompressForMDView: false,
onceOffCompressFlagReset: false,
onceOffGPTVersionReset: false,
autosave: true,
autosaveIntervalDesktop: 30000,
autosaveIntervalMobile: 20000,
autosaveIntervalDesktop: 60000,
autosaveIntervalMobile: 30000,
drawingFilenamePrefix: "Drawing ",
drawingEmbedPrefixWithFilename: true,
drawingFilnameEmbedPostfix: " ",
@@ -236,12 +253,14 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
annotatePreserveSize: false,
displaySVGInPreview: undefined,
previewImageType: undefined,
renderingConcurrency: 3,
allowImageCache: true,
allowImageCacheInScene: true,
displayExportedImageIfAvailable: false,
previewMatchObsidianTheme: false,
width: "400",
height: "",
overrideObsidianFontSize: false,
dynamicStyling: "colorful",
isLeftHanded: false,
iframeMatchExcalidrawTheme: true,
@@ -251,6 +270,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
defaultMode: "normal",
defaultPenMode: "never",
penModeDoubleTapEraser: true,
penModeSingleFingerPanning: true,
penModeCrosshairVisible: true,
renderImageInMarkdownReadingMode: false,
renderImageInHoverPreviewForMDNotes: false,
@@ -267,9 +287,9 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
done: "🗹",
hoverPreviewWithoutCTRL: false,
linkOpacity: 1,
openInAdjacentPane: false,
openInAdjacentPane: true,
showSecondOrderLinks: true,
focusOnFileTab: false,
focusOnFileTab: true,
openInMainWorkspace: true,
showLinkBrackets: true,
allowCtrlClick: true,
@@ -329,6 +349,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
showNewVersionNotification: true,
//mathjaxSourceURL: "https://cdn.jsdelivr.net/npm/mathjax@3.2.1/es5/tex-svg.js",
latexBoilerplate: "\\color{blue}",
latexPreambleLocation: "preamble.sty",
taskboneEnabled: false,
taskboneAPIkey: "",
pinnedScripts: [],
@@ -355,6 +376,11 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
pdfNumRows: 1,
pdfDirection: "right",
pdfImportScale: 0.3,
gridSettings: {
DYNAMIC_COLOR: true,
COLOR: "#000000",
OPACITY: 50,
},
laserSettings: {
DECAY_LENGTH: 50,
DECAY_TIME: 1000,
@@ -466,6 +492,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
areaZoomLimit: 1,
longPressDesktop: 500,
longPressMobile: 500,
doubleClickLinkOpenViewMode: true,
isDebugMode: false,
rank: "Bronze",
modifierKeyOverrides: [
@@ -474,6 +501,15 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
{modifiers: ["Mod"], key:"G"},
],
showSplashscreen: true,
pdfSettings: {
pageSize: "A4",
pageOrientation: "portrait",
fitToPage: 1,
paperColor: "white",
customPaperColor: "#ffffff",
alignment: "center",
margin: "normal",
},
};
export class ExcalidrawSettingTab extends PluginSettingTab {
@@ -498,6 +534,14 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
}
async hide() {
if(this.plugin.settings.overrideObsidianFontSize) {
document.documentElement.style.fontSize = "";
setRootElementSize(16);
} else if(!document.documentElement.style.fontSize) {
document.documentElement.style.fontSize = getComputedStyle(document.body).getPropertyValue("--font-text-size");
setRootElementSize();
}
this.plugin.settings.scriptFolderPath = normalizePath(
this.plugin.settings.scriptFolderPath,
);
@@ -509,31 +553,25 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
}
this.plugin.saveSettings();
if (this.requestUpdatePinnedPens) {
this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> {
if (v.view instanceof ExcalidrawView) v.view.updatePinnedCustomPens()
})
getExcalidrawViews(this.app).forEach(excalidrawView =>
excalidrawView.updatePinnedCustomPens()
)
}
if (this.requestUpdateDynamicStyling) {
this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> {
if (v.view instanceof ExcalidrawView) {
setDynamicStyle(this.plugin.ea,v.view,v.view.previousBackgroundColor,this.plugin.settings.dynamicStyling);
}
})
getExcalidrawViews(this.app).forEach(excalidrawView =>
setDynamicStyle(this.plugin.ea,excalidrawView,excalidrawView.previousBackgroundColor,this.plugin.settings.dynamicStyling)
)
}
this.hotkeyEditor.unload();
if (this.hotkeyEditor.isDirty) {
this.plugin.registerHotkeyOverrides();
}
if (this.requestReloadDrawings) {
const exs =
this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
for (const v of exs) {
if (v.view instanceof ExcalidrawView) {
await v.view.save(false);
//debug({where:"ExcalidrawSettings.hide",file:v.view.file.name,before:"reload(true)"})
await v.view.reload(true);
}
const excalidrawViews = getExcalidrawViews(this.app);
for (const excalidrawView of excalidrawViews) {
await excalidrawView.save(false);
//debug({where:"ExcalidrawSettings.hide",file:v.view.file.name,before:"reload(true)"})
await excalidrawView.reload(true);
}
this.requestEmbedUpdate = true;
}
@@ -720,7 +758,6 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
}),
);
// ------------------------------------------------
// Saving
// ------------------------------------------------
@@ -1032,44 +1069,6 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
cls: "excalidraw-setting-h1",
});
new Setting(detailsEl)
.setName(t("DEFAULT_PEN_MODE_NAME"))
.setDesc(fragWithHTML(t("DEFAULT_PEN_MODE_DESC")))
.addDropdown((dropdown) =>
dropdown
.addOption("never", "Never")
.addOption("mobile", "On Obsidian Mobile")
.addOption("always", "Always")
.setValue(this.plugin.settings.defaultPenMode)
.onChange(async (value: "never" | "always" | "mobile") => {
this.plugin.settings.defaultPenMode = value;
this.applySettingsUpdate();
}),
);
new Setting(detailsEl)
.setName(t("DISABLE_DOUBLE_TAP_ERASER_NAME"))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.penModeDoubleTapEraser)
.onChange(async (value) => {
this.plugin.settings.penModeDoubleTapEraser = value;
this.applySettingsUpdate();
}),
);
new Setting(detailsEl)
.setName(t("SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_NAME"))
.setDesc(fragWithHTML(t("SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.penModeCrosshairVisible)
.onChange(async (value) => {
this.plugin.settings.penModeCrosshairVisible = value;
this.applySettingsUpdate();
}),
);
const readingModeEl = new Setting(detailsEl)
.setName(t("SHOW_DRAWING_OR_MD_IN_READING_MODE_NAME"))
.setDesc(fragWithHTML(t("SHOW_DRAWING_OR_MD_IN_READING_MODE_DESC")))
@@ -1141,6 +1140,18 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
cls: "excalidraw-setting-h3",
});
new Setting(detailsEl)
.setName(t("OVERRIDE_OBSIDIAN_FONT_SIZE_NAME"))
.setDesc(fragWithHTML(t("OVERRIDE_OBSIDIAN_FONT_SIZE_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.overrideObsidianFontSize)
.onChange((value) => {
this.plugin.settings.overrideObsidianFontSize = value;
this.applySettingsUpdate();
}),
);
new Setting(detailsEl)
.setName(t("DYNAMICSTYLE_NAME"))
.setDesc(fragWithHTML(t("DYNAMICSTYLE_DESC")))
@@ -1241,9 +1252,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
.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()
})
getExcalidrawViews(this.app).forEach(excalidrawView=>excalidrawView.updatePinchZoom())
this.applySettingsUpdate();
}),
);
@@ -1257,9 +1266,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
.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()
})
getExcalidrawViews(this.app).forEach(excalidrawView=>excalidrawView.updateWheelZoom());
this.applySettingsUpdate();
}),
);
@@ -1288,29 +1295,143 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
}),
);
let zoomText: HTMLDivElement;
new Setting(detailsEl)
.setName(t("ZOOM_TO_FIT_MAX_LEVEL_NAME"))
.setDesc(fragWithHTML(t("ZOOM_TO_FIT_MAX_LEVEL_DESC")))
.addSlider((slider) =>
slider
.setLimits(0.5, 10, 0.5)
.setValue(this.plugin.settings.zoomToFitMaxLevel)
.onChange(async (value) => {
zoomText.innerText = ` ${value.toString()}`;
this.plugin.settings.zoomToFitMaxLevel = value;
this.applySettingsUpdate();
}),
)
.settingEl.createDiv("", (el) => {
zoomText = el;
el.style.minWidth = "2.3em";
el.style.textAlign = "right";
el.innerText = ` ${this.plugin.settings.zoomToFitMaxLevel.toString()}`;
createSliderWithText(detailsEl, {
name: t("ZOOM_TO_FIT_MAX_LEVEL_NAME"),
desc: t("ZOOM_TO_FIT_MAX_LEVEL_DESC"),
value: this.plugin.settings.zoomToFitMaxLevel,
min: 0.5,
max: 10,
step: 0.5,
onChange: (value) => {
this.plugin.settings.zoomToFitMaxLevel = value;
this.applySettingsUpdate();
}
})
// ------------------------------------------------
// Pen
// ------------------------------------------------
detailsEl = displayDetailsEl.createEl("details");
detailsEl.createEl("summary", {
text: t("PEN_HEAD"),
cls: "excalidraw-setting-h3",
});
new Setting(detailsEl)
.setName(t("DEFAULT_PEN_MODE_NAME"))
.setDesc(fragWithHTML(t("DEFAULT_PEN_MODE_DESC")))
.addDropdown((dropdown) =>
dropdown
.addOption("never", "Never")
.addOption("mobile", "On Obsidian Mobile")
.addOption("always", "Always")
.setValue(this.plugin.settings.defaultPenMode)
.onChange(async (value: "never" | "always" | "mobile") => {
this.plugin.settings.defaultPenMode = value;
this.applySettingsUpdate();
}),
);
new Setting(detailsEl)
.setName(t("DISABLE_DOUBLE_TAP_ERASER_NAME"))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.penModeDoubleTapEraser)
.onChange(async (value) => {
this.plugin.settings.penModeDoubleTapEraser = value;
this.applySettingsUpdate();
}),
);
new Setting(detailsEl)
.setName(t("DISABLE_SINGLE_FINGER_PANNING_NAME"))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.penModeSingleFingerPanning)
.onChange(async (value) => {
this.plugin.settings.penModeSingleFingerPanning = value;
this.applySettingsUpdate();
}),
);
new Setting(detailsEl)
.setName(t("SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_NAME"))
.setDesc(fragWithHTML(t("SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.penModeCrosshairVisible)
.onChange(async (value) => {
this.plugin.settings.penModeCrosshairVisible = value;
this.applySettingsUpdate();
}),
);
// ------------------------------------------------
// Grid
// ------------------------------------------------
detailsEl = displayDetailsEl.createEl("details");
detailsEl.createEl("summary", {
text: t("GRID_HEAD"),
cls: "excalidraw-setting-h3",
});
const updateGridColor = () => {
getExcalidrawViews(this.app).forEach(excalidrawView=>excalidrawView.updateGridColor());
};
// Dynamic color toggle
let gridColorSection: HTMLDivElement;
new Setting(detailsEl)
.setName(t("GRID_DYNAMIC_COLOR_NAME"))
.setDesc(fragWithHTML(t("GRID_DYNAMIC_COLOR_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.gridSettings.DYNAMIC_COLOR)
.onChange(async (value) => {
this.plugin.settings.gridSettings.DYNAMIC_COLOR = value;
gridColorSection.style.display = value ? "none" : "block";
this.applySettingsUpdate();
updateGridColor();
}),
);
// Create a div to contain color and opacity settings
gridColorSection = detailsEl.createDiv();
gridColorSection.style.display = this.plugin.settings.gridSettings.DYNAMIC_COLOR ? "none" : "block";
// Grid color picker
new Setting(gridColorSection)
.setName(t("GRID_COLOR_NAME"))
.addColorPicker((colorPicker) =>
colorPicker
.setValue(this.plugin.settings.gridSettings.COLOR)
.onChange(async (value) => {
this.plugin.settings.gridSettings.COLOR = value;
this.applySettingsUpdate();
updateGridColor();
}),
);
// Grid opacity slider (hex value between 00 and FF)
createSliderWithText(detailsEl, {
name: t("GRID_OPACITY_NAME"),
desc: t("GRID_OPACITY_DESC"),
value: this.plugin.settings.gridSettings.OPACITY,
min: 0,
max: 100,
step: 1,
onChange: (value) => {
this.plugin.settings.gridSettings.OPACITY = value;
this.applySettingsUpdate();
updateGridColor();
},
minWidth: "3em",
})
// ------------------------------------------------
// Laser Pointer
// ------------------------------------------------
detailsEl = displayDetailsEl.createEl("details");
detailsEl.createEl("summary", {
text: t("LASER_HEAD"),
@@ -1327,47 +1448,33 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
}),
);
let decayTime: HTMLDivElement;
new Setting(detailsEl)
.setName(t("LASER_DECAY_TIME_NAME"))
.setDesc(fragWithHTML(t("LASER_DECAY_TIME_DESC")))
.addSlider((slider) =>
slider
.setLimits(500, 20000, 500)
.setValue(this.plugin.settings.laserSettings.DECAY_TIME)
.onChange(async (value) => {
decayTime.innerText = ` ${value.toString()}`;
this.plugin.settings.laserSettings.DECAY_TIME = value;
this.applySettingsUpdate();
}),
)
.settingEl.createDiv("", (el) => {
decayTime = el;
el.style.minWidth = "3em";
el.style.textAlign = "right";
el.innerText = ` ${this.plugin.settings.laserSettings.DECAY_TIME.toString()}`;
});
createSliderWithText(detailsEl, {
name: t("LASER_DECAY_TIME_NAME"),
desc: t("LASER_DECAY_TIME_DESC"),
value: this.plugin.settings.laserSettings.DECAY_TIME,
min: 500,
max: 20000,
step: 500,
onChange: (value) => {
this.plugin.settings.laserSettings.DECAY_TIME = value;
this.applySettingsUpdate();
},
minWidth: "3em",
})
let decayLength: HTMLDivElement;
new Setting(detailsEl)
.setName(t("LASER_DECAY_LENGTH_NAME"))
.setDesc(fragWithHTML(t("LASER_DECAY_LENGTH_DESC")))
.addSlider((slider) =>
slider
.setLimits(25, 2000, 25)
.setValue(this.plugin.settings.laserSettings.DECAY_LENGTH)
.onChange(async (value) => {
decayLength.innerText = ` ${value.toString()}`;
this.plugin.settings.laserSettings.DECAY_LENGTH = value;
this.applySettingsUpdate();
}),
)
.settingEl.createDiv("", (el) => {
decayLength = el;
el.style.minWidth = "3em";
el.style.textAlign = "right";
el.innerText = ` ${this.plugin.settings.laserSettings.DECAY_LENGTH.toString()}`;
});
createSliderWithText(detailsEl, {
name: t("LASER_DECAY_LENGTH_NAME"),
desc: t("LASER_DECAY_LENGTH_DESC"),
value: this.plugin.settings.laserSettings.DECAY_LENGTH,
min: 25,
max: 2000,
step: 25,
onChange: (value) => {
this.plugin.settings.laserSettings.DECAY_LENGTH = value;
this.applySettingsUpdate();
},
minWidth: "3em",
})
detailsEl = displayDetailsEl.createEl("details");
detailsEl.createEl("summary", {
@@ -1376,47 +1483,43 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
});
detailsEl.createDiv({ text: t("DRAG_MODIFIER_DESC"), cls: "setting-item-description" });
let longPressDesktop: HTMLDivElement;
new Setting(detailsEl)
.setName(t("LONG_PRESS_DESKTOP_NAME"))
.setDesc(fragWithHTML(t("LONG_PRESS_DESKTOP_DESC")))
.addSlider((slider) =>
slider
.setLimits(300, 3000, 100)
.setValue(this.plugin.settings.longPressDesktop)
.onChange(async (value) => {
longPressDesktop.innerText = ` ${value.toString()}`;
this.plugin.settings.longPressDesktop = value;
this.applySettingsUpdate(true);
}),
)
.settingEl.createDiv("", (el) => {
longPressDesktop = el;
el.style.minWidth = "2.3em";
el.style.textAlign = "right";
el.innerText = ` ${this.plugin.settings.longPressDesktop.toString()}`;
});
createSliderWithText(detailsEl, {
name: t("LONG_PRESS_DESKTOP_NAME"),
desc: t("LONG_PRESS_DESKTOP_DESC"),
value: this.plugin.settings.longPressDesktop,
min: 300,
max: 3000,
step: 100,
onChange: (value) => {
this.plugin.settings.longPressDesktop = value;
this.applySettingsUpdate(true);
},
})
createSliderWithText(detailsEl, {
name: t("LONG_PRESS_MOBILE_NAME"),
desc: t("LONG_PRESS_MOBILE_DESC"),
value: this.plugin.settings.longPressMobile,
min: 300,
max: 3000,
step: 100,
onChange: (value) => {
this.plugin.settings.longPressMobile = value;
this.applySettingsUpdate(true);
},
})
let longPressMobile: HTMLDivElement;
new Setting(detailsEl)
.setName(t("LONG_PRESS_MOBILE_NAME"))
.setDesc(fragWithHTML(t("LONG_PRESS_MOBILE_DESC")))
.addSlider((slider) =>
slider
.setLimits(300, 3000, 100)
.setValue(this.plugin.settings.longPressMobile)
.setName(t("DOUBLE_CLICK_LINK_OPEN_VIEW_MODE"))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.doubleClickLinkOpenViewMode)
.onChange(async (value) => {
longPressMobile.innerText = ` ${value.toString()}`;
this.plugin.settings.longPressMobile = value;
this.applySettingsUpdate(true);
this.plugin.settings.doubleClickLinkOpenViewMode = value;
this.applySettingsUpdate();
}),
)
.settingEl.createDiv("", (el) => {
longPressMobile = el;
el.style.minWidth = "2.3em";
el.style.textAlign = "right";
el.innerText = ` ${this.plugin.settings.longPressMobile.toString()}`;
});
);
new ModifierKeySettingsComponent(
detailsEl,
@@ -1576,26 +1679,18 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
);
donePrefixSetting.setDisabled(!this.plugin.settings.parseTODO);
let opacityText: HTMLDivElement;
new Setting(detailsEl)
.setName(t("LINKOPACITY_NAME"))
.setDesc(fragWithHTML(t("LINKOPACITY_DESC")))
.addSlider((slider) =>
slider
.setLimits(0, 1, 0.05)
.setValue(this.plugin.settings.linkOpacity)
.onChange(async (value) => {
opacityText.innerText = ` ${value.toString()}`;
this.plugin.settings.linkOpacity = value;
this.applySettingsUpdate(true);
}),
)
.settingEl.createDiv("", (el) => {
opacityText = el;
el.style.minWidth = "2.3em";
el.style.textAlign = "right";
el.innerText = ` ${this.plugin.settings.linkOpacity.toString()}`;
});
createSliderWithText(detailsEl, {
name: t("LINKOPACITY_NAME"),
desc: t("LINKOPACITY_DESC"),
value: this.plugin.settings.linkOpacity,
min: 0,
max: 1,
step: 0.05,
onChange: (value) => {
this.plugin.settings.linkOpacity = value;
this.applySettingsUpdate(true);
},
});
new Setting(detailsEl)
.setName(t("HOVERPREVIEW_NAME"))
@@ -1829,6 +1924,19 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
cls: "excalidraw-setting-h3",
});
createSliderWithText(detailsEl, {
name: t("RENDERING_CONCURRENCY_NAME"),
desc: t("RENDERING_CONCURRENCY_DESC"),
min: 1,
max: 5,
step: 1,
value: this.plugin.settings.renderingConcurrency,
onChange: (value) => {
this.plugin.settings.renderingConcurrency = value;
this.applySettingsUpdate();
}
});
new Setting(detailsEl)
.setName(t("EMBED_IMAGE_CACHE_NAME"))
.setDesc(fragWithHTML(t("EMBED_IMAGE_CACHE_DESC")))
@@ -1953,49 +2061,31 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
}),
);
let scaleText: HTMLDivElement;
createSliderWithText(detailsEl, {
name: t("EXPORT_PNG_SCALE_NAME"),
desc: t("EXPORT_PNG_SCALE_DESC"),
value: this.plugin.settings.pngExportScale,
min: 1,
max: 5,
step: 0.5,
onChange: (value) => {
this.plugin.settings.pngExportScale = value;
this.applySettingsUpdate();
}
});
new Setting(detailsEl)
.setName(t("EXPORT_PNG_SCALE_NAME"))
.setDesc(fragWithHTML(t("EXPORT_PNG_SCALE_DESC")))
.addSlider((slider) =>
slider
.setLimits(1, 5, 0.5)
.setValue(this.plugin.settings.pngExportScale)
.onChange(async (value) => {
scaleText.innerText = ` ${value.toString()}`;
this.plugin.settings.pngExportScale = value;
this.applySettingsUpdate();
}),
)
.settingEl.createDiv("", (el) => {
scaleText = el;
el.style.minWidth = "2.3em";
el.style.textAlign = "right";
el.innerText = ` ${this.plugin.settings.pngExportScale.toString()}`;
});
let exportPadding: HTMLDivElement;
new Setting(detailsEl)
.setName(t("EXPORT_PADDING_NAME"))
.setDesc(fragWithHTML(t("EXPORT_PADDING_DESC")))
.addSlider((slider) =>
slider
.setLimits(0, 50, 5)
.setValue(this.plugin.settings.exportPaddingSVG)
.onChange(async (value) => {
exportPadding.innerText = ` ${value.toString()}`;
this.plugin.settings.exportPaddingSVG = value;
this.applySettingsUpdate();
}),
)
.settingEl.createDiv("", (el) => {
exportPadding = el;
el.style.minWidth = "2.3em";
el.style.textAlign = "right";
el.innerText = ` ${this.plugin.settings.exportPaddingSVG.toString()}`;
});
createSliderWithText(detailsEl, {
name: t("EXPORT_PADDING_NAME"),
desc: fragWithHTML(t("EXPORT_PADDING_DESC")),
value: this.plugin.settings.exportPaddingSVG,
min: 0,
max: 50,
step: 5,
onChange: (value) => {
this.plugin.settings.exportPaddingSVG = value;
this.applySettingsUpdate();
}
});
detailsEl = exportDetailsEl.createEl("details");
detailsEl.createEl("summary", {
@@ -2041,6 +2131,20 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
}),
);
detailsEl = exportDetailsEl.createEl("details");
detailsEl.createEl("summary", {
text: t("PDF_EXPORT_SETTINGS"),
cls: "excalidraw-setting-h4",
});
new PDFExportSettingsComponent(
detailsEl,
this.plugin.settings.pdfSettings,
() => {
this.applySettingsUpdate();
}
).render();
detailsEl = exportDetailsEl.createEl("details");
detailsEl.createEl("summary", {
text: t("EXPORT_HEAD"),
@@ -2185,9 +2289,6 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
el.innerHTML = t("MD_EMBED_CUSTOMDATA_HEAD_DESC");
});
new EmbeddalbeMDFileCustomDataSettingsComponent(
detailsEl,
this.plugin.settings.embeddableMarkdownDefaults,
@@ -2263,7 +2364,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
d.addOption("Assistant", "Assistant");
this.app.vault
.getFiles()
.filter((f) => ["ttf", "woff", "woff2", "otf"].contains(f.extension))
.filter((f) => ["ttf", "woff", "woff2", "otf"].contains(f.extension) && !f.path.startsWith(this.plugin.settings.fontAssetsPath))
.forEach((f: TFile) => {
d.addOption(f.path, f.name);
});
@@ -2336,27 +2437,19 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
cls: "excalidraw-setting-h3",
});
let areaZoomText: HTMLDivElement;
new Setting(detailsEl)
.setName(t("MAX_IMAGE_ZOOM_IN_NAME"))
.setDesc(fragWithHTML(t("MAX_IMAGE_ZOOM_IN_DESC")))
.addSlider((slider) =>
slider
.setLimits(1, 10, 0.5)
.setValue(this.plugin.settings.areaZoomLimit)
.onChange(async (value) => {
areaZoomText.innerText = ` ${value.toString()}`;
this.plugin.settings.areaZoomLimit = value;
this.applySettingsUpdate();
this.plugin.excalidrawConfig.updateValues(this.plugin);
}),
)
.settingEl.createDiv("", (el) => {
areaZoomText = el;
el.style.minWidth = "2.3em";
el.style.textAlign = "right";
el.innerText = ` ${this.plugin.settings.areaZoomLimit.toString()}`;
createSliderWithText(detailsEl, {
name: t("MAX_IMAGE_ZOOM_IN_NAME"),
desc: fragWithHTML(t("MAX_IMAGE_ZOOM_IN_DESC")),
value: this.plugin.settings.areaZoomLimit,
min: 1,
max: 10,
step: 0.5,
onChange: (value) => {
this.plugin.settings.areaZoomLimit = value;
this.applySettingsUpdate();
this.plugin.excalidrawConfig.updateValues(this.plugin);
},
});
detailsEl = nonstandardDetailsEl.createEl("details");
@@ -2388,8 +2481,20 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
this.applySettingsUpdate(false);
})
)
// ------------------------------------------------
// Fonts supported features
// ------------------------------------------------
containerEl.createEl("hr", { cls: "excalidraw-setting-hr" });
containerEl.createDiv({ text: t("FONTS_DESC"), cls: "setting-item-description" });
detailsEl = this.containerEl.createEl("details");
const fontsDetailsEl = detailsEl;
detailsEl.createEl("summary", {
text: t("FONTS_HEAD"),
cls: "excalidraw-setting-h1",
});
detailsEl = nonstandardDetailsEl.createEl("details");
detailsEl = fontsDetailsEl.createEl("details");
detailsEl.createEl("summary", {
text: t("CUSTOM_FONT_HEAD"),
cls: "excalidraw-setting-h3",
@@ -2405,6 +2510,9 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
this.requestReloadDrawings = true;
this.plugin.settings.experimentalEnableFourthFont = value;
this.applySettingsUpdate();
if(value) {
this.plugin.initializeFonts();
}
}),
);
@@ -2416,7 +2524,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
d.addOption("Virgil", "Virgil");
this.app.vault
.getFiles()
.filter((f) => ["ttf", "woff", "woff2", "otf"].contains(f.extension))
.filter((f) => ["ttf", "woff", "woff2", "otf"].contains(f.extension) && !f.path.startsWith(this.plugin.settings.fontAssetsPath))
.forEach((f: TFile) => {
d.addOption(f.path, f.name);
});
@@ -2430,7 +2538,61 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
);
});
detailsEl = fontsDetailsEl.createEl("details");
detailsEl.createEl("summary", {
text: t("OFFLINE_CJK_NAME"),
cls: "excalidraw-setting-h3",
});
const cjkdescdiv = detailsEl.createDiv({ cls: "setting-item-description" });
cjkdescdiv.innerHTML = t("OFFLINE_CJK_DESC");
new Setting(detailsEl)
.setName(t("CJK_ASSETS_FOLDER_NAME"))
.setDesc(fragWithHTML(t("CJK_ASSETS_FOLDER_DESC")))
.addText((text) =>
text
.setPlaceholder("e.g.: Excalidraw/FontAssets")
.setValue(this.plugin.settings.fontAssetsPath)
.onChange(async (value) => {
this.plugin.settings.fontAssetsPath = value;
this.applySettingsUpdate();
}),
);
new Setting(detailsEl)
.setName(t("LOAD_CHINESE_FONTS_NAME"))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.loadChineseFonts)
.onChange(async (value) => {
this.plugin.settings.loadChineseFonts = value;
this.applySettingsUpdate();
}),
);
new Setting(detailsEl)
.setName(t("LOAD_JAPANESE_FONTS_NAME"))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.loadJapaneseFonts)
.onChange(async (value) => {
this.plugin.settings.loadJapaneseFonts = value;
this.applySettingsUpdate();
}),
);
new Setting(detailsEl)
.setName(t("LOAD_KOREAN_FONTS_NAME"))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.loadKoreanFonts)
.onChange(async (value) => {
this.plugin.settings.loadKoreanFonts = value;
this.applySettingsUpdate();
}),
);
// ------------------------------------------------
// Experimental features
// ------------------------------------------------
@@ -2456,6 +2618,19 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
}),
);
new Setting(detailsEl)
.setName(t("LATEX_PREAMBLE_NAME"))
.setDesc(fragWithHTML(t("LATEX_PREAMBLE_DESC")))
.addText((text) =>
text
.setPlaceholder("e.g.: preamble.sty")
.setValue(this.plugin.settings.latexPreambleLocation)
.onChange(async (value) => {
this.plugin.settings.latexPreambleLocation = value;
this.applySettingsUpdate();
}),
);
new Setting(detailsEl)
.setName(t("FILETYPE_NAME"))
.setDesc(fragWithHTML(t("FILETYPE_DESC")))

View File

@@ -1,224 +0,0 @@
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
import { Modal, Setting, TFile } from "obsidian";
import { getEA } from "src";
import { DEVICE } from "src/constants/constants";
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
import ExcalidrawView from "src/ExcalidrawView";
import ExcalidrawPlugin from "src/main";
import { fragWithHTML, getExportPadding, getExportTheme, getPNGScale, getWithBackground, shouldEmbedScene } 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 selectedOnlySetting: Setting;
private hasSelectedElements: boolean = false;
private boundingBox: {
topX: number;
topY: number;
width: number;
height: number;
};
public embedScene: boolean;
public exportSelectedOnly: boolean;
public saveToVault: boolean;
constructor(
private plugin: ExcalidrawPlugin,
private view: ExcalidrawView,
private file: TFile,
) {
super(plugin.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 = shouldEmbedScene(this.plugin, this.file);
this.exportSelectedOnly = false;
this.saveToVault = true;
this.transparent = !getWithBackground(this.plugin, this.file);
this.saveSettings = false;
}
destroy() {
this.app = null;
this.plugin = null;
this.ea.destroy();
this.ea = null;
this.view = null;
this.file = null;
this.api = null;
this.theme = null;
this.selectedOnlySetting = null;
this.containerEl.remove();
}
onOpen(): void {
this.containerEl.classList.add("excalidraw-release");
this.titleEl.setText(`Export Image`);
this.hasSelectedElements = this.view.getViewSelectedElements().length > 0;
//@ts-ignore
this.selectedOnlySetting.setVisibility(this.hasSelectedElements);
}
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());
})
)
new Setting(this.contentEl)
.setName("Export theme")
.addDropdown(dropdown =>
dropdown
.addOption("light","Light")
.addOption("dark","Dark")
.setValue(this.theme)
.onChange(value => {
this.theme = value;
})
)
new Setting(this.contentEl)
.setName("Background color")
.addDropdown(dropdown =>
dropdown
.addOption("transparent","Transparent")
.addOption("with-color","Use scene background color")
.setValue(this.transparent?"transparent":"with-color")
.onChange(value => {
this.transparent = value === "transparent";
})
)
new Setting(this.contentEl)
.setName("Save or one-time settings?")
.addDropdown(dropdown =>
dropdown
.addOption("save","Save these settings as the preset for this image")
.addOption("one-time","These are one-time settings")
.setValue(this.saveSettings?"save":"one-time")
.onChange(value => {
this.saveSettings = value === "save";
})
)
this.contentEl.createEl("h1",{text:"Export settings"});
new Setting(this.contentEl)
.setName("Embed the Excalidraw scene in the exported file?")
.addDropdown(dropdown =>
dropdown
.addOption("embed","Embed scene")
.addOption("no-embed","Do not embed scene")
.setValue(this.embedScene?"embed":"no-embed")
.onChange(value => {
this.embedScene = value === "embed";
})
)
if(DEVICE.isDesktop) {
new Setting(this.contentEl)
.setName("Where to save the image?")
.addDropdown(dropdown =>
dropdown
.addOption("vault","Save image to your Vault")
.addOption("outside","Export image outside your Vault")
.setValue(this.saveToVault?"vault":"outside")
.onChange(value => {
this.saveToVault = value === "vault";
})
)
}
this.selectedOnlySetting = new Setting(this.contentEl)
.setName("Export entire scene or just selected elements?")
.addDropdown(dropdown =>
dropdown
.addOption("all","Export entire scene")
.addOption("selected","Export selected elements")
.setValue(this.exportSelectedOnly?"selected":"all")
.onChange(value => {
this.exportSelectedOnly = value === "selected";
})
)
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.getScene(this.hasSelectedElements && this.exportSelectedOnly))
: this.view.exportPNG(this.embedScene,this.hasSelectedElements && this.exportSelectedOnly);
this.close();
};
const bSVG = div.createEl("button", { text: "SVG to File", cls: "excalidraw-prompt-button" });
bSVG.onclick = () => {
this.saveToVault
? this.view.saveSVG(this.view.getScene(this.hasSelectedElements && this.exportSelectedOnly))
: this.view.exportSVG(this.embedScene,this.hasSelectedElements && this.exportSelectedOnly);
this.close();
};
const bExcalidraw = div.createEl("button", { text: "Excalidraw", cls: "excalidraw-prompt-button" });
bExcalidraw.onclick = () => {
this.view.exportExcalidraw(this.hasSelectedElements && this.exportSelectedOnly);
this.close();
};
if(DEVICE.isDesktop) {
const bPNGClipboard = div.createEl("button", { text: "PNG to Clipboard", cls: "excalidraw-prompt-button" });
bPNGClipboard.onclick = () => {
this.view.exportPNGToClipboard(this.embedScene, this.hasSelectedElements && this.exportSelectedOnly);
this.close();
};
}
}
}

View File

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

View File

@@ -1,71 +0,0 @@
import { App, FuzzySuggestModal, TFile } from "obsidian";
import { REG_LINKINDEX_INVALIDCHARS } from "../constants/constants";
import { t } from "../lang/helpers";
import ExcalidrawPlugin from "src/main";
import { getLink } from "src/utils/FileUtils";
export class InsertLinkDialog extends FuzzySuggestModal<TFile> {
private addText: Function;
private drawingPath: string;
destroy() {
this.app = null;
this.addText = null;
this.drawingPath = null;
}
constructor(private plugin: ExcalidrawPlugin) {
super(plugin.app);
this.app = plugin.app;
this.limit = 20;
this.setInstructions([
{
command: t("SELECT_FILE"),
purpose: "",
},
]);
this.setPlaceholder(t("SELECT_FILE_TO_LINK"));
this.emptyStateText = t("NO_MATCH");
}
getItems(): any[] {
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/422
return (
this.app.metadataCache
//@ts-ignore
.getLinkSuggestions()
//@ts-ignore
.filter((x) => !x.path.match(REG_LINKINDEX_INVALIDCHARS))
);
}
getItemText(item: any): string {
return item.path + (item.alias ? `|${item.alias}` : "");
}
onChooseItem(item: any): void {
let filepath = item.path;
if (item.file) {
filepath = this.app.metadataCache.fileToLinktext(
item.file,
this.drawingPath,
true,
);
}
const link = getLink(this.plugin,{embed: false, path: filepath, alias: item.alias});
this.addText(getLink(this.plugin,{embed: false, path: filepath, alias: item.alias}), filepath, item.alias);
}
onClose(): void {
window.setTimeout(()=>{
this.addText = null
}); //make sure this happens after onChooseItem runs
super.onClose();
}
public start(drawingPath: string, addText: Function) {
this.addText = addText;
this.drawingPath = drawingPath;
this.open();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,33 @@
//Solution copied from obsidian-kanban: https://github.com/mgmeyers/obsidian-kanban/blob/44118e25661bff9ebfe54f71ae33805dc88ffa53/src/lang/helpers.ts
import { moment } from "obsidian";
import { errorlog } from "src/utils/Utils";
import { LOCALE } from "src/constants/constants";
import en from "./locale/en";
declare const PLUGIN_LANGUAGES: Record<string, string>;
declare var LZString: any;
let locale: Partial<typeof en> | null = null;
function loadLocale(lang: string): Partial<typeof en> {
if(lang === "zh") lang = "zh-cn"; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2247
if (Object.keys(PLUGIN_LANGUAGES).includes(lang)) {
const decompressed = LZString.decompressFromBase64(PLUGIN_LANGUAGES[lang]);
let x = {};
eval(decompressed);
return x;
} else {
return en;
}
}
export function t(str: keyof typeof en): string {
if (!locale) {
locale = loadLocale(LOCALE);
}
return (locale && locale[str]) || en[str];
}
/*
import ar from "./locale/ar";
import cz from "./locale/cz";
import da from "./locale/da";
@@ -51,11 +77,4 @@ const localeMap: { [k: string]: Partial<typeof en> } = {
tr,
"zh-cn": zhCN,
"zh-tw": zhTW,
};
const locale = localeMap[LOCALE];
export function t(str: keyof typeof en): string {
return (locale && locale[str]) || en[str];
}
};*/

View File

@@ -1,12 +1,17 @@
import {
DEVICE,
FRONTMATTER_KEYS,
CJK_FONTS,
} from "src/constants/constants";
import { TAG_AUTOEXPORT, TAG_MDREADINGMODE, TAG_PDFEXPORT } from "src/constants/constSettingsTags";
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/ModifierkeyHelper";
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/modifierkeyHelper";
declare const PLUGIN_VERSION:string;
// English
export default {
// Sugester
SELECT_FILE_TO_INSERT: "Select a file to insert",
// main.ts
CONVERT_URL_TO_FILE: "Save image from URL to local file",
UNZIP_CURRENT_FILE: "Decompress current Excalidraw file",
@@ -25,9 +30,10 @@ export default {
"Script is up to date - Click to reinstall",
OPEN_AS_EXCALIDRAW: "Open as Excalidraw Drawing",
TOGGLE_MODE: "Toggle between Excalidraw and Markdown mode",
DUPLICATE_IMAGE: "Duplicate selected image with a different image ID",
CONVERT_NOTE_TO_EXCALIDRAW: "Convert markdown note to Excalidraw Drawing",
CONVERT_EXCALIDRAW: "Convert *.excalidraw to *.md files",
CREATE_NEW: "Create new drawing",
CREATE_NEW: "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",
@@ -75,6 +81,7 @@ export default {
IMPORT_SVG_CONTEXTMENU: "Convert SVG to strokes - with limitations",
INSERT_MD: "Insert markdown file from vault",
INSERT_PDF: "Insert PDF file from vault",
INSERT_LAST_ACTIVE_PDF_PAGE_AS_IMAGE: "Insert last active PDF page as image",
UNIVERSAL_ADD_FILE: "Insert ANY file",
INSERT_CARD: "Add back-of-note card",
CONVERT_CARD_TO_FILE: "Move back-of-note card to File",
@@ -97,8 +104,14 @@ export default {
RESET_IMG_ASPECT_RATIO: "Reset selected image element aspect ratio",
TEMPORARY_DISABLE_AUTOSAVE: "Disable autosave until next time Obsidian starts (only set this if you know what you are doing)",
TEMPORARY_ENABLE_AUTOSAVE: "Enable autosave",
FONTS_LOADED: "Excalidraw: CJK Fonts loaded",
FONTS_LOAD_ERROR: "Excalidraw: Could not find CJK Fonts in the assets folder\n",
//Prompt.ts
SELECT_LINK_TO_OPEN: "Select a link to open",
//ExcalidrawView.ts
ERROR_CANT_READ_FILEPATH: "Error, can't read file path. Importing file instead",
NO_SEARCH_RESULT: "Didn't find a matching element in the drawing",
FORCE_SAVE_ABORTED: "Force Save aborted because saving is in progress",
LINKLIST_SECOND_ORDER_LINK: "Second Order Link",
@@ -128,7 +141,10 @@ export default {
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 an ImageElement, or select a TextElement that contains an internal or external link.\n",
"Select an element that contains an internal or external link.\n",
LINEAR_ELEMENT_LINK_CLICK_ERROR:
"Arrow- and Line-Element links cannot be navigated by " + labelCTRL() + " + CLICKing on the element because that also activates the line editor.\n" +
"Use the right-click context menu to open the link, or click the link indicator in the top right corner of the element.\n",
FILENAME_INVALID_CHARS:
'File name cannot contain any of the following characters: * " \\ < > : | ? #',
FORCE_SAVE:
@@ -184,7 +200,7 @@ export default {
BASIC_HEAD: "Basic",
BASIC_DESC: `In the "Basic" settings, you can configure options such as displaying release notes after updates, receiving plugin update notifications, setting the default location for new drawings, specifying the Excalidraw folder for embedding drawings into active documents, defining an Excalidraw template file, and designating an Excalidraw Automate script folder for managing automation scripts.`,
FOLDER_NAME: "Excalidraw folder",
FOLDER_NAME: "Excalidraw folder (CAsE sEnsITive!)",
FOLDER_DESC:
"Default location for new drawings. If empty, drawings will be created in the Vault root.",
CROP_PREFIX_NAME: "Crop file prefix",
@@ -198,10 +214,10 @@ export default {
ANNOTATE_PRESERVE_SIZE_NAME: "Preserve image size when annotating",
ANNOTATE_PRESERVE_SIZE_DESC:
"When annotating an image in markdown the replacment image link will include the width of the original image.",
CROP_FOLDER_NAME: "Crop file folder",
CROP_FOLDER_NAME: "Crop file folder (CaSE senSItive!)",
CROP_FOLDER_DESC:
"Default location for new drawings created when cropping an image. If empty, drawings will be created following the Vault attachments settings.",
ANNOTATE_FOLDER_NAME: "Image annotation file folder",
ANNOTATE_FOLDER_NAME: "Image annotation file folder (CaSe SeNSitIVe!)",
ANNOTATE_FOLDER_DESC:
"Default location for new drawings created when annotating an image. If empty, drawings will be created following the Vault attachments settings.",
FOLDER_EMBED_NAME:
@@ -210,7 +226,7 @@ export default {
"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><u>Toggle ON:</u></b> Use Excalidraw folder<br><b><u>Toggle OFF:</u></b> Use the attachments folder defined in Obsidian settings.",
TEMPLATE_NAME: "Excalidraw template file or folder",
TEMPLATE_NAME: "Excalidraw template file or folder (caSe SenSiTive!)",
TEMPLATE_DESC:
"Full filepath or folderpath to the Excalidraw template.<br>" +
"<b>Template File:</b>E.g.: If your template is in the default Excalidraw folder and its name is " +
@@ -316,6 +332,11 @@ FILENAME_HEAD: "Filename",
"i.e. you are not using Excalidraw markdown files.<br><b><u>Toggle ON:</u></b> filename ends with .excalidraw.md<br><b><u>Toggle OFF:</u></b> filename ends with .md",
DISPLAY_HEAD: "Excalidraw appearance and behavior",
DISPLAY_DESC: "In the 'appearance and behavior' section of Excalidraw Settings, you can fine-tune how Excalidraw appears and behaves. This includes options for dynamic styling, left-handed mode, matching Excalidraw and Obsidian themes, default modes, and more.",
OVERRIDE_OBSIDIAN_FONT_SIZE_NAME: "Limit Obsidian Font Size to Editor Text",
OVERRIDE_OBSIDIAN_FONT_SIZE_DESC:
"Obsidian's custom font size setting affects the entire interface, including Excalidraw and themes that depend on the default font size. " +
"Enabling this option restricts font size changes to editor text, which will improve the look of Excalidraw. " +
"If parts of the UI look incorrect after enabling, try turning this setting off.",
DYNAMICSTYLE_NAME: "Dynamic styling",
DYNAMICSTYLE_DESC:
"Change Excalidraw UI colors to match the canvas color",
@@ -349,6 +370,7 @@ FILENAME_HEAD: "Filename",
DEFAULT_PEN_MODE_DESC:
"Should pen mode be automatically enabled when opening Excalidraw?",
DISABLE_DOUBLE_TAP_ERASER_NAME: "Enable double-tap eraser in pen mode",
DISABLE_SINGLE_FINGER_PANNING_NAME: "Enable single-finger panning in pen mode",
SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_NAME: "Show (+) crosshair in pen mode",
SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_DESC:
"Show crosshair in pen mode when using the freedraw tool. <b><u>Toggle ON:</u></b> SHOW <b><u>Toggle OFF:</u></b> HIDE<br>"+
@@ -364,13 +386,14 @@ FILENAME_HEAD: "Filename",
"This setting will not affect the display of the drawing when you are in Excalidraw mode or when you embed the drawing into a markdown document or when rendering hover preview.<br><ul>" +
"<li>See other related setting for <a href='#"+TAG_PDFEXPORT+"'>PDF Export</a> under 'Embedding and Exporting' further below.</li></ul><br>" +
"You must close the active excalidraw/markdown file and reopen it for this change to take effect.",
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_NAME: "Render the file as an image when exporting an Excalidraw file to PDF",
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_NAME: "Render Excalidraw as Image in Obsidian PDF Export",
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_DESC:
"This setting controls the behavior of Excalidraw when exporting an Excalidraw file to PDF in markdown view mode using Obsidian's <b>Export to PDF</b> feature.<br>" +
"<ul><li>When <b>enabled</b> the PDF will show the Excalidraw drawing only;</li>" +
"<li>When <b>disabled</b> the PDF will show the markdown side of the document.</li></ul>" +
"See the other related setting for <a href='#"+TAG_MDREADINGMODE+"'>Markdown Reading Mode</a> under 'Appearnace and Behavior' further above.<br>" +
"⚠️ Note, you must close the active excalidraw/markdown file and reopen for this change to take effect. ⚠️",
"This setting controls how Excalidraw files are exported to PDF using Obsidian's built-in <b>Export to PDF</b> feature.<br>" +
"<ul><li><b>Enabled:</b> The PDF will include the Excalidraw drawing as an image.</li>" +
"<li><b>Disabled:</b> The PDF will include the markdown content as text.</li></ul>" +
"Note: This setting does not affect the PDF export feature within Excalidraw itself.<br>" +
"See the other related setting for <a href='#"+TAG_MDREADINGMODE+"'>Markdown Reading Mode</a> under 'Appearance and Behavior' further above.<br>" +
"⚠️ You must close and reopen the Excalidraw/markdown file for changes to take effect. ⚠️",
HOTKEY_OVERRIDE_HEAD: "Hotkey overrides",
HOTKEY_OVERRIDE_DESC: `Some of the Excalidraw hotkeys such as <code>${labelCTRL()}+Enter</code> to edit text or <code>${labelCTRL()}+K</code> to create an element link ` +
"conflict with Obsidian hotkey settings. The hotkey combinations you add below will override Obsidian's hotkey settings while useing Excalidraw, thus " +
@@ -395,6 +418,15 @@ FILENAME_HEAD: "Filename",
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%).",
PEN_HEAD: "Pen",
GRID_HEAD: "Grid",
GRID_DYNAMIC_COLOR_NAME: "Dynamic grid color",
GRID_DYNAMIC_COLOR_DESC:
"<b><u>Toggle ON:</u></b>Change grid color to match the canvas color<br><b><u>Toggle OFF:</u></b>Use the color below as the grid color",
GRID_COLOR_NAME: "Grid color",
GRID_OPACITY_NAME: "Grid opacity",
GRID_OPACITY_DESC: "Grid opacity will also control the opacity of the binding box when binding an arrow to an element.<br>" +
"Set the opacity of the grid. 0 is transparent, 100 is opaque.",
LASER_HEAD: "Laser pointer",
LASER_COLOR: "Laser pointer color",
LASER_DECAY_TIME_NAME: "Laser pointer decay time",
@@ -420,6 +452,7 @@ FILENAME_HEAD: "Filename",
LONG_PRESS_DESKTOP_DESC: "Long press delay in milliseconds to open an Excalidraw Drawing embedded in a Markdown file. ",
LONG_PRESS_MOBILE_NAME: "Long press to open mobile",
LONG_PRESS_MOBILE_DESC: "Long press delay in milliseconds to open an Excalidraw Drawing embedded in a Markdown file. ",
DOUBLE_CLICK_LINK_OPEN_VIEW_MODE: "Allow double-click to open links in view mode",
FOCUS_ON_EXISTING_TAB_NAME: "Focus on Existing Tab",
FOCUS_ON_EXISTING_TAB_DESC: "When opening a link, Excalidraw will focus on the existing tab if the file is already open. " +
@@ -548,7 +581,11 @@ FILENAME_HEAD: "Filename",
EMBED_CANVAS_DESC:
"Hide canvas node border and background when embedding an Excalidraw drawing to Canvas. " +
"Note that for a full transparent background for your image, you will still need to configure Excalidraw to export images with transparent background.",
EMBED_CACHING: "Image caching",
EMBED_CACHING: "Image caching and rendering optimization",
RENDERING_CONCURRENCY_NAME: "Image rendering concurrency",
RENDERING_CONCURRENCY_DESC:
"Number of parallel workers to use for image rendering. Increasing this number will speed up the rendering process, but may slow down the rest of the system. " +
"The default value is 3. You can increase this number if you have a powerful system.",
EXPORT_SUBHEAD: "Export Settings",
EMBED_SIZING: "Image sizing",
EMBED_THEME_BACKGROUND: "Image theme and background color",
@@ -625,6 +662,7 @@ FILENAME_HEAD: "Filename",
EXPORT_EMBED_SCENE_DESC:
"Embed Excalidraw scene in exported image. Can be overridden at a file level by adding the <code>excalidraw-export-embed-scene: true/false<code> frontmatter key. " +
"The setting only takes effect the next time you (re)open drawings.",
PDF_EXPORT_SETTINGS: "PDF Export Settings",
EXPORT_HEAD: "Auto-export Settings",
EXPORT_SYNC_NAME:
"Keep the .SVG and/or .PNG filenames in sync with the drawing file",
@@ -653,7 +691,7 @@ FILENAME_HEAD: "Filename",
"Use this setting if for good reasons you have decided to ignore my recommendation and configured linting of Excalidraw files.<br> " +
"The <code>## Text Elements</code> section is sensitive to empty lines. A common linting approach is to add an empty line after section headings. In case of Excalidraw this will break/change the first text element in your drawing. " +
"To overcome this, you can enable this setting. When enabled, Excalidraw will add a dummy element to the beginning of <code>## Text Elements</code> that the linter can safely modify." ,
PRESERVE_TEXT_AFTER_DRAWING_NAME: "Zotero compatibility",
PRESERVE_TEXT_AFTER_DRAWING_NAME: "Zotero and Footnotes compatibility",
PRESERVE_TEXT_AFTER_DRAWING_DESC: "Preserve text after the ## Drawing section of the markdown file. This may have a very slight performance impact when saving very large drawings.",
DEBUGMODE_NAME: "Enable debug messages",
DEBUGMODE_DESC: "I recommend restarting Obsidian after enabling/disabling this setting. This enable debug messages in the console. This is useful for troubleshooting issues. " +
@@ -684,6 +722,8 @@ FILENAME_HEAD: "Filename",
"restart Obsidian after closing settings, for this change to take effect.",
LATEX_DEFAULT_NAME: "Default LaTeX formula for new equations",
LATEX_DEFAULT_DESC: "Leave empty if you don't want a default formula. You can add default formatting here such as <code>\\color{white}</code>.",
LATEX_PREAMBLE_NAME: "LaTeX preamble file (CasE SEnSiTivE!)",
LATEX_PREAMBLE_DESC: "Full filepath to the preamble file, leave empty for default. If the file doesn't exist this option will be ignored.<br><strong>Important:</strong> Requires obsidian reload after change to take effect!",
NONSTANDARD_HEAD: "Non-Excalidraw.com supported features",
NONSTANDARD_DESC: `These settings in the "Non-Excalidraw.com Supported Features" section provide customization options beyond the default Excalidraw.com features. These features are not available on excalidraw.com. When exporting the drawing to Excalidraw.com these features will appear different.
You can configure the number of custom pens displayed next to the Obsidian Menu on the canvas, allowing you to choose from a range of options. Additionally, you can enable a local font option, which adds a local font to the list of fonts on the element properties panel for text elements. `,
@@ -736,6 +776,8 @@ FILENAME_HEAD: "Filename",
"Enabling this feature simplifies the use of Excalidraw front matter properties, allowing you to leverage many powerful settings. If you prefer not to load these properties automatically, " +
"you can disable this feature, but you will need to manually remove any unwanted properties from the suggester. " +
"Note that turning on this setting requires restarting the plugin as properties are loaded at startup.",
FONTS_HEAD: "Fonts",
FONTS_DESC: "Configure local fontfaces and downloaded CJK fonts for Excalidraw.",
CUSTOM_FONT_HEAD: "Local font",
ENABLE_FOURTH_FONT_NAME: "Enable local font option",
ENABLE_FOURTH_FONT_DESC:
@@ -749,6 +791,20 @@ FILENAME_HEAD: "Filename",
"If no file is selected, Excalidraw will default to the Virgil font. " +
"For optimal performance, it is recommended to use a .woff2 file, as Excalidraw will encode only the necessary glyphs when exporting images to SVG. " +
"Other font formats will embed the entire font in the exported file, potentially resulting in significantly larger file sizes.",
OFFLINE_CJK_NAME: "Offline CJK font support",
OFFLINE_CJK_DESC:
`<strong>Changes you make here will only take effect after restarting Obsidian.</strong><br>
Excalidraw.com offers handwritten CJK fonts. By default these fonts are not included in the plugin locally, but are served from the Internet.
If you prefer to keep Excalidraw fully local, allowing it to work without Internet access you can download the necessary <a href="https://github.com/zsviczian/obsidian-excalidraw-plugin/raw/refs/heads/master/assets/excalidraw-fonts.zip" target="_blank">font files from GitHub</a>.
After downloading, unzip the contents into a folder within your Vault.<br>
Pre-loading fonts will impact startup performance. For this reason you can select which fonts to load.`,
CJK_ASSETS_FOLDER_NAME: "CJK Font Folder (cAsE sENsiTIvE!)",
CJK_ASSETS_FOLDER_DESC: `You can set the location of the CJK fonts folder here. For example, you may choose to place it under <code>Excalidraw/CJK Fonts</code>.<br><br>
<strong>Important:</strong> Do not set this folder to the Vault root! Do not put other fonts in this folder.<br><br>
<strong>Note:</strong> If you're using Obsidian Sync and want to synchronize these font files across your devices, ensure that Obsidian Sync is set to synchronize "All other file types".`,
LOAD_CHINESE_FONTS_NAME: "Load Chinese fonts from file at startup",
LOAD_JAPANESE_FONTS_NAME: "Load Japanese fonts from file at startup",
LOAD_KOREAN_FONTS_NAME: "Load Korean fonts frome file at startup",
SCRIPT_SETTINGS_HEAD: "Settings for installed Scripts",
SCRIPT_SETTINGS_DESC: "Some of the Excalidraw Automate Scripts include settings. Settings are organized by script. Settings will only become visible in this list after you have executed the newly downloaded script once.",
TASKBONE_HEAD: "Taskbone Optical Character Recogntion",
@@ -805,6 +861,35 @@ FILENAME_HEAD: "Filename",
//ExcalidrawData.ts
LOAD_FROM_BACKUP: "Excalidraw file was corrupted. Loading from backup file.",
FONT_LOAD_SLOW: "Loading Fonts...\n\n This is taking longer than expected. If this delay occurs regulary then you may download the fonts locally to your Vault. \n\n" +
"(click=dismiss, right-click=Info)",
FONT_INFO_TITLE: "Starting v2.5.3 fonts load from the Internet",
FONT_INFO_DETAILED: `
<p>
To improve Obsidian's startup time and manage the large <strong>CJK font family</strong>,
I've moved the CJK fonts out of the plugin's <code>main.js</code>. CJK fonts will be loaded from the internet by default.
This typically shouldn't cause issues as Obsidian caches these files after first use.
</p>
<p>
If you prefer to keep Obsidian 100% local or experience performance issues, you can download the font assets.
</p>
<h3>Instructions:</h3>
<ol>
<li>Download the fonts from <a href="https://github.com/zsviczian/obsidian-excalidraw-plugin/raw/refs/heads/master/assets/excalidraw-fonts.zip">GitHub</a>.</li>
<li>Unzip and copy files into a Vault folder (default: <code>Excalidraw/${CJK_FONTS}</code>; folder names are cAse-senSITive).</li>
<li><mark>DO NOT</mark> set this folder to the Vault root or mix with other local fonts.</li>
</ol>
<h3>For Obsidian Sync Users:</h3>
<p>
Ensure Obsidian Sync is set to synchronize "All other file types" or download and unzip the file on all devices.
</p>
<h3>Note:</h3>
<p>
If you find this process cumbersome, please submit a feature request to Obsidian.md for supporting assets in the plugin folder.
Currently, only a single <code>main.js</code> is supported, which leads to large files and slow startup times for complex plugins like Excalidraw.
I apologize for the inconvenience.
</p>
`,
//ObsidianMenu.tsx
GOTO_FULLSCREEN: "Goto fullscreen mode",
@@ -835,6 +920,8 @@ FILENAME_HEAD: "Filename",
ES_YOUTUBE_START_INVALID: "The YouTube Start Time is invalid. Please check the format and try again",
ES_FILENAME_VISIBLE: "Filename Visible",
ES_BACKGROUND_HEAD: "Embedded note background color",
ES_BACKGROUND_DESC_INFO: "Click here for more info on colors",
ES_BACKGROUND_DESC_DETAIL: "Background color affects only the preview mode of the markdown embeddable. When editing, it follows the Obsidian light/dark theme as set for the scene (via document property) or in plugin settings. The background color has two layers: the element background color (lower layer) and a color on top (upper layer). Selecting 'Match Element Background' means both layers follow the element color. Selecting 'Match Canvas' or a specific background color keeps the element background layer. Setting opacity (e.g., 50%) mixes the canvas or selected color with the element background color. To remove the element background layer, set the element color to transparent in Excalidraw's element properties editor. This makes only the upper layer effective.",
ES_BACKGROUND_MATCH_ELEMENT: "Match Element Background Color",
ES_BACKGROUND_MATCH_CANVAS: "Match Canvas Background Color",
ES_BACKGROUND_COLOR: "Background Color",
@@ -894,4 +981,118 @@ FILENAME_HEAD: "Filename",
IPM_GROUP_PAGES_DESC: "This will group all pages into a single group. This is recommended if you are locking the pages after import, because the group will be easier to unlock later rather than unlocking one by one.",
IPM_SELECT_PDF: "Please select a PDF file",
//Utils.ts
UPDATE_AVAILABLE: `A newer version of Excalidraw is available in Community Plugins.\n\nYou are using ${PLUGIN_VERSION}.\nThe latest is`,
ERROR_PNG_TOO_LARGE: "Error exporting PNG - PNG too large, try a smaller resolution",
//modifierkeyHelper.ts
// WebBrowserDragAction
WEB_DRAG_IMPORT_IMAGE: "Import Image to Vault",
WEB_DRAG_IMAGE_URL: "Insert Image or YouTube Thumbnail with URL",
WEB_DRAG_LINK: "Insert Link",
WEB_DRAG_EMBEDDABLE: "Insert Interactive-Frame",
// LocalFileDragAction
LOCAL_DRAG_IMPORT: "Import external file or reuse existing file if path is from the Vault",
LOCAL_DRAG_IMAGE: "Insert Image: with local URI or internal-link if from Vault",
LOCAL_DRAG_LINK: "Insert Link: local URI or internal-link if from Vault",
LOCAL_DRAG_EMBEDDABLE: "Insert Interactive-Frame: local URI or internal-link if from Vault",
// InternalDragAction
INTERNAL_DRAG_IMAGE: "Insert Image",
INTERNAL_DRAG_IMAGE_FULL: "Insert Image @100%",
INTERNAL_DRAG_LINK: "Insert Link",
INTERNAL_DRAG_EMBEDDABLE: "Insert Interactive-Frame",
// LinkClickAction
LINK_CLICK_ACTIVE: "Open in current active window",
LINK_CLICK_NEW_PANE: "Open in a new adjacent window",
LINK_CLICK_POPOUT: "Open in a popout window",
LINK_CLICK_NEW_TAB: "Open in a new tab",
LINK_CLICK_MD_PROPS: "Show the Markdown image-properties dialog (only relevant if you have embedded a markdown document as an image)",
//ExportDialog
// Dialog and tabs
EXPORTDIALOG_TITLE: "Export Drawing",
EXPORTDIALOG_TAB_IMAGE: "Image",
EXPORTDIALOG_TAB_PDF: "PDF",
// Settings persistence
EXPORTDIALOG_SAVE_SETTINGS: "Save image settings to file doc.properties?",
EXPORTDIALOG_SAVE_SETTINGS_SAVE: "Save as preset",
EXPORTDIALOG_SAVE_SETTINGS_ONETIME: "One-time use",
// Image settings
EXPORTDIALOG_IMAGE_SETTINGS: "Image",
EXPORTDIALOG_IMAGE_DESC: "PNG supports transparency. External files can include Excalidraw scene data.",
EXPORTDIALOG_PADDING: "Padding",
EXPORTDIALOG_SCALE: "Scale",
EXPORTDIALOG_CURRENT_PADDING: "Current padding:",
EXPORTDIALOG_SIZE_DESC: "Scale affects output size",
EXPORTDIALOG_SCALE_VALUE: "Scale:",
EXPORTDIALOG_IMAGE_SIZE: "Size:",
// Theme and background
EXPORTDIALOG_EXPORT_THEME: "Theme",
EXPORTDIALOG_THEME_LIGHT: "Light",
EXPORTDIALOG_THEME_DARK: "Dark",
EXPORTDIALOG_BACKGROUND: "Background",
EXPORTDIALOG_BACKGROUND_TRANSPARENT: "Transparent",
EXPORTDIALOG_BACKGROUND_USE_COLOR: "Use scene color",
// Selection
EXPORTDIALOG_SELECTED_ELEMENTS: "Export",
EXPORTDIALOG_SELECTED_ALL: "Entire scene",
EXPORTDIALOG_SELECTED_SELECTED: "Selection only",
// Export options
EXPORTDIALOG_EMBED_SCENE: "Include scene data?",
EXPORTDIALOG_EMBED_YES: "Yes",
EXPORTDIALOG_EMBED_NO: "No",
// PDF settings
EXPORTDIALOG_PDF_SETTINGS: "PDF",
EXPORTDIALOG_PAGE_SIZE: "Size",
EXPORTDIALOG_PAGE_ORIENTATION: "Orientation",
EXPORTDIALOG_ORIENTATION_PORTRAIT: "Portrait",
EXPORTDIALOG_ORIENTATION_LANDSCAPE: "Landscape",
EXPORTDIALOG_PDF_FIT_TO_PAGE: "Page Fitting",
EXPORTDIALOG_PDF_FIT_OPTION: "Fit to page",
EXPORTDIALOG_PDF_FIT_2_OPTION: "Fit to max 2-pages",
EXPORTDIALOG_PDF_FIT_4_OPTION: "Fit to max 4-pages",
EXPORTDIALOG_PDF_FIT_6_OPTION: "Fit to max 6-pages",
EXPORTDIALOG_PDF_FIT_8_OPTION: "Fit to max 8-pages",
EXPORTDIALOG_PDF_FIT_12_OPTION: "Fit to max 12-pages",
EXPORTDIALOG_PDF_FIT_16_OPTION: "Fit to max 16-pages",
EXPORTDIALOG_PDF_SCALE_OPTION: "Use image scale (may span multiple pages)",
EXPORTDIALOG_PDF_PAPER_COLOR: "Paper Color",
EXPORTDIALOG_PDF_PAPER_WHITE: "White",
EXPORTDIALOG_PDF_PAPER_SCENE: "Use scene color",
EXPORTDIALOG_PDF_PAPER_CUSTOM: "Custom color",
EXPORTDIALOG_PDF_ALIGNMENT: "Position on Page",
EXPORTDIALOG_PDF_ALIGN_CENTER: "Center",
EXPORTDIALOG_PDF_ALIGN_CENTER_LEFT: "Center Left",
EXPORTDIALOG_PDF_ALIGN_CENTER_RIGHT: "Center Right",
EXPORTDIALOG_PDF_ALIGN_TOP_LEFT: "Top Left",
EXPORTDIALOG_PDF_ALIGN_TOP_CENTER: "Top Center",
EXPORTDIALOG_PDF_ALIGN_TOP_RIGHT: "Top Right",
EXPORTDIALOG_PDF_ALIGN_BOTTOM_LEFT: "Bottom Left",
EXPORTDIALOG_PDF_ALIGN_BOTTOM_CENTER: "Bottom Center",
EXPORTDIALOG_PDF_ALIGN_BOTTOM_RIGHT: "Bottom Right",
EXPORTDIALOG_PDF_MARGIN: "Margin",
EXPORTDIALOG_PDF_MARGIN_NONE: "None",
EXPORTDIALOG_PDF_MARGIN_TINY: "Small",
EXPORTDIALOG_PDF_MARGIN_NORMAL: "Normal",
EXPORTDIALOG_SAVE_PDF_SETTINGS: "Save PDF settings",
EXPORTDIALOG_SAVE_CONFIRMATION: "PDF config saved to plugin settings as default",
// Buttons
EXPORTDIALOG_PNGTOFILE : "Export PNG",
EXPORTDIALOG_SVGTOFILE : "Export SVG",
EXPORTDIALOG_PNGTOVAULT : "PNG to Vault",
EXPORTDIALOG_SVGTOVAULT : "SVG to Vault",
EXPORTDIALOG_EXCALIDRAW: "Excalidraw",
EXPORTDIALOG_PNGTOCLIPBOARD : "PNG to Clipboard",
EXPORTDIALOG_SVGTOCLIPBOARD : "SVG to Clipboard",
EXPORTDIALOG_PDF: "Export PDF",
EXPORTDIALOG_PDF_PROGRESS_NOTICE: "Exporting PDF. If this image is large, it may take a while.",
EXPORTDIALOG_PDF_PROGRESS_DONE: "Export complete",
EXPORTDIALOG_PDF_PROGRESS_ERROR: "Error exporting PDF, check developer console for details",
//exportUtils.ts
PDF_EXPORT_DESKTOP_ONLY: "PDF export is only available on desktop",
};

View File

@@ -1,3 +1,856 @@
// русский
import { DEVICE, FRONTMATTER_KEYS, CJK_FONTS } from "src/constants/constants";
import { TAG_AUTOEXPORT, TAG_MDREADINGMODE, TAG_PDFEXPORT } from "src/constants/constSettingsTags";
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/modifierkeyHelper";
export default {};
// русский
export default {
// main.ts
CONVERT_URL_TO_FILE: "Сохранить изображение из URL в локальный файл",
UNZIP_CURRENT_FILE: "Распаковать текущий файл Excalidraw",
ZIP_CURRENT_FILE: "Сжать текущий файл Excalidraw",
PUBLISH_SVG_CHECK: "Obsidian Publish: Поиск устаревших экспортированных SVG и PNG-файлов",
EMBEDDABLE_PROPERTIES: "Свойства встраиваемых элементов",
EMBEDDABLE_RELATIVE_ZOOM: "Масштабирование выбранных встраиваемых элементов до 100% относительно текущего масштаба холста",
OPEN_IMAGE_SOURCE: "Открыть чертеж Excalidraw",
INSTALL_SCRIPT: "Установите скрипт",
UPDATE_SCRIPT: "Доступно обновление - нажмите для установки",
CHECKING_SCRIPT: "Проверка на наличие новой версии - Нажмите для переустановки",
UNABLETOCHECK_SCRIPT: "Проверка обновления не удалась - Нажмите, чтобы переустановить",
UPTODATE_SCRIPT: "Скрипт обновлен - Нажмите для переустановки",
OPEN_AS_EXCALIDRAW: "Открыть как рисунок Excalidraw",
TOGGLE_MODE: "Переключение между режимами Excalidraw и Markdown",
CONVERT_NOTE_TO_EXCALIDRAW: "Конвертировать заметку в формате Markdown в Excalidraw Drawing",
CONVERT_EXCALIDRAW: "Преобразование файлов *.excalidraw в файлы *.md",
CREATE_NEW: "Создать новый чертеж",
CONVERT_FILE_KEEP_EXT: "*.excalidraw => *.excalidraw.md",
CONVERT_FILE_REPLACE_EXT: "*.excalidraw => *.md (совместимость с Logseq)",
DOWNLOAD_LIBRARY: "Экспорт библиотеки трафаретов в файл *.excalidrawlib",
OPEN_EXISTING_NEW_PANE: "Открыть существующий чертеж - В НОВОЙ ПАНЕЛИ",
OPEN_EXISTING_ACTIVE_PANE: "Открыть существующий чертеж - В ТЕКУЩЕЙ АКТИВНОЙ ПАНЕЛИ",
TRANSCLUDE: "Вставить чертеж",
TRANSCLUDE_MOST_RECENT: "Вставка последнего отредактированного рисунка",
TOGGLE_LEFTHANDED_MODE: "Переключить левосторонний режим",
TOGGLE_SPLASHSCREEN: "Показывать заставку в новых чертежах",
FLIP_IMAGE: "Открыть фоновым рисуноком выбранное изображения excalidraw",
NEW_IN_NEW_PANE: "Создать новый рисунок - В СОСЕДНЕМ ОКНЕ",
NEW_IN_NEW_TAB: "Создать новый рисунок - В НОВОЙ ТАБЛИЦЕ",
NEW_IN_ACTIVE_PANE: "Создать новый рисунок - В ТЕКУЩЕМ АКТИВНОМ ОКНЕ",
NEW_IN_POPOUT_WINDOW: "Создать новый рисунок - В ОТКРЫВАЮЩЕМСЯ ОКНЕ",
NEW_IN_NEW_PANE_EMBED: "Создание нового рисунка - В СОСЕДНЕМ ОКНЕ - и вставка в активный документ",
NEW_IN_NEW_TAB_EMBED: "Создать новый чертеж - В НОВОЙ ТАБЛИЦЕ - и вставить в активный документ",
NEW_IN_ACTIVE_PANE_EMBED: "Создать новый рисунок - В ТЕКУЩЕМ АКТИВНОМ ОКНЕ - и вставить в активный документ",
NEW_IN_POPOUT_WINDOW_EMBED: "Создать новый рисунок - В ОТКРЫВАЮЩЕМСЯ ОКНЕ - и вставить в активный документ",
TOGGLE_LOCK: "Переключение текстового элемента между режимами редактирования RAW (без обработки) и PREVIEW (просмотр)",
DELETE_FILE: "Удалить выбранное изображение или файл Markdown из Obsidian хранилища",
COPY_ELEMENT_LINK: "Скопировать [[ссылку]] для выбранного элемента(ов)",
COPY_DRAWING_LINK: "Скопировать ![[ссылку на вставку]] для этого рисунка",
INSERT_LINK_TO_ELEMENT: `Копирование [[ссылка]] для выбранного элемента в буфер обмена. ${labelCTRL()}+CLICK для копирования ссылки 'group='. ${labelSHIFT()}+CLICK для копирования ссылки 'area='.`,
INSERT_LINK_TO_ELEMENT_GROUP: "Скопируйте 'group=' ![[ссылка]] для выбранного элемента в буфер обмена.",
INSERT_LINK_TO_ELEMENT_AREA: "Скопировать 'area=' ![[ссылка]] для выбранного элемента в буфер обмена.",
INSERT_LINK_TO_ELEMENT_FRAME: "Скопировать 'frame=' ![[ссылка]] для выбранного элемента в буфер обмена.",
INSERT_LINK_TO_ELEMENT_FRAME_CLIPPED: "Скопировать 'clippedframe=' ![[ссылка]] для выбранного элемента в буфер обмена.",
INSERT_LINK_TO_ELEMENT_NORMAL: "Скопировать [[ссылка]] для выбранного элемента в буфер обмена.",
INSERT_LINK_TO_ELEMENT_ERROR: "Выбор отдельного элемента в сцене",
INSERT_LINK_TO_ELEMENT_READY: "Ссылка ГОТОВА и доступна в буфере обмена",
INSERT_LINK: "Вставить ссылку на файл",
INSERT_COMMAND: "Вставить команду Obsidian в качестве ссылки",
INSERT_IMAGE: "Вставить изображение или рисунок Excalidraw из вашего хранилища",
IMPORT_SVG: "Импорт SVG-файла в виде штрихов Excalidraw (поддержка SVG ограничена, TEXT в настоящее время не поддерживается)",
IMPORT_SVG_CONTEXTMENU: "Преобразование SVG в штрихи - с ограничениями",
INSERT_MD: "Вставка файла markdown из хранилища",
INSERT_PDF: "Вставить PDF-файл из хранилища",
UNIVERSAL_ADD_FILE: "Вставка ЛЮБОГО файла",
INSERT_CARD: "Добавить сноски",
CONVERT_CARD_TO_FILE: "Переместить сноску в файл",
ERROR_TRY_AGAIN: "Пожалуйста, попробуйте еще раз.",
PASTE_CODEBLOCK: "Вставить блок кода",
INSERT_LATEX: `Вставьте формулу LaTeX (например, \\\binom{n}{k} = \\\frac{n!}{k!(n-k)!}).`,
ENTER_LATEX: "Введите правильное выражение LaTeX",
READ_RELEASE_NOTES: "Прочитать последние заметки о выпуске",
RUN_OCR: "OCR полного чертежа: Захват текста из freedraw + изображения в буфер обмена и doc.props",
RERUN_OCR: "Повторный запуск полного чертежа OCR: Захват текста из freedraw + изображения в буфер обмена и doc.props",
RUN_OCR_ELEMENTS: "OCR выделенных элементов: Захват текста из freedraw + изображения в буфер обмена",
TRAY_MODE: "Переключение панели свойств в трей-режим",
SEARCH: "Поиск текста на чертеже",
CROP_PAGE: "Обрезка и маскирование выделенной страницы",
CROP_IMAGE: "Обрезка и маскирование изображения",
ANNOTATE_IMAGE : "Аннотирование изображения в Excalidraw",
INSERT_ACTIVE_PDF_PAGE_AS_IMAGE: "Вставка активной страницы PDF в качестве изображения",
RESET_IMG_TO_100: "Установить размер выбранного элемента изображения на 100% от исходного",
RESET_IMG_ASPECT_RATIO: "Сбросить соотношение сторон выбранного элемента изображения",
TEMPORARY_DISABLE_AUTOSAVE: "Отключить автосохранение до следующего запуска Obsidian (устанавливайте этот параметр, только если вы знаете, что делаете)",
TEMPORARY_ENABLE_AUTOSAVE: "Включить автосохранение",
//ExcalidrawView.ts
NO_SEARCH_RESULT: "Не удалось найти подходящий элемент на чертеже",
FORCE_SAVE_ABORTED: "Принудительное сохранение прервано, поскольку идет процесс сохранения",
LINKLIST_SECOND_ORDER_LINK: "Ссылка второго порядка",
MARKDOWN_EMBED_CUSTOMIZE_LINK_PROMPT_TITLE: "Настройка ссылки на встроенный файл",
MARKDOWN_EMBED_CUSTOMIZE_LINK_PROMPT: "Не добавляйте [[квадратные скобки]] вокруг имени файла! <br>" +
"При редактировании ссылок на изображения в формате markdown-страниц следуйте этому формату: <mark>filename#^blockref|WIDTHxMAXHEIGHT</mark><br>" +
"Вы можете привязать изображения Excalidraw к 100% их размера, добавив <code>|100%</code> в конец ссылки.<br>" +
"Вы можете изменить страницу PDF, изменив <code>#page=1</code> на <code>#page=2</code> и т.д.<br>" +
"Значения обрезки прямоугольника PDF: <code>left, bottom, right, top</code>. Например: <code>#rect=0,0,500,500</code><br>",
FRAME_CLIPPING_ENABLED: "Рендеринг кадров: Включено",
FRAME_CLIPPING_DISABLED: "Рендеринг кадров: Отключено",
ARROW_BINDING_INVERSE_MODE: "Инвертированный режим: Привязка стрелок по умолчанию теперь отключена. Используйте CTRL/CMD, чтобы временно включить привязку, когда это необходимо.",
ARROW_BINDING_NORMAL_MODE: "Обычный режим: Привязка стрелок теперь включена. Используйте CTRL/CMD, чтобы временно отключить привязку при необходимости.",
EXPORT_FILENAME_PROMPT: "Пожалуйста, укажите имя файла",
EXPORT_FILENAME_PROMPT_PLACEHOLDER: "имя файла, оставьте пустым, чтобы отменить действие",
WARNING_SERIOUS_ERROR: "ПРЕДУПРЕЖДЕНИЕ: Excalidraw столкнулся с неизвестной проблемой!\n\n" +
"Есть риск, что последние изменения не будут сохранены.\n\n" +
"На всякий случай...\n" +
"1) Выберите рисунок с помощью CTRL/CMD+A и создайте копию с помощью CTRL/CMD+C.\n" +
"2) Затем создайте пустой чертеж в новой панели, нажав CTRL/CMD+кнопку ленты Excalidraw,\n" +
"3) и вставьте свою работу в новый документ с помощью CTRL/CMD+V.",
ARIA_LABEL_TRAY_MODE: "Трей-Режим предлагает альтернативный, более просторный холст",
MASK_FILE_NOTICE: "Это файл маски. Он используется для кадрирования изображений и маскирования частей изображения. Нажмите и удерживайте уведомление, чтобы открытьe help video.",
INSTALL_SCRIPT_BUTTON: "Установка или обновление скриптов Excalidraw",
OPEN_AS_MD: "Открыть как Markdown",
EXPORT_IMAGE: `Экспорт изображения`,
OPEN_LINK: "Открыть выделенный текст как ссылку\n(SHIFT+CLICK для открытия в новой панели)",
EXPORT_EXCALIDRAW: "Экспорт в файл .Excalidraw",
LINK_BUTTON_CLICK_NO_TEXT: "Выберите элемент, содержащий внутреннюю или внешнюю ссылку.\n",
LINEAR_ELEMENT_LINK_CLICK_ERROR:
"Ссылки на элементы со стрелками и линиями нельзя перемещать с помощью " + labelCTRL() + " + КЛИКА по элементу, поскольку при этом также активируется редактор строк.\n" +
"Чтобы открыть ссылку, воспользуйтесь контекстным меню правой кнопки мыши или щелкните индикатор ссылки в правом верхнем углу элемента.\n",
FILENAME_INVALID_CHARS: 'Имя файла не может содержать ни одного из следующих символов: * " \\ < > : | ? #',
FORCE_SAVE: "Сохранить (также будут обновлены включения)",
RAW: "Переход в режим PREVIEW (влияет только на текстовые элементы со ссылками или включениями)",
PARSED: "Переход в режим RAW (влияет только на текстовые элементы со ссылками или включениями)",
NOFILE: "Excalidraw (без файла)",
COMPATIBILITY_MODE: "Файл *.excalidraw открыт в режиме совместимости. Конвертируйте в новый формат для полной функциональности плагина.",
CONVERT_FILE: "Преобразование в новый формат",
BACKUP_AVAILABLE: "Мы столкнулись с ошибкой при загрузке вашего рисунка. Это могло произойти, если Obsidian неожиданно закрылся во время операции сохранения. Например, если вы случайно закрыли Obsidian на своем мобильном устройстве во время сохранения.<br><br><b>ХОРОШАЯ НОВОСТЬ:</b> К счастью, доступна локальная резервная копия. Однако учтите, что если вы последний раз изменяли этот рисунок на другом устройстве (например, на планшете), а сейчас находитесь на рабочем столе, то на другом устройстве, скорее всего, имеется более свежая резервная копия.<br><br>Я рекомендую сначала попробовать открыть рисунок на другом устройстве и восстановить резервную копию из его локального хранилища.<br><br>Хотите загрузить резервную копию?",
BACKUP_RESTORED: "Резервная копия восстановлена",
CACHE_NOT_READY: "Приношу извинения за неудобства, но при загрузке вашего файла произошла ошибка.<br><br><mark>Немного терпения может сэкономить вам массу времени...</mark><br><br>Плагин имеет резервный кэш, но похоже, что вы только что запустили Obsidian. Инициализация резервного кэша может занять некоторое время, обычно до минуты или больше, в зависимости от производительности вашего устройства. Вы получите уведомление в правом верхнем углу, когда инициализация кэша будет завершена.<br><br>Нажмите OK, чтобы попытаться загрузить файл снова и проверить, завершилась ли инициализация кэша. Если за этим сообщением вы видите абсолютно пустой файл, я рекомендую подождать, пока кэш резервного копирования будет готов, прежде чем продолжать. Кроме того, вы можете выбрать «Отмена», чтобы вручную исправить файл.<br>",
OBSIDIAN_TOOLS_PANEL: "Панель инструментов Obsidian",
ERROR_SAVING_IMAGE: "При получении изображения произошла неизвестная ошибка. Возможно, по какой-то причине изображение недоступно или отклонен запрос на получение от Obsidian",
WARNING_PASTING_ELEMENT_AS_TEXT: "ВСТАВКА ЭЛЕМЕНТОВ EXCALIDRAW В КАЧЕСТВЕ ТЕКСТОВОГО ЭЛЕМЕНТА ЗАПРЕЩЕНА",
USE_INSERT_FILE_MODAL: "Используйте 'Вставить любой файл', чтобы вставить заметку в формате markdown",
RECURSIVE_INSERT_ERROR: "Нельзя рекурсивно вставлять часть изображения в одно и то же изображение, так как это приведет к созданию бесконечного цикла",
CONVERT_TO_MARKDOWN: "Преобразовать в файл...",
SELECT_TEXTELEMENT_ONLY: "Выбрать только текстовый элемент (не контейнер)",
REMOVE_LINK: "Удалить ссылку на текстовый элемент",
LASER_ON: "Включить лазерный указатель",
LASER_OFF: "Отключить лазерный указатель",
WELCOME_RANK_NEXT: "Больше рисунков до следующего ранга!",
WELCOME_RANK_LEGENDARY: "Вы на вершине. Продолжайте быть легендарным!",
WELCOME_COMMAND_PALETTE: 'Введите «Excalidraw» в палитре коман',
WELCOME_OBSIDIAN_MENU: "Изучите меню Обсидиана в правом верхнем углу",
WELCOME_SCRIPT_LIBRARY: "Посетите библиотеку сценариев",
WELCOME_HELP_MENU: "Найдите помощь в гамбургер-меню",
WELCOME_YOUTUBE_ARIA: "Канал Visual PKM на YouTube",
WELCOME_YOUTUBE_LINK: "Загляните на YouTube-канал Visual PKM.",
WELCOME_DISCORD_ARIA: "Присоединяйтесь к серверу Discord",
WELCOME_DISCORD_LINK: "Присоединяйтесь к серверу Discord",
WELCOME_TWITTER_ARIA: "Следите за мной в Twitter",
WELCOME_TWITTER_LINK: "Следите за мной в Twitter",
WELCOME_LEARN_ARIA: "Изучение Visual PKM",
WELCOME_LEARN_LINK: "Запишитесь на семинар по визуальному мышлению",
WELCOME_DONATE_ARIA: "Пожертвовать на поддержку Excalidraw-Obsidian",
WELCOME_DONATE_LINK: 'Скажите «Спасибо» и поддержите плагин.',
SAVE_IS_TAKING_LONG: "Сохранение предыдущего файла занимает много времени. Пожалуйста, подождите...",
SAVE_IS_TAKING_VERY_LONG: "Для повышения производительности рассмотрите возможность разделения больших рисунков на несколько файлов меньшего размера.",
//settings.ts
RELEASE_NOTES_NAME: "Отображение информации о выпуске после обновления",
RELEASE_NOTES_DESC:
"<b><u>Переключатель ВКЛ:</u></b> Отображение информации о выпуске при каждом обновлении Excalidraw до новой версии.<br>" +
"<b><u>Переключатель ВЫКЛ:</u></b> Тихий режим. Вы все еще можете прочитать заметки о выпуске на <a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/releases'>GitHub</a>.",
NEWVERSION_NOTIFICATION_NAME: "Уведомление об обновлении плагина",
NEWVERSION_NOTIFICATION_DESC:
"<b><u>Переключатель ВКЛ:</u></b> Показывайте уведомление о появлении новой версии плагина.<br>" +
"<b><u>Переключатель ВЫКЛ:</u></b> Тихий режим. Вам необходимо проверить обновления плагинов в разделе Community Plugins.",
BASIC_HEAD: "Основные",
BASIC_DESC: `В настройках "Основные" можно настроить такие параметры, как отображение заметок о выпуске после обновлений, получение уведомлений об обновлении плагинов, установка местоположения по умолчанию для новых чертежей, указание папки Excalidraw для вставки чертежей в активные документы, определение файла шаблона Excalidraw и указание папки сценария Excalidraw Automate для управления сценариями автоматизации.`,
FOLDER_NAME: "Папка Excalidraw",
FOLDER_DESC: "Место по умолчанию для новых чертежей. Если пусто, чертежи будут создаваться в корне хранилища.",
CROP_PREFIX_NAME: "Префикс файла обрезки",
CROP_PREFIX_DESC:
"Первая часть имени файла для новых чертежей, созданных при обрезке изображения. " +
"Если пусто, то по умолчанию будет использоваться значение 'cropped_'.",
ANNOTATE_PREFIX_NAME: "Префикс файла аннотации",
ANNOTATE_PREFIX_DESC:
"Первая часть имени файла для новых чертежей, созданных при аннотировании изображения. " +
"Если пусто, то по умолчанию будет использоваться 'annotated_'.",
ANNOTATE_PRESERVE_SIZE_NAME: "Preserve image size when annotating",
ANNOTATE_PRESERVE_SIZE_DESC: "When annotating an image in markdown the replacment image link will include the width of the original image.",
CROP_FOLDER_NAME: "Папка с файлами обрезки",
CROP_FOLDER_DESC: "Место по умолчанию для новых чертежей, созданных при обрезке изображения. Если папка пуста, рисунки будут создаваться в соответствии с настройками вложений Хранилища.",
ANNOTATE_FOLDER_NAME: "Папка с файлами аннотаций изображений",
ANNOTATE_FOLDER_DESC: "Место по умолчанию для новых рисунков, создаваемых при аннотировании изображения. Если пусто, рисунки будут создаваться в соответствии с настройками вложений Хранилища.",
FOLDER_EMBED_NAME: "Использовать папку Excalidraw при встраивании рисунка в активный документ",
FOLDER_EMBED_DESC:
"Определите, в какую папку поместить новый вставленный рисунок " +
"при использовании действия палитры команд: 'Создать новый рисунок и вставить в активный документ'.<br>" +
"<b><u>Переключатель ВКЛ:</u></b> Используйте папку Excalidraw<br><b><u>Переключатель ВЫКЛ:</u></b> Используйте папку вложений, определенную в настройках Obsidian.",
TEMPLATE_NAME: "Файл или папка шаблона Excalidraw",
TEMPLATE_DESC:
"Полный путь к файлу или папке с шаблоном Excalidraw.<br>" +
"<b>Файл шаблона:</b>Например: Если ваш шаблон находится в папке Excalidraw по умолчанию и его имя " +
"Template.md, настройка должна быть: Excalidraw/Template.md (или только Excalidraw/Template - вы можете опустить .md расширение файла). " +
"Если вы используете Excalidraw в режиме совместимости, то ваш шаблон также должен быть устаревшим файлом Excalidraw " +
"такие как Excalidraw/Template.excalidraw. <br><b>Папка с шаблонами:</b> Вы также можете задать папку в качестве шаблона. " +
"В этом случае вам будет предложено выбрать шаблон при создании нового чертежа.<br>" +
"<b>Совет профи:</b> Если вы используете плагин Obsidian Templater, вы можете добавить код Templater в различные Excalidraw " +
"шаблоны для автоматизации настройки чертежей.",
SCRIPT_FOLDER_NAME: "Папка скриптов Excalidraw Automate (РеГИстРозависимЫЙ!)",
SCRIPT_FOLDER_DESC:
"Файлы, которые вы поместите в эту папку, будут рассматриваться как сценарии Excalidraw Automate. " +
"Вы можете получить доступ к своим скриптам из Excalidraw через палитру команд Obsidian. Назначьте " +
"горячие клавиши для ваших любимых скриптов, как и для любой другой команды Obsidian. " +
"Эта папка может не быть корневой папкой вашего хранилища. ",
AI_HEAD: "Настройки ИИ - Экспериментальные",
AI_DESC: `В настройках "ИИ" вы можете настроить параметры использования GPT API OpenAI. ` +
`Пока API OpenAI находится в бета-версии, его использование строго ограничено - поэтому мы требуем, чтобы вы использовали свой собственный ключ API. ` +
`Вы можете создать аккаунт OpenAI, добавить небольшой кредит (минимум 5 долларов) и сгенерировать свой собственный ключ API. ` +
`После установки API-ключа вы сможете использовать инструменты искусственного интеллекта в Excalidraw.`,
AI_OPENAI_TOKEN_NAME: "Ключ API OpenAI",
AI_OPENAI_TOKEN_DESC: "Вы можете получить свой ключ API OpenAI из вашего <a href='https://platform.openai.com/api-keys'>OpenAI аккаунта</a>.",
AI_OPENAI_TOKEN_PLACEHOLDER: "Введите свой ключ API OpenAI здесь",
AI_OPENAI_DEFAULT_MODEL_NAME: "Модель ИИ по умолчанию",
AI_OPENAI_DEFAULT_MODEL_DESC:
"Модель ИИ по умолчанию, используемая при генерации текста. Это поле свободного текста, поэтому вы можете ввести любое действительное имя модели OpenAI. " +
"Узнайте больше о доступных моделях на <a href='https://platform.openai.com/docs/models'>OpenAI сайте</a>.",
AI_OPENAI_DEFAULT_MODEL_PLACEHOLDER: "Введите здесь модель искусственного интеллекта по умолчанию, например: gpt-3.5-turbo-1106.",
AI_OPENAI_DEFAULT_IMAGE_MODEL_NAME: "Модель ИИ для генерации изображений по умолчанию",
AI_OPENAI_DEFAULT_IMAGE_MODEL_DESC:
"Модель ИИ по умолчанию, используемая при генерации изображений. Редактирование и изменение изображений поддерживается OpenAI только в dall-e-2, " +
"поэтому dall-e-2 будет автоматически использоваться в таких случаях независимо от этой настройки.<br>" +
"Это поле свободного текста, поэтому вы можете ввести любое действительное имя модели OpenAI. " +
"Узнайте больше о доступных моделях на <a href='https://platform.openai.com/docs/models'>OpenAI сайте</a>.",
AI_OPENAI_DEFAULT_IMAGE_MODEL_PLACEHOLDER: "Введите здесь модель ИИ Image Generation по умолчанию, например: dall-e-3.",
AI_OPENAI_DEFAULT_VISION_MODEL_NAME: "Модель видения ИИ по умолчанию",
AI_OPENAI_DEFAULT_VISION_MODEL_DESC:
"Модель зрения ИИ по умолчанию, используемая при генерации текста из изображений. Это поле свободного текста, поэтому вы можете ввести любое действительное имя модели OpenAI. " +
"Узнайте больше о доступных моделях на <a href='https://platform.openai.com/docs/models'>OpenAI сайте</a>.",
AI_OPENAI_DEFAULT_API_URL_NAME: "URL-адрес API OpenAI",
AI_OPENAI_DEFAULT_API_URL_DESC:
"URL-адрес OpenAI API по умолчанию. Это поле свободного текста, поэтому вы можете ввести любой действительный URL, совместимый с OpenAI API. " +
"Excalidraw будет использовать этот URL при отправке API-запросов в OpenAI. Я не делаю никакой обработки ошибок в этом поле, поэтому убедитесь, что вы вводите правильный URL и изменяйте его только в том случае, если вы знаете, что делаете. ",
AI_OPENAI_DEFAULT_IMAGE_API_URL_NAME: "URL-адрес API генерации изображений OpenAI",
AI_OPENAI_DEFAULT_VISION_MODEL_PLACEHOLDER: "Введите здесь модель зрения ИИ по умолчанию. Например: gpt-4o",
SAVING_HEAD: "Сохранение",
SAVING_DESC: "В разделе 'Сохранение' раздела Настройки Excalidraw вы можете настроить способ сохранения ваших чертежей. Сюда входят опции сжатия Excalidraw JSON в Markdown, установки интервалов автосохранения для настольных и мобильных компьютеров, определения форматов имен файлов, а также выбора расширения файла .excalidraw.md или .md. ",
COMPRESS_NAME: "Сжатие Excalidraw JSON в формате Markdown",
COMPRESS_DESC:
"При включении этой функции Excalidraw будет хранить JSON рисунка в формате Base64. " +
"формат с использованием алгоритма <a href='https://pieroxy.net/blog/pages/lz-string/index.html'>LZ-String</a>. " +
"Это уменьшит вероятность того, что Excalidraw JSON загромоздит результаты поиска в Obsidian. " +
"Как побочный эффект, это также уменьшит размер файлов чертежей Excalidraw. " +
"При переключении чертежа Excalidraw в режим Markdown с помощью меню опций Excalidraw файл будет " +
"сохранен без сжатия, чтобы вы могли читать и редактировать строку JSON. Чертеж будет снова сжат " +
"как только вы переключитесь обратно в вид Excalidraw. " +
"Настройка имеет силу только 'на перспективу', то есть существующие чертежи не будут затронуты настройкой " +
"пока вы не откроете и не сохраните их.<br><b><u>Переключатель ВКЛ:</u></b> Сжать чертеж JSON<br><b><u>Переключатель ВЫКЛ:</u></b> Оставьте JSON для рисования без сжатия",
DECOMPRESS_FOR_MD_NAME: "Декомпрессия Excalidraw JSON в Markdown Режим",
DECOMPRESS_FOR_MD_DESC:
"При включении этой функции Excalidraw будет автоматически распаковывать JSON чертежа при переключении в режим Markdown." +
"Это позволит вам легко читать и редактировать строку JSON. Чертеж будет снова сжат " +
"как только вы переключитесь обратно в режим Excalidraw и сохраните чертеж (CTRL+S).<br>" +
"Я рекомендую отключить эту функцию, так как это приведет к уменьшению размера файлов и избавит от ненужных результатов в поиске Obsidian. " +
"Вы всегда можете воспользоваться командой 'Excalidraw: Распаковать текущий файл Excalidraw' из палитры команд. "+
"чтобы вручную распаковывать JSON чертежа, когда вам нужно его прочитать или отредактировать.",
AUTOSAVE_INTERVAL_DESKTOP_NAME: "Интервал для автосохранения на рабочем столе",
AUTOSAVE_INTERVAL_DESKTOP_DESC:
"Интервал времени между сохранениями. Автосохранение будет пропущено, если в чертеже нет изменений. " +
"Excalidraw также сохранит файл при закрытии вкладки рабочей области или при навигации в Obsidian, но вне активной вкладки Excalidraw (например, при нажатии на ленту Obsidian, проверке обратных ссылок и т. д.). " +
"Excalidraw не сможет сохранить вашу работу при завершении работы Obsidian напрямую, либо убив процесс Obsidian, либо нажав кнопку закрытия Obsidian вообще.",
AUTOSAVE_INTERVAL_MOBILE_NAME: "Интервал для автосохранения на мобильном телефоне",
AUTOSAVE_INTERVAL_MOBILE_DESC:
"Для мобильников я рекомендую более частый интервал. " +
"Excalidraw также сохранит файл при закрытии вкладки рабочей области или при навигации в Obsidian, но вне активной вкладки Excalidraw (например, при нажатии на ленту Obsidian, проверке обратных ссылок и т. д.). " +
"Excalidraw не сможет сохранить вашу работу при прямом завершении работы Obsidian (т.е. смахнув ее). Также обратите внимание, что при переключении приложений на мобильном устройстве, иногда Android и iOS закрываются " +
"Obsidian в фоновом режиме для экономии системных ресурсов. В этом случае Excalidraw не сможет сохранить последние изменения.",
FILENAME_HEAD: "Имя файла",
FILENAME_DESC:
"<p>Нажмите на эту ссылку, чтобы получить <a href='https://momentjs.com/docs/#/displaying/format/'>" +
"справочник по формату даты и времени</a>.</p>",
FILENAME_SAMPLE: "Filename for a new drawing is: ",
FILENAME_EMBED_SAMPLE: "Имя файла для нового встроенного чертежа: ",
FILENAME_PREFIX_NAME: "Префикс имени файла",
FILENAME_PREFIX_DESC: "Первая часть имени файла",
FILENAME_PREFIX_EMBED_NAME: "Префикс имени файла при вставке нового чертежа в заметку в формате markdown",
FILENAME_PREFIX_EMBED_DESC:
"Должно ли имя файла нового вставленного чертежа начинаться с имени активной заметки в формате markdown " +
"при использовании действия палитры команд: <code>Создать новый чертеж и вставить его в активный документ</code>?<br>" +
"<b><u>Переключатель ВКЛ:</u></b> Да, имя файла нового чертежа должно начинаться с имени файла активного документа<br><b><u>Переключатель ВЫКЛ:</u></b> Нет, имя файла нового чертежа не должно включать имя файла активного документа",
FILENAME_POSTFIX_NAME: "Пользовательский текст после имени заметки в формате markdown при вставке",
FILENAME_POSTFIX_DESC: "Влияет на имя файла только при вставке в документ markdown. Этот текст будет вставлен после имени заметки, но перед датой.",
FILENAME_DATE_NAME: "Дата имени файла",
FILENAME_DATE_DESC: "Последняя часть имени файла. Оставьте пустой, если дата не нужна.",
FILENAME_EXCALIDRAW_EXTENSION_NAME: ".excalidraw.md или .md",
FILENAME_EXCALIDRAW_EXTENSION_DESC:
"Эта настройка не применяется, если вы используете Excalidraw в режиме совместимости, " +
"т.е. вы не используете файлы разметки Excalidraw.<br><b><u>Переключатель ВКЛ:</u></b> Имя файла заканчивается на .excalidraw.md<br><b><u>Переключатель ВЫКЛ:</u></b> Имя файла заканчивается на .md",
DISPLAY_HEAD: "Внешний вид и поведение Excalidraw",
DISPLAY_DESC: "В разделе 'Внешний вид и поведение' раздела Настройки Excalidraw вы можете настроить внешний вид и поведение Excalidraw. Сюда входят опции динамической стилизации, режима для левой руки, соответствия тем Excalidraw и Obsidian, режимов по умолчанию и многое другое.",
DYNAMICSTYLE_NAME: "Динамическая стилизация",
DYNAMICSTYLE_DESC: "Изменение цветов пользовательского интерфейса Excalidraw в соответствии с цветом холста",
LEFTHANDED_MODE_NAME: "Левосторонний режим",
LEFTHANDED_MODE_DESC:
"В настоящее время действует только в трей режиме. Если включить этот режим, трей будет находиться с правой стороны." +
"<br><b><u>Переключатель ВКЛ:</u></b> Левосторонний режим.<br><b><u>Переключатель ВЫКЛ:</u></b> Правосторонний режим",
IFRAME_MATCH_THEME_NAME: "Вставки Markdown для соответствия теме Excalidraw",
IFRAME_MATCH_THEME_DESC:
"<b><u>Переключатель ВКЛ:</u></b> Установите значение true, если, например, вы используете Obsidian в темном режиме, но применяете excalidraw со светлым фоном. " +
"С этой настройкой встроенный документ разметки Obsidian будет соответствовать теме Excalidraw (т.е. светлые цвета, если Excalidraw находится в светлом режиме).<br>" +
"<b><u>Переключатель ВЫКЛ:</u></b> Установите значение false, если хотите, чтобы встроенный в Obsidian документ разметки соответствовал теме Obsidian (т.е. темные цвета, если Obsidian находится в темном режиме).",
MATCH_THEME_NAME: "Новый чертеж в соответствии с темой Obsidian",
MATCH_THEME_DESC:
"Если тема темная, новый рисунок будет создан в темном режиме. Это не относится к случаям, когда вы используете шаблон для новых рисунков. " +
"Также это не повлияет на открытие существующего чертежа. Они будут соответствовать теме шаблона/чертежа соответственно." +
"<br><b><u>Переключатель ВКЛ:</u></b> Следуйте за Obsidian Theme<br><b><u>Переключатель ВЫКЛ:</u></b> Следовать теме, заданной в вашем шаблоне",
MATCH_THEME_ALWAYS_NAME: "Существующие чертежи должны соответствовать теме Obsidian",
MATCH_THEME_ALWAYS_DESC:
"Если тема темная, чертежи будут открываться в темном режиме. Если тема светлая, они будут открываться в светлом режиме. " +
"<br><b><u>Переключатель ВКЛ:</u></b> Соответствовать теме Obsidian<br><b><u>Переключатель ВЫКЛ:</u></b> Открывать ту же тему, что и при последнем сохранении",
MATCH_THEME_TRIGGER_NAME: "Excalidraw будет следовать за изменениями Темы Obsidian",
MATCH_THEME_TRIGGER_DESC:
"Если эта опция включена, открытая панель Excalidraw будет переключаться в светлый/темный режим при смене темы Obsidian. " +
"<br><b><u>Переключатель ВКЛ:</u></b> Следить за изменениями темы<br><b><u>Переключатель ВЫКЛ:</u></b> Чертежи не подвержены изменениям темы Obsidian",
DEFAULT_OPEN_MODE_NAME: "Режим по умолчанию при открытии Excalidraw",
DEFAULT_OPEN_MODE_DESC:
"Указывает режим, в котором открывается Excalidraw: Обычный, Zen или режим просмотра. Вы также можете задать это поведение на уровне файла " +
"добавив в документ ключ excalidraw-default-mode frontmatter со значением: normal, view или zen.",
DEFAULT_PEN_MODE_NAME: "Режим пера",
DEFAULT_PEN_MODE_DESC: "Должен ли режим пера автоматически включаться при открытии Excalidraw?",
DISABLE_DOUBLE_TAP_ERASER_NAME: "Включение двойного нажатия ластика в режиме пера",
SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_NAME: "Показать (+) перекрестие в режиме пера",
SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_DESC:
"Показывайте перекрестие в режиме пера при использовании инструмента freedraw. <b><u>Toggle Переключатель ВКЛ</u></b> Показывать <b><u>Toggle Переключатель ВЫКЛ</u></b> Скрывать<br>"+
"Эффект зависит от устройства. Перекрестие обычно видно на планшетах для рисования, MS Surface, но не на iOS.",
SHOW_DRAWING_OR_MD_IN_HOVER_PREVIEW_NAME: "Передача файла Excalidraw в виде изображения в предварительном просмотре при наведении...",
SHOW_DRAWING_OR_MD_IN_HOVER_PREVIEW_DESC:
"...даже если файл имеет ключ <b>excalidraw-open-md: true</b> frontmatter.<br>" +
"Если этот параметр выключен и файл по умолчанию открывается в формате md, при наведении на предварительный просмотр" +
"будет показана часть документа, содержащая разметку.",
SHOW_DRAWING_OR_MD_IN_READING_MODE_NAME: "Рендеринг в виде изображения при чтении файла Excalidraw в режиме разметки",
SHOW_DRAWING_OR_MD_IN_READING_MODE_DESC:
"Когда вы находитесь в режиме чтения разметки (а именно, читаете обратную сторону рисунка), должен ли рисунок Excalidraw отображаться как изображение? " +
"Этот параметр не влияет на отображение чертежа в режиме Excalidraw, а также при встраивании чертежа в документ с пометками или при предварительном просмотре при наведении.<br><ul>" +
"<li>Смотрите другие связанные настройки для <a href='#«+TAG_PDFEXPORT+»'>экспорта PDF</a> в разделе 'Встраивание и экспорт' ниже.</li></ul><br>" +
"Вы должны закрыть активный файл excalidraw/markdown и снова открыть его, чтобы это изменение вступило в силу.",
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_NAME: "При экспорте файла Excalidraw в PDF файл отображается как изображение.",
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_DESC:
"Этот параметр управляет поведением Excalidraw при экспорте файла Excalidraw в PDF в режиме просмотра разметки с помощью функции Obsidian <b>Экспорт в PDF</b> <br>" +
"<ul><li>Если <b>разрешить</b>, в PDF будет отображаться только чертеж Excalidraw;</li>" +
"<li>Если <b>заблокировать</b>, то в PDF будет отображаться разметка документа.</li></ul>" +
"См. другие связанные настройки для <a href='#«+TAG_MDREADINGMODE+»'>режима чтения разметки</a> в разделе 'Внешний вид и поведение' выше.<br>" +
"⚠️ Обратите внимание, что необходимо закрыть активный файл excalidraw/markdown и открыть его снова, чтобы изменения вступили в силу. ⚠️",
HOTKEY_OVERRIDE_HEAD: "Переопределение горячих клавиш",
HOTKEY_OVERRIDE_DESC: `Некоторые горячие клавиши Excalidraw, такие как <code>${labelCTRL()}+Enter</code> для редактирования текста или <code>${labelCTRL()}+K</code> создания ссылки на элемент ` +
"конфликтуют с настройками горячих клавиш Obsidian. Комбинации горячих клавиш, которые вы добавите ниже, отменят настройки горячих клавиш Obsidian при использовании Excalidraw, таким образом " +
`Вы можете добавить <code>${labelCTRL()}+G</code>, если хотите по умолчанию перейти к Группе Объектов в Excalidraw вместо открытия Режима просмотра Графиков.`,
THEME_HEAD: "Тема и стиль",
ZOOM_HEAD: "Масштабирование",
DEFAULT_PINCHZOOM_NAME: "Разрешить масштабирование в режиме пера",
DEFAULT_PINCHZOOM_DESC:
"По умолчанию зуммирование в режиме пера при использовании инструмента «Свободное рисование» отключено, чтобы предотвратить нежелательное случайное масштабирование с помощью ладони.<br>" +
"<b><u>Переключатель ВКЛ:</u></b>Включение щипкового масштабирования в режиме пера<br><b><u>Переключатель ВЫКЛ:</u></b>Выключение щипкового масштабирования в режиме пера",
DEFAULT_WHEELZOOM_NAME: "Колесо мыши для масштабирования по умолчанию",
DEFAULT_WHEELZOOM_DESC:
`<b><u>Переключатель ВКЛ:</u></b> Колесо мыши для масштабирования; ${labelCTRL()} + Колесо мыши для прокрутки</br><b><u>Переключатель ВЫКЛ:</u></b>${labelCTRL()} + Колесико мыши для масштабирования; Колесико мыши для прокрутки`,
ZOOM_TO_FIT_NAME: "Изменение масштаба при изменении размера просмотра",
ZOOM_TO_FIT_DESC: "Изменение масштаба чертежа при изменении размера панели" +
"<br><b><u>Переключатель ВКЛ:</u></b> Увеличить масштаб<br><b><u>Переключатель ВЫКЛ:</u></b> Автоматическое масштабирование отключено",
ZOOM_TO_FIT_ONOPEN_NAME: "Увеличение масштаба при открытии файла",
ZOOM_TO_FIT_ONOPEN_DESC: "Изменение масштаба чертежа при его первом открытии" +
"<br><b><u>Переключатель ВКЛ:</u></b> Увеличить масштаб<br><b><u>Переключатель ВЫКЛ:</u></b> Автоматическое масштабирование отключено",
ZOOM_TO_FIT_MAX_LEVEL_NAME: "Увеличение до максимального уровня масштабирования",
ZOOM_TO_FIT_MAX_LEVEL_DESC: "Установите максимальный уровень, до которого масштабирование будет увеличивать чертеж. Минимальное значение - 0,5 (50 %), максимальное - 10 (1000 %).",
GRID_HEAD: "Сетка",
GRID_DYNAMIC_COLOR_NAME: "Динамический цвет сетки",
GRID_DYNAMIC_COLOR_DESC: "<b><u>Переключатель ВКЛ:</u></b>Измените цвет сетки, чтобы он соответствовал цвету холста<br><b><u>Переключатель ВЫКЛ:</u></b>Используйте цвет, указанный ниже, в качестве цвета сетки",
GRID_COLOR_NAME: "Цвет сетки",
GRID_OPACITY_NAME: "Прозрачность сетки",
GRID_OPACITY_DESC: "Прозрачность сетки также будет управлять прозрачностью поля привязки при привязке стрелки к элементу.<br>" +
"Установите прозрачность сетки. 0 - прозрачная, 100 - непрозрачная.",
LASER_HEAD: "Лазерный указатель",
LASER_COLOR: "Цвет лазерного указателя",
LASER_DECAY_TIME_NAME: "Время затухания лазерного указателя",
LASER_DECAY_TIME_DESC: "Время затухания лазерного указателя в миллисекундах. По умолчанию - 1000 (т. е. 1 секунда).",
LASER_DECAY_LENGTH_NAME: "Длительность затухания лазерного указателя.",
LASER_DECAY_LENGTH_DESC: "Длина затухания лазерного указателя в точках линии. По умолчанию 50.",
LINKS_HEAD: "Ссылки, включение и задачи TODO",
LINKS_HEAD_DESC: "В разделе 'Ссылки, включения и TODO' раздела Настройки Excalidraw вы можете настроить, как Excalidraw обрабатывает ссылки, включения и элементы TODO. Сюда входят опции для открытия ссылок, управления панелями, отображения ссылок со скобками, настройки префиксов ссылок, обработки элементов TODO и т. д. ",
LINKS_DESC:
`${labelCTRL()}+КЛИКНИТЕ на <code>[[Text Elements]]</code> чтобы открыть их как ссылки. ` +
"Если выделенный текст имеет более одного <code>[[valid Obsidian links]]</code>, только первый будет открыт. " +
"Если текст начинается как правильная веб-ссылка (то есть <code>https://</code> или <code>http://</code>), потом " +
"плагин откроет его в браузере. " +
"Когда файлы Obsidian изменяются, соответствующие <code>[[link]]</code> в ваших чертежах также изменится. " +
"Если вы не хотите, чтобы текст случайно менялся в ваших чертежах, используйте <code>[[links|with aliases]]</code>.",
DRAG_MODIFIER_NAME: "Щелкните ссылку и перетащите клавиши-модификаторы",
DRAG_MODIFIER_DESC: "Поведение клавиши-модификатора при нажатии на ссылки и перетаскивании элементов. " +
"Excalidraw не будет проверять вашу конфигурацию... обратите внимание, чтобы избежать конфликтов настроек. " +
"Эти настройки отличаются для Apple и не-Apple. Если вы используете Obsidian на нескольких платформах, вам нужно будет сделать настройки отдельно. "+
"Переключатели расположены в порядке" +
(DEVICE.isIOS || DEVICE.isMacOS ? "SHIFT, CMD, OPT, CONTROL." : "SHIFT, CTRL, ALT, META (Клавишы Windows)."),
LONG_PRESS_DESKTOP_NAME: "Длительное нажатие открывает рабочий стол",
LONG_PRESS_DESKTOP_DESC: "Задержка нажатия в миллисекундах для открытия чертежа Excalidraw, встроенного в файл Markdown.",
LONG_PRESS_MOBILE_NAME: "Длительное нажатие открывает мобильную версию",
LONG_PRESS_MOBILE_DESC: "Задержка нажатия в миллисекундах для открытия чертежа Excalidraw, встроенного в файл Markdown.",
FOCUS_ON_EXISTING_TAB_NAME: "Фокус на существующей вкладке",
FOCUS_ON_EXISTING_TAB_DESC: "При открытии ссылки Excalidraw будет фокусироваться на существующей вкладке, если файл уже открыт. " +
"Включение этого параметра отменяет 'Повторное использование соседней панели', если файл уже открыт.",
SECOND_ORDER_LINKS_NAME: "Показать ссылки второго порядка",
SECOND_ORDER_LINKS_DESC: "Показывать ссылки при нажатии на ссылку в Excalidraw. Ссылки второго порядка - это обратные ссылки, указывающие на ссылку, по которой переходят. " +
"При использовании значков изображений для соединения похожих заметок ссылки второго порядка позволяют перейти к связанным заметкам одним щелчком мыши, а не двумя. " +
"Для понимания смотрите <a href='https://youtube.com/shorts/O_1ls9c6wBY?feature=share'>YT Short</a>.",
ADJACENT_PANE_NAME: "Повторное использование соседней панели",
ADJACENT_PANE_DESC:
`Когда ${labelCTRL()}+${labelALT()} нажимает на ссылку в Excalidraw, по умолчанию плагин открывает ссылку в новой панели. ` +
"Если включить этот параметр, Excalidraw сначала будет искать существующую панель и пытаться открыть ссылку в ней. " +
"Excalidraw будет искать другую панель рабочего пространства, основываясь на истории фокуса/навигации, то есть на той панели, которая была активна до того, " +
"как вы активировали Excalidraw.",
MAINWORKSPACE_PANE_NAME: "Открыть в основном рабочем пространстве",
MAINWORKSPACE_PANE_DESC:
`Когда ${labelCTRL()}+${labelALT()} нажимает на ссылку в Excalidraw, по умолчанию плагин открывает ссылку в новой панели в текущем активном окне. ` +
"Если включить этот параметр, Excalidraw откроет ссылку в существующей или новой панели в основном рабочем пространстве. ",
LINK_BRACKETS_NAME: "Показать <code>[[brackets]]</code> вокруг ссылок",
LINK_BRACKETS_DESC: `${
"В режиме ПРЕДВАРИТЕЛЬНОГО ПРОСМОТРА при разборе элементов текста ставьте скобки вокруг ссылок. " +
"Вы можете переопределить эту настройку для конкретного чертежа, добавив <code>"
}${FRONTMATTER_KEYS["link-brackets"].name}: true/false</code> в frontmatter файла.`,
LINK_PREFIX_NAME: "Префикс ссылки",
LINK_PREFIX_DESC: `${
"В режиме ПРЕДВАРИТЕЛЬНОГО ПРОСМОТРА, если элемент 'Текст' содержит ссылку, перед текстом должны стоять эти символы. " +
"Вы можете переопределить эту настройку для конкретного чертежа, добавив <code>"
}${FRONTMATTER_KEYS["link-prefix"].name}: "📍 "</code> в frontmatter файла.`,
URL_PREFIX_NAME: "Префикс URL-адреса",
URL_PREFIX_DESC: `${
"В режиме ПРЕДВАРИТЕЛЬНОГО ПРОСМОТРА, если элемент 'Текст' содержит ссылку URL, перед текстом должны стоять эти символы. " +
"Вы можете переопределить эту настройку для конкретного чертежа, добавив <code>"
}${FRONTMATTER_KEYS["url-prefix"].name}: "🌐 "</code> в frontmatter файла.`,
PARSE_TODO_NAME: "Парсинг TODO",
PARSE_TODO_DESC: "Преобразуйте '- [ ] ' и '- [x] ' в чекбокс и поставьте галочку.",
TODO_NAME: "Открыть иконку TODO",
TODO_DESC: "Иконка для открытых пунктов TODO",
DONE_NAME: "Иконка завершенного TODO",
DONE_DESC: "Иконка для завершенных элементов TODO",
HOVERPREVIEW_NAME: `Предварительный просмотр наведением без нажатия клавиши ${labelCTRL()}`,
HOVERPREVIEW_DESC:
`<b><u>Переключатель ВКЛ:</u></b> <u>В режиме просмотра</u> Exalidraw предварительный просмотр при наведении на [[вики-ссылки]] будет показан сразу, без необходимости удерживать клавишу ${labelCTRL()}. ` +
"В Excalidraw <u>нормальный режим</u>, предварительный просмотр будет показан сразу только при наведении на синий значок ссылки в правом верхнем углу элемента.<br> " +
`<b><u>Переключатель ВЫКЛ:</u></b> Предварительный просмотр при наведении отображается только в том случае, если при наведении на ссылку вы удерживаете клавишу ${labelCTRL()}.`,
LINKOPACITY_NAME: "Прозрачность значка ссылки",
LINKOPACITY_DESC: "Прозрачность значка индикатора ссылки в правом верхнем углу элемента. 1 - непрозрачный, 0 - прозрачный.",
LINK_CTRL_CLICK_NAME: `${labelCTRL()}+КЛИК на текст с [[links]] или [](links), чтобы открыть их`,
LINK_CTRL_CLICK_DESC:
"Вы можете отключить эту функцию, если она мешает работе стандартных функций Excalidraw, которые вы хотите использовать. " +
`Если эта функция отключена, для открытия ссылок можно использовать либо ${labelCTRL()} + ${labelMETA()}, либо индикатор ссылок в правом верхнем углу элемента.`,
TRANSCLUSION_WRAP_NAME: "Поведение переноса при переполненнии включенного текста",
TRANSCLUSION_WRAP_DESC:
"Число задает количество символов, через которое должен быть перенесен текст. " +
"Устанавливает поведение переноса текста. Включите этот параметр, чтобы принудительно перенести " +
" текст (т. е. без переполнения), или выключите, чтобы мягко перенести текст (по ближайшему пробелу).",
TRANSCLUSION_DEFAULT_WRAP_NAME: "Перенос по словам включения по умолчанию",
TRANSCLUSION_DEFAULT_WRAP_DESC:
"Вы можете вручную задать/переопределить длину переноса слов, используя формат `![[page#^block]]{NUMBER}`. " +
"Обычно вам не нужно устанавливать значение по умолчанию, поскольку если вы вставите текст внутрь стикера, то Excalidraw автоматически позаботится о переносе слов. " +
"Установите это значение на '0', если вы не хотите устанавливать значение по умолчанию. ",
PAGE_TRANSCLUSION_CHARCOUNT_NAME: "Максимальное количество символов при включении страниц (трансклюзии)",
PAGE_TRANSCLUSION_CHARCOUNT_DESC:
"Максимальное количество символов, отображаемых на странице при включении всей страницы" +
"в формате ![[markdown page]].",
QUOTE_TRANSCLUSION_REMOVE_NAME: "Включение (Трансклюзия) цитат: удалите ведущие '> ' из каждой строки",
QUOTE_TRANSCLUSION_REMOVE_DESC: "Удалите начальный '>' из каждой строки включения. Это улучшит читаемость цитат в текстовых включениях <br>" +
"<b><u>Переключатель ВКЛ:</u></b> Удалить ведущие '> '<br><b><u>Переключатель ВЫКЛ:</u></b> Не удалить ведущие '> ' (обратите внимание, что он все равно будет удален из первой строки из-за функциональности API Obsidian.)",
GET_URL_TITLE_NAME: "Используйте iframely для преобразования заголовка страницы",
GET_URL_TITLE_DESC:
"Используйте <code>http://iframely.server.crestify.com/iframely?url=</code> для получения заголовка страницы при переходе по ссылке в Excalidraw",
PDF_TO_IMAGE: "PDF в изображение",
PDF_TO_IMAGE_SCALE_NAME: "Шкала преобразования PDF в изображения",
PDF_TO_IMAGE_SCALE_DESC: "Устанавливает разрешение изображения, которое генерируется из PDF-страницы. Более высокое разрешение приведет к увеличению размера изображений в памяти и, как следствие, к увеличению нагрузки на систему (замедлению производительности), но при этом изображение будет более четким. " +
"Кроме того, если вы хотите скопировать страницы PDF (как изображения) на Excalidraw.com, больший размер изображения может привести к превышению лимита в 2 МБ на Excalidraw.com.",
EMBED_TOEXCALIDRAW_HEAD: "Встраивание файлов в Excalidraw",
EMBED_TOEXCALIDRAW_DESC: "В разделе Встраивание файлов раздела Настройки Excalidraw вы можете настроить, как различные файлы будут встраиваться в Excalidraw. Сюда входят опции для встраивания интерактивных файлов разметки (Markdown), PDF-файлов и файлов разметки (Markdown) в виде изображений.",
MD_HEAD: "Встраивать разметку в Excalidraw в виде изображения",
MD_EMBED_CUSTOMDATA_HEAD_NAME: "Интерактивные файлы Markdown",
MD_EMBED_CUSTOMDATA_HEAD_DESC: `Приведенные ниже настройки будут влиять только на будущие вставки. Текущие вставки остаются неизменными. Настройки темы для встроенных фреймов находятся в разделе "Внешний вид и поведение Excalidraw".`,
MD_EMBED_SINGLECLICK_EDIT_NAME: "Редактирование встроенной разметки (Markdown) одним щелчком мыши",
MD_EMBED_SINGLECLICK_EDIT_DESC:
"Однократный щелчок на встроенном файле разметки (Markdown) для его редактирования. " +
"Если отключить эту функцию, файл с пометками сначала откроется в режиме предварительного просмотра, а затем переключится в режим редактирования, когда вы снова нажмете на него.",
MD_TRANSCLUDE_WIDTH_NAME: "Ширина по умолчанию для включенного документа с разметкой",
MD_TRANSCLUDE_WIDTH_DESC:
"Ширина страницы разметки (Markdown). Это влияет на обертку слов при встраивание длинных абзацев, а также на ширину элемента изображения " +
" Вы можете изменить ширину встроенного файла по умолчанию, " +
"используя синтаксис <code>[[filename#heading|WIDTHxMAXHEIGHT]]</code> в режиме просмотра markdown в разделе встроенных файлов.",
MD_TRANSCLUDE_HEIGHT_NAME: "Максимальная высота по умолчанию для документа с пометкой встраиваемый",
MD_TRANSCLUDE_HEIGHT_DESC:
"Встроенное изображение будет настолько высоким, насколько этого требует текст разметки (Markdown), но не выше этого значения. " +
"Вы можете переопределить это значение, отредактировав ссылку на встроенное изображение в режиме просмотра markdown со следующим синтаксисом <code>[[filename#^blockref|WIDTHxMAXHEIGHT]]</code>.",
MD_DEFAULT_FONT_NAME: "Шрифт по умолчанию, используемый для встроенных файлов разметки (Markdown).",
MD_DEFAULT_FONT_DESC:
'Установите это значение на "Virgil" или "Cascadia" или на имя файла <code>.ttf</code>, <code>.woff</code>, или <code>.woff2</code> шрифта, например. <code>MyFont.woff2</code> ' +
"Вы можете отменить эту настройку, добавив следующий frontmatter-ключ во встроенный файл разметки (markdown): <code>excalidraw-font: font_or_filename</code>",
MD_DEFAULT_COLOR_NAME: "Цвет шрифта по умолчанию, используемый для встроенных файлов разметки (markdown).",
MD_DEFAULT_COLOR_DESC:
'Установите это значение в любое допустимое имя цвета css, например, "steelblue" (<a href="https://www.w3schools.com/colors/colors_names.asp">имена цветов</a>), или допустимый шестнадцатеричный цвет, например "#e67700", ' +
"или на любую другую допустимую строку цвета css. Вы можете отменить эту настройку, добавив следующий frontmatter-ключ во встроенный файл разметки (markdown): <code>excalidraw-font-color: steelblue</code>",
MD_DEFAULT_BORDER_COLOR_NAME: "Цвет границы, используемый по умолчанию для встроенных файлов разметки (markdown).",
MD_DEFAULT_BORDER_COLOR_DESC:
'Установите это значение на любое допустимое имя цвета css, например "steelblue" (<a href="https://www.w3schools.com/colors/colors_names.asp">имена цветов</a>), или на допустимый шестнадцатеричный цвет, например "#e67700", ' +
"или на любую другую допустимую строку цвета css. Вы можете отменить эту настройку, добавив следующий frontmatter-key во встроенный файл разметки (markdown): <code>excalidraw-border-color: gray</code>. " +
"Оставьте пустым, если вам не нужна граница. ",
MD_CSS_NAME: "CSS файл",
MD_CSS_DESC:
"Имя файла CSS для применения к вставкам markdown. Укажите имя файла с расширением (например, 'md-embed.css'). Файл css также может быть обычным файлом " +
"markdow (e.g. 'md-embed-css.md'), просто убедитесь, что содержимое написано с использованием правильного синтаксиса css. " +
`Если вам нужно просмотреть HTML-код, к которому вы применяете CSS, откройте Obsidian Developer Console (${DEVICE.isIOS || DEVICE.isMacOS ? "CMD+OPT+i" : "CTRL+SHIFT+i"}) и введите следующую команду: ` +
'"ExcalidrawAutomate.mostRecentMarkdownSVG". Это отобразит последний SVG, сгенерированный Excalidraw. ' +
"Установка font-family в css имеет свои ограничения. По умолчанию доступны только стандартные шрифты вашей операционной системы (подробнее см. в README). " +
"Вы можете добавить еще один пользовательский шрифт, используя настройки выше. " +
'Вы можете переопределить эту настройку css, добавив следующий frontmatter-ключ во встроенный файл разметки: "excalidraw-css: css_file_in_vault|css-snippet".',
EMBED_HEAD: "Встраивание Excalidraw в заметки и экспорт",
EMBED_DESC: `В настройках "Вставка и экспорт" можно настроить вставку и экспорт изображений и рисунков Excalidraw в документы. Основные настройки включают выбор типа изображения для предварительного просмотра в формате разметки (например, Native SVG или PNG), указание типа файла для вставки в документ (оригинальный Excalidraw, PNG или SVG) и управление кэшированием изображений для вставки в разметку. Вы также можете управлять размерами изображений, вставлять рисунки с помощью ссылок на вики или ссылок на разметку, а также настраивать темы изображений, цвета фона и интеграцию с Obsidian.
Кроме того, есть настройки автоэкспорта, который автоматически генерирует файлы SVG и/или PNG, соответствующие названию ваших рисунков Excalidraw, сохраняя их синхронизацию при переименовании и удалении файлов.`,
EMBED_CANVAS: "Поддержка Obsidian Canvas",
EMBED_CANVAS_NAME: "Иммерсивное встраивание",
EMBED_CANVAS_DESC:
"Скрывайте границы и фон узлов холста при встраивании чертежа Excalidraw в холст. " +
"Обратите внимание, что для создания полностью прозрачного фона изображения вам все равно придется настроить Excalidraw на экспорт изображений с прозрачным фоном.",
EMBED_CACHING: "Кэширование изображений",
EXPORT_SUBHEAD: "Настройки экспорта",
EMBED_SIZING: "Размер изображения",
EMBED_THEME_BACKGROUND: "Тема изображения и цвет фона",
EMBED_IMAGE_CACHE_NAME: "Кэширование изображений для вставки в markdown",
EMBED_IMAGE_CACHE_DESC: "Кэшируйте изображения для вставки в markdown. Это ускорит процесс встраивания, но в случае, если вы составите изображения из нескольких чертежей-субкомпонентов, " +
"встроенное изображение в Markdown не будет обновляться, пока вы не откроете рисунок и не сохраните его, чтобы вызвать обновление кэша.",
SCENE_IMAGE_CACHE_NAME: "Кэширование вложенных Excalidraws в Cцене",
SCENE_IMAGE_CACHE_DESC: "Кэшируйте вложенные Excalidraws в сцене для ускорения рендеринга сцены. Это ускорит процесс рендеринга, особенно если в сцене есть глубоко вложенные Excalidraw. " +
"Excalidraw попытается интеллектуально определить, изменились ли дочерние элементы вложенного Excalidraw, и соответствующим образом обновит кэш. " +
"Вы можете отключить эту функцию, если у вас есть подозрения, что кэш обновляется неправильно.",
EMBED_IMAGE_CACHE_CLEAR: "Очистка кэша",
BACKUP_CACHE_CLEAR: "Очистка резервных копий",
BACKUP_CACHE_CLEAR_CONFIRMATION: "Это действие удалит все резервные копии чертежей Excalidraw. Резервные копии используются в качестве меры безопасности на случай повреждения файла рисунка. Каждый раз, когда вы открываете Obsidian, плагин автоматически удаляет резервные копии файлов, которые больше не существуют в вашем хранилище. Вы уверены, что хотите удалить все резервные копии?",
EMBED_REUSE_EXPORTED_IMAGE_NAME: "Если найдено, используйте уже экспортированное изображение для предварительного просмотра",
EMBED_REUSE_EXPORTED_IMAGE_DESC:
"Эта настройка работает в сочетании с настройкой <a href='#«+TAG_AUTOEXPORT+»'>Автоэкспорт SVG/PNG</a>. Если имеется экспортированное изображение, соответствующее имени файла чертежа, используйте это изображение вместо того, " +
"чтобы генерировать изображение предварительного просмотра на лету. Однако это позволит ускорить предварительный просмотр, особенно если в чертеже много встроенных объектов, " +
"может случиться так, что последние изменения не будут отображаться, а изображение не будет автоматически соответствовать вашей теме Obsidian, " +
"если вы изменили тему Obsidian с момента создания экспорта. Эта настройка применяется только для вставки изображений в документы markdown. " +
"По ряду причин этот же подход не может быть использован для ускорения загрузки чертежей с большим количеством встроенных объектов. Смотрите демонстрацию <a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/1.6.23' target='_blank'>здесь</a>.",
/*EMBED_PREVIEW_SVG_NAME: "Отображение SVG в предварительном просмотре разметки (markdown)",
EMBED_PREVIEW_SVG_DESC:
"<b><u>Переключатель ВКЛ:</u></b> Вставьте рисунок как изображение <a href='https://en.wikipedia.org/wiki/Scalable_Vector_Graphics' target='_blank'>SVG</a> в предварительный просмотр разметки (markdown).<br>" +
"<b><u>Переключатель ВЫКЛ:</u></b> Встроить рисунок как изображение <a href='' target='_blank'>PNG</a>. Обратите внимание, что некоторые из <a href='https://www.youtube.com/watch?v=yZQoJg2RCKI&t=633s' target='_blank'>функций ссылок на блоки изображений</a> не работают с встраиванием PNG.",*/
EMBED_PREVIEW_IMAGETYPE_NAME: "Тип изображения в предварительном просмотре разметки (markdown)",
EMBED_PREVIEW_IMAGETYPE_DESC:
"<b><u>Родной SVG</u></b>: Высокое качество изображения. Встраиваемые веб-сайты, видео с YouTube, ссылки на Obsidian и внешние изображения, вставленные через URL-адрес, будут работать. Встроенные страницы Obsidian не будут<br>" +
"<b><u>SVG-изображение</u></b>: Высокое качество изображений. Встроенные элементы и изображения, вставленные по URL, имеют только заполнители, ссылки не работают<br>" +
"<b><u>PNG-изображение</u></b>: Более низкое качество изображения, но в некоторых случаях лучшая производительность при работе с большими рисунками. Встроенные элементы и изображения, вставленные по URL, имеют только заполнители, ссылки не работают. Также некоторые функции <a href='https://www.youtube.com/watch?v=yZQoJg2RCKI&t=633s' target='_blank'>ссылки на блок изображений</a> не работают с PNG-вставками.",
PREVIEW_MATCH_OBSIDIAN_NAME: "Предварительный просмотр Excalidraw в соответствии с темой Obsidian",
PREVIEW_MATCH_OBSIDIAN_DESC:
"Предварительный просмотр изображений в документах должен соответствовать теме Obsidian. Если эта функция включена, то когда Obsidian находится в темном режиме, изображения Excalidraw будут отображаться в темном режиме. " +
"Когда Obsidian находится в режиме освещения, Excalidraw также будет рендерить в режиме освещения. Вы можете отключить функцию 'Экспортировать изображение с фоном', чтобы получить более интегрированный в Obsidian вид и ощущение.",
EMBED_WIDTH_NAME: "Ширина по умолчанию для встроенного ('включенного') изображения",
EMBED_WIDTH_DESC:
"Ширина по умолчанию для встроенного рисунка. Это относится к режиму редактирования и чтения, а также к предварительным просмотрам при наведении. При вставке изображения можно указать его " +
"ширину используя <code>![[drawing.excalidraw|100]]</code> или " +
"<code>[[drawing.excalidraw|100x100]]</code> формат.",
EMBED_HEIGHT_NAME: "Высота по умолчанию для встроенного ('включенного') изображения",
EMBED_HEIGHT_DESC:
"Высота по умолчанию для встроенного рисунка. Это относится к режиму редактирования и чтения, а также к предварительным просмотрам при наведении. При вставке изображения можно указать его " +
"высоту используя <code>![[drawing.excalidraw|100]]</code> или " +
"<code>[[drawing.excalidraw|100x100]]</code> формат.",
EMBED_TYPE_NAME: "Тип файла для вставки в документ",
EMBED_TYPE_DESC:
"Когда вы вставляете изображение в документ с помощью командной палитры, этот параметр определяет, должен ли Excalidraw вставлять оригинальный файл Excalidraw " +
"или копию PNG или SVG. Чтобы эти типы изображений были доступны в раскрывающемся списке, их необходимо включить <a href='#"+TAG_AUTOEXPORT+"'>auto-export PNG / SVG</a> (см. ниже в разделе 'Настройки экспорта'). Для чертежей, не имеющих соответствующего PNG или " +
"SVG, действие из палитры команд вставит неработающую ссылку. Необходимо открыть исходный чертеж и инициировать экспорт вручную. " +
"Эта опция не будет автоматически генерировать файлы PNG/SVG, а просто будет ссылаться на уже существующие файлы.",
EMBED_MARKDOWN_COMMENT_NAME: "Вставить ссылку на чертеж как комментари",
EMBED_MARKDOWN_COMMENT_DESC:
"Вставьте ссылку на исходный файл Excalidraw в виде ссылки в формате markdown под изображением, например: <code>%%[[drawing.excalidraw]]%%</code>.<br>" +
"Вместо добавления комментария можно также выделить встроенную строку SVG или PNG и использовать действие из палитры команд: " +
"'<code>Excalidraw: Open Excalidraw drawing</code>' чтобы открыть чертеж.",
EMBED_WIKILINK_NAME: "Встраивание рисунка с помощью ссылки Wiki",
EMBED_WIKILINK_DESC: "<b><u>Переключатель ВКЛ:</u></b> Excalidraw будет встраивать [[wiki link]].<br><b><u>Переключатель ВЫКЛ:</u></b> Excalidraw будет встраивать [markdown](link).",
EXPORT_PNG_SCALE_NAME: "Масштаб экспортируемого изображения PNG",
EXPORT_PNG_SCALE_DESC: "Масштаб экспортируемого PNG-изображения",
EXPORT_BACKGROUND_NAME: "Экспорт изображения с фоном",
EXPORT_BACKGROUND_DESC: "Если отключить эту функцию, экспортируемое изображение будет прозрачным.",
EXPORT_PADDING_NAME: "Отступы изображений",
EXPORT_PADDING_DESC:
"Размер (в пикселях) вокруг экспортируемого изображения SVG или PNG. Для ссылок на clippedFrame значение Отступов равно 0." +
"Если кривые линии расположены близко к краю изображения, они могут быть обрезаны при экспорте. Вы можете увеличить это значение, чтобы избежать обрезки. " +
"Вы также можете отменить эту настройку на уровне файла, добавив ключ frontmatter <code>excalidraw-export-padding: 5<code>.",
EXPORT_THEME_NAME: "Экспорт изображения с темой",
EXPORT_THEME_DESC:
"Экспортируйте изображение, соответствующее темной/светлой теме вашего рисунка. Если отключить эту функцию, " +
"рисунки, созданные в темном режиме, будут отображаться так же, как и в светлом режиме. ",
EXPORT_EMBED_SCENE_NAME: "Встроить сцену в экспортированное изображение",
EXPORT_EMBED_SCENE_DESC:
"Вставка сцены Excalidraw в экспортируемое изображение. Можно переопределить на уровне файла, добавив ключ frontmatter. <code>excalidraw-export-embed-scene: true/false<code>. " +
"Настройка вступит в силу только при следующем (повторном) открытии чертежей.",
EXPORT_HEAD: "Настройки автоэкспорта",
EXPORT_SYNC_NAME: "Поддерживайте синхронизацию имен файлов .SVG и/или .PNG с файлом чертежа",
EXPORT_SYNC_DESC:
"Если плагин включен, он будет автоматически обновлять имена файлов .SVG и/или .PNG при переименовании чертежа в той же папке (и с тем же именем). " +
"Плагин также автоматически удалит файлы .SVG и/или .PNG при удалении рисунка в той же папке (и с тем же именем). ",
EXPORT_SVG_NAME: "Автоэкспорт SVG",
EXPORT_SVG_DESC:
"Автоматическое создание SVG-экспорта вашего чертежа, соответствующего названию файла. " +
"Плагин сохранит файл *.SVG в той же папке, что и чертеж. " +
"Встраивайте .svg-файл в документы вместо Excalidraw, делая вставки независимыми от платформы. " +
"Если переключатель автоэкспорта включен, этот файл будет обновляться каждый раз, когда вы редактируете чертеж Excalidraw с соответствующим именем. " +
"Вы можете отменить эту настройку на уровне файла, добавив ключ frontmatter <code>excalidraw-autoexport</code>.Допустимыми значениями для этого ключа являются" +
"<code>none</code>,<code>both</code>,<code>svg</code>, и <code>png</code>.",
EXPORT_PNG_NAME: "Автоэкспорт PNG",
EXPORT_PNG_DESC: "То же самое, что и автоэкспорт SVG, но для *.PNG",
EXPORT_BOTH_DARK_AND_LIGHT_NAME: "Экспорт изображения с темной и светлой тематикой",
EXPORT_BOTH_DARK_AND_LIGHT_DESC: "Если включить эту функцию, Excalidraw будет экспортировать два файла вместо одного: filename.dark.png, filename.light.png и/или filename.dark.svg и filename.light.svg.<br>" +
"Двойные файлы будут экспортированы как при включенном автоэкспорте SVG или PNG (или обоих), так и при нажатии кнопки экспорта на одном изображении.",
COMPATIBILITY_HEAD: "Особенности совместимости",
COMPATIBILITY_DESC: "Включать эти функции следует только в том случае, если у вас есть веские причины работать с файлами excalidraw.com, а не с файлами markdown. Многие функции плагина не поддерживаются в старых файлах. Типичным случаем может быть использование хранилища поверх папки проекта Visual Studio Code, а также наличие чертежей .excalidraw, к которым вы хотите получить доступ из Visual Studio Code. Другим примером может быть параллельное использование Excalidraw в Logseq и Obsidian.",
DUMMY_TEXT_ELEMENT_LINT_SUPPORT_NAME: "Совместимость с линтерами",
DUMMY_TEXT_ELEMENT_LINT_SUPPORT_DESC: "Excalidraw чувствителен к структуре файлов ниже <code># Excalidraw Data</code>. Автоматическая линтинговая обработка документов может создавать ошибки в Excalidraw Data. " +
"Хотя я приложил некоторые усилия, чтобы сделать загрузку данных устойчивой к изменениям линта," +
"это решение не является надежным.<br><mark>Лучше всего избегать линтинга или других автоматических изменений документов Excalidraw с помощью различных плагинов.</mark><br>" +
"Используйте эту настройку, если по уважительным причинам вы решили проигнорировать мою рекомендацию и настроили линтинг файлов Excalidraw.<br> " +
"Раздел <code>## Текстовые элементы</code> чувствителен к пустым строкам. Обычный подход к линтингу заключается в добавлении пустой строки после заголовков разделов. В случае Excalidraw это приведет к поломке/изменению первого текстового элемента в чертеже. " +
"Чтобы решить эту проблему, можно включить эту настройку. WhenЕсли она включена, Excalidraw добавит в начало фиктивный элемент, <code>## Текстовые элементы</code> который линтер может безопасно модифицировать." ,
PRESERVE_TEXT_AFTER_DRAWING_NAME: "Совместимость Zotero и Footnotes",
PRESERVE_TEXT_AFTER_DRAWING_DESC: "Сохраните текст после раздела ## Чертеж в файле Markdown. Это может незначительно повлиять на производительность при сохранении очень больших рисунков.",
DEBUGMODE_NAME: "Включить отладочные сообщения",
DEBUGMODE_DESC: "Я рекомендую перезапустить Obsidian после включения/выключения этой настройки. Это позволяет выводить отладочные сообщения в консоль. Это полезно для устранения неполадок. " +
"Если у вас возникли проблемы с плагином, пожалуйста, включите эту настройку, воспроизведите проблему и включите журнал консоли в проблему, которую вы поднимаете на <a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/issues'>GitHub</a>",
SLIDING_PANES_NAME: "Поддержка плагина раздвижных областей окна (Sliding Panes plugin)",
SLIDING_PANES_DESC:
"Чтобы это изменение вступило в силу, необходимо перезапустить Obsidian.<br>" +
"Если вы используете <a href='https://github.com/deathau/sliding-panes-obsidian' target='_blank'>Sliding Panes plugin</a> " +
"Вы можете включить эту настройку, чтобы чертежи Excalidraw работали с плагином Sliding Panes.<br>" +
"Обратите внимание, что поддержка раздвижных областей окна (Sliding Panes plugin) Excalidraw вызывает проблемы совместимости с рабочими пространствами Obsidian.<br>" +
"Обратите внимание, что функция 'Stack Tabs' теперь доступна в Obsidian, обеспечивая встроенную поддержку большинства функций раздвижных областей окна (Sliding Panes plugin)",
EXPORT_EXCALIDRAW_NAME: "Автоэкспорт Excalidraw",
EXPORT_EXCALIDRAW_DESC: "Аналогично автоэкспорту SVG, но для *.Excalidraw",
SYNC_EXCALIDRAW_NAME: "Синхронизация *.excalidraw с *.md-версией одного и того же чертежа",
SYNC_EXCALIDRAW_DESC:
"Если дата изменения файла *.excalidraw более поздняя, чем дата изменения файла *.md " +
"то обновите чертеж в файле .md на основе файла .excalidraw",
COMPATIBILITY_MODE_NAME: "Новые чертежи в виде устаревших файлов",
COMPATIBILITY_MODE_DESC:
"⚠️ Включайте эту функцию, только если вы знаете, что делаете. В 99,9% случаев включать эту функцию НЕ нужно. " +
"При включении этой функции рисунки, которые вы создаете с помощью значка ленты, действий палитры команд, " +
"и в файловом проводнике, будут все старые файлы *.excalidraw. Эта настройка также отключит напоминание" +
"при открытии устаревшего файла для редактирования.",
MATHJAX_NAME: "Хост библиотеки javascript MathJax (LaTeX)",
MATHJAX_DESC: "Если вы используете уравнения LaTeX в Excalidraw, то плагину необходимо загрузить библиотеку javascript для этого. " +
"Некоторые пользователи не могут получить доступ к определенным хост-серверам. Если у вас возникли проблемы, попробуйте сменить хост здесь. "+
"Возможно, вам придется перезапустить Obsidian после закрытия настроек, чтобы это изменение вступило в силу.",
LATEX_DEFAULT_NAME: "Формула LaTeX по умолчанию для новых уравнений",
LATEX_DEFAULT_DESC: "Оставьте пустым, если вам не нужна формула по умолчанию. Здесь можно добавить форматирование по умолчанию, например <code>\\color{white}</code>.",
NONSTANDARD_HEAD: "Поддерживаемые функции, не с Excalidraw.com",
NONSTANDARD_DESC: `Эти настройки в разделе "Поддерживаемые функции, не относящиеся к Excalidraw.com" предоставляют возможности настройки, выходящие за рамки стандартных функций Excalidraw.com. Эти функции недоступны на сайте excalidraw.com. При экспорте чертежа в Excalidraw.com эти функции будут выглядеть иначе.
Вы можете настроить количество пользовательских ручек, отображаемых рядом с меню Obsidian на холсте, что позволит вам выбирать из множества вариантов. Кроме того, можно включить опцию локального шрифта, которая добавляет локальный шрифт в список шрифтов на панели свойств элементов для текстовых элементов. `,
RENDER_TWEAK_HEAD: "Улучшения рендеринга",
MAX_IMAGE_ZOOM_IN_NAME: "Максимальное разрешение увеличения изображения",
MAX_IMAGE_ZOOM_IN_DESC: "В целях экономии памяти и из-за того, что Apple Safari (Obsidian на iOS) имеет некоторые жестко закодированные ограничения, Excalidraw.com ограничивает максимальное разрешение изображений и крупных объектов при увеличении. Вы можете обойти это ограничение с помощью мультипликатора. " +
"Это означает, что вы умножаете предел, установленный по умолчанию в Excalidraw. Чем больше множитель, тем лучше будет разрешение увеличения изображения, и тем больше памяти оно будет потреблять. " +
"Я рекомендую поиграть с несколькими значениями этой настройки. Вы знаете, что натолкнулись на стену, когда при увеличении масштаба PNG-изображения оно вдруг исчезает из поля зрения. Значение по умолчанию - 1. Настройка не влияет на iOS.",
CUSTOM_PEN_HEAD: "Пользовательские Ручки",
CUSTOM_PEN_NAME: "Количество пользовательских ручек",
CUSTOM_PEN_DESC: "Вы увидите эти ручки рядом с меню Obsidian на холсте. Вы можете настроить ручки на холсте, долго нажимая на кнопку ручки.",
EXPERIMENTAL_HEAD: "Разные возможности",
EXPERIMENTAL_DESC: `Среди прочих возможностей Excalidraw - установка формул LaTeX по умолчанию для новых уравнений, включение Предложение полей (Suggester) для автозаполнения, отображение индикаторов типов файлов Excalidraw, включение иммерсивного встраивания изображений в режиме предварительного просмотра и эксперименты с оптическим распознаванием символов Taskbone для извлечения текста из изображений и чертежей. Пользователи также могут ввести API-ключ Taskbone для расширенного использования сервиса OCR.`,
EA_HEAD: "Автоматизация Excalidraw",
EA_DESC:
"Excalidraw Автоматизация - это скриптовый и автоматизированный API для Excalidraw. К сожалению, документация по API скудна. " +
"Рекомендую прочитать <a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/docs/API/ExcalidrawAutomate.d.ts'>ExcalidrawAutomate.d.ts</a> файл, " +
"посетить <a href='https://zsviczian.github.io/obsidian-excalidraw-plugin/'>ExcalidrawAutomate How-to</a> страницу - хотя информация " +
"здесь давно не обновлялся, - и, наконец, включите расположенный ниже Предложитель полей. Предложитель полей покажет вам доступные " +
"функции, их параметры и краткое описание по мере ввода. Предложитель полей - это самая актуальная документация по API.",
FIELD_SUGGESTER_NAME: "Включить Предложение полей (Suggester)",
FIELD_SUGGESTER_DESC:
"Предложение полей (Suggester) позаимствован у плагинов Breadcrumbs и Templater. Предложение полей (Suggester) полей будет показывать " +
"меню автозаполнения при вводе текста с описанием функций <code>excalidraw-</code> или <code>ea.</code> в качестве подсказок для отдельных элементов в списке.",
STARTUP_SCRIPT_NAME: "Сценарий запуска",
STARTUP_SCRIPT_DESC:
"Если этот параметр установлен, excalidraw будет выполнять скрипт при запуске плагина. Это полезно, если вы хотите установить какой-либо из крючков Excalidraw Automate. " +
"Скрипт запуска - это файл в формате markdown, который должен содержать код javascript, который вы хотите выполнять при запуске Excalidraw.",
STARTUP_SCRIPT_BUTTON_CREATE: "Создание сценария запуска",
STARTUP_SCRIPT_BUTTON_OPEN: "Открыть сценарий запуска",
STARTUP_SCRIPT_EXISTS: "Файл сценария запуска уже существует",
FILETYPE_NAME: "Тип отображения (✏️) для файлов excalidraw.md в Файловом Проводнике",
FILETYPE_DESC: "Файлы Excalidraw получат индикатор с помощью эмодзи или текста, заданного в следующей настройке.",
FILETAG_NAME: "Установка типа индикатора для файлов excalidraw.md",
FILETAG_DESC: "Текст или эмодзи для отображения в качестве типа индикатора.",
INSERT_EMOJI: "Вставьте эмодзи",
LIVEPREVIEW_NAME: "Встраивание изображений в режиме предварительного просмотра в реальном времени",
LIVEPREVIEW_DESC:
"Включите этот параметр для поддержки стилей вставки изображений, таких как ![[drawing|width|style]], в режиме редактирования живого предварительного просмотра. " +
"Настройка не повлияет на открытые в данный момент документы. Чтобы изменения вступили в силу, необходимо закрыть открытые документы и" +
"открыть их снова.",
FADE_OUT_EXCALIDRAW_MARKUP_NAME: "Затухание разметки Excalidraw",
FADE_OUT_EXCALIDRAW_MARKUP_DESC: "В режиме просмотра Markdown раздел после комментария %% исчезает. " +
"Текст остается на месте, но визуальный беспорядок уменьшается. Обратите внимание, вы можете поместить %% в строку прямо над #Элементы текста, " +
"в этом случае вся разметка рисунка исчезнет, включая #Элементы текста. Побочным эффектом будет то, что вы не сможете блокировать текст ссылок в других примечаниях, то есть после секции комментариев %%. Это редко является проблемой. " +
"Если вы захотите отредактировать сценарий разметки Excalidraw, просто переключитесь в режим просмотра разметки и временно удалите комментарий %%.",
EXCALIDRAW_PROPERTIES_NAME: "Загрузка свойств Excalidraw в Obsidian Suggester",
EXCALIDRAW_PROPERTIES_DESC: "Отключите этот параметр, чтобы при запуске плагина свойства документа Excalidraw загружались в предложение свойств Obsidian. "+
"Включение этой функции упрощает использование свойств титульного листа Excalidraw, позволяя использовать множество мощных настроек. Если вы предпочитаете не загружать эти свойства автоматически, " +
"Вы можете отключить эту функцию, но при этом вам придется вручную удалить все ненужные свойства из предложения. " +
"Обратите внимание, что включение этой настройки требует перезапуска плагина, так как свойства загружаются при запуске.",
CUSTOM_FONT_HEAD: "Локальный шрифт",
ENABLE_FOURTH_FONT_NAME: "Включите опцию локального шрифта",
ENABLE_FOURTH_FONT_DESC:
"Включение этой опции добавит локальный шрифт в список шрифтов на панели свойств для текстовых элементов. " +
"Имейте в виду, что использование локального шрифта может нарушить независимость от платформы. " +
"Файлы, использующие пользовательский шрифт, могут отображаться по-разному при открытии в другом хранилище или в более позднее время, в зависимости от настроек шрифта. " +
"Кроме того, на сайте excalidraw.com или других версиях Excalidraw 4-й шрифт по умолчанию будет соответствовать системному шрифту.",
FOURTH_FONT_NAME: "Локальный файл шрифта",
FOURTH_FONT_DESC:
"Выберите файл шрифта .otf, .ttf, .woff или .woff2 из своего хранилища, чтобы использовать его в качестве локального шрифта. " +
"Если файл не выбран, Excalidraw по умолчанию использует шрифт Virgil. " +
"Для оптимальной производительности рекомендуется использовать файл .woff2, так как Excalidraw закодирует только необходимые глифы при экспорте изображений в SVG. " +
"Другие форматы шрифтов будут встраивать весь шрифт в экспортируемый файл, что может привести к значительному увеличению размера файла.",
SCRIPT_SETTINGS_HEAD: "Настройки для установленных сценариев",
SCRIPT_SETTINGS_DESC: "Некоторые сценарии Excalidraw Automate Scripts включают в себя настройки. Настройки упорядочены по сценариям. Настройки станут видны в этом списке только после того, как вы один раз выполните загруженный скрипт.",
TASKBONE_HEAD: "Taskbone Оптический распознаватель символов",
TASKBONE_DESC: "Это экспериментальная интеграция оптического распознавания символов в Excalidraw. Обратите внимание, что taskbone - это независимый внешний сервис, не предоставляемый ни Excalidraw, ни проектом плагинов Excalidraw-Obsidian. " +
"Сервис OCR выхватывает разборчивый текст из произвольных линий и встроенных изображений на вашем холсте и помещает распознанный текст на передний план вашего рисунка, а также в буфер обмена. " +
"Наличие текста во frontmatter позволит вам искать в Obsidian их текстовое содержание. " +
"Обратите внимание, что процесс извлечения текста из изображения происходит не локально, а через онлайн API. Сервис taskbone хранит изображение на своих серверах только до тех пор, пока это необходимо для извлечения текста. Однако если вас это не устраивает, не используйте эту функцию.",
TASKBONE_ENABLE_NAME: "Включить Taskbone",
TASKBONE_ENABLE_DESC: "Включая эту услугу, вы соглашаетесь с <a href='https://www.taskbone.com/legal/terms/' target='_blank'>Условиями использования Taskbone </a> и " +
"<a href='https://www.taskbone.com/legal/privacy/' target='_blank'>политикой конфиденциальности</a>.",
TASKBONE_APIKEY_NAME: "Taskbone API Ключ",
TASKBONE_APIKEY_DESC: "Taskbone предлагает бесплатную услугу с разумным количеством сканирований в месяц. Если вы хотите использовать эту функцию чаще, или вам необходимо повысить " +
"разработчика Taskbone (как вы можете себе представить, не существует такого понятия, как «бесплатно», предоставление этого потрясающего сервиса OCR стоит разработчику Taskbone определенных денег), вы можете " +
"приобрести платный API-ключ на сайте <a href='https://www.taskbone.com/' target='_blank'>taskbone.com</a>. Если вы уже приобрели ключ, просто перезапишите этот автоматически сгенерированный бесплатный API-ключ своим платным ключом.",
//HotkeyEditor
HOTKEY_PRESS_COMBO_NANE: "Нажмите комбинацию горячих клавиш",
HOTKEY_PRESS_COMBO_DESC: "Пожалуйста, нажмите нужную комбинацию клавиш",
HOTKEY_BUTTON_ADD_OVERRIDE: "Добавить новое переопределение",
HOTKEY_BUTTON_REMOVE: "Удалить",
//openDrawings.ts
SELECT_FILE: "Выберите файл и нажмите Enter.",
SELECT_COMMAND: "Выберите команду и нажмите Enter.",
SELECT_FILE_WITH_OPTION_TO_SCALE: `Выберите файл и нажмите ENTER, или ${labelSHIFT()}+${labelMETA()}+ENTER для вставки в масштабе 100%.`,
NO_MATCH: "Ни один файл не соответствует вашему запросу.",
NO_MATCHING_COMMAND: "Ни одна команда не соответствует вашему запросу.",
SELECT_FILE_TO_LINK: "Выберите файл, для которого нужно вставить ссылку.",
SELECT_COMMAND_PLACEHOLDER: "Выберите команду, для которой нужно вставить ссылку.",
SELECT_DRAWING: "Выберите изображение или рисунок, который необходимо вставить.",
TYPE_FILENAME: "Введите название чертежа для выбора.",
SELECT_FILE_OR_TYPE_NEW: "Выберите существующий чертеж или введите имя нового чертежа, затем нажмите Enter.",
SELECT_TO_EMBED: "Выберите чертеж для вставки в активный документ.",
SELECT_MD: "Выберите документ в формате markdown для вставки.",
SELECT_PDF: "Выберите документ PDF для вставки.",
PDF_PAGES_HEADER: "Страницы для загрузки?",
PDF_PAGES_DESC: "Формат: 1, 3-5, 7, 9-11",
//SelectCard.ts
TYPE_SECTION: "Введите название раздела для выбора.",
SELECT_SECTION_OR_TYPE_NEW: "Выберите существующий раздел или введите название нового раздела, затем нажмите Enter.",
INVALID_SECTION_NAME: "Недопустимое название раздела.",
EMPTY_SECTION_MESSAGE: "Введите название раздела и нажмите Enter, чтобы создать новый раздел.",
//EmbeddedFileLoader.ts
INFINITE_LOOP_WARNING: "ПРЕДУПРЕЖДЕНИЕ EXCALIDRAW\nОшибка при загрузке встроенных изображений из-за бесконечного цикла в файле:\n",
//Scripts.ts
SCRIPT_EXECUTION_ERROR: "Ошибка выполнения сценария. Пожалуйста, найдите сообщение об ошибке в консоли разработчика.",
//ExcalidrawData.ts
LOAD_FROM_BACKUP: "Файл Excalidraw был поврежден. Загрузка из резервного файла.",
//ObsidianMenu.tsx
GOTO_FULLSCREEN: "Переход в полноэкранный режим",
EXIT_FULLSCREEN: "Выход из полноэкранного режима",
TOGGLE_FULLSCREEN: "Переключить полноэкранный режим",
TOGGLE_DISABLEBINDING: "Переключить инвертирование поведения привязки по умолчанию",
TOGGLE_FRAME_RENDERING: "Переключить рендеринг кадра",
TOGGLE_FRAME_CLIPPING: "Переключить обрезку кадра",
OPEN_LINK_CLICK: "Открыть ссылку",
OPEN_LINK_PROPS: "Открыть ссылку на изображение или редактор формул LaTeX",
//IFrameActionsMenu.tsx
NARROW_TO_HEADING: "Узкий к заголовку...",
NARROW_TO_BLOCK: "Сузить до блока...",
SHOW_ENTIRE_FILE: "Показать весь файл",
ZOOM_TO_FIT: "Увеличить до нужного размера",
RELOAD: "Перезагрузить исходную ссылку",
OPEN_IN_BROWSER: "Открыть текущую ссылку в браузере",
PROPERTIES: "Свойства",
COPYCODE: "Копировать источник в буфер обмена",
//EmbeddableSettings.tsx
ES_TITLE: "Настройки встраиваемых элементов",
ES_RENAME: "Переименовать файл",
ES_ZOOM: "Масштабирование встраиваемого контента",
ES_YOUTUBE_START: "Время начала YouTube",
ES_YOUTUBE_START_DESC: "ss, mm:ss, hh:mm:ss",
ES_YOUTUBE_START_INVALID: "Время начала YouTube недействительно. Проверьте формат и повторите попытку.",
ES_FILENAME_VISIBLE: "Видимое имя файла",
ES_BACKGROUND_HEAD: "Цвет фона встроенной заметки",
ES_BACKGROUND_MATCH_ELEMENT: "Соответствие фонового цвета элемента",
ES_BACKGROUND_MATCH_CANVAS: "Соответствие цвета фона холста",
ES_BACKGROUND_COLOR: "Цвет фона",
ES_BORDER_HEAD: "Цвет границы встроенной заметки",
ES_BORDER_COLOR: "Цвет границы",
ES_BORDER_MATCH_ELEMENT: "Цвет границы элемента",
ES_BACKGROUND_OPACITY: "Непрозрачность фона",
ES_BORDER_OPACITY: "Непрозрачность границы",
ES_EMBEDDABLE_SETTINGS: "Настройки встраиваемой разметки",
ES_USE_OBSIDIAN_DEFAULTS: "Использовать настройки Obsidian по умолчанию",
ES_ZOOM_100_RELATIVE_DESC: "Кнопка настроит масштаб элемента так, чтобы он отображал содержимое на 100% относительно текущего уровня масштабирования холста",
ES_ZOOM_100: "Относительный 100%",
//Prompts.ts
PROMPT_FILE_DOES_NOT_EXIST: "Файл не существует. Вы хотите его создать?",
PROMPT_ERROR_NO_FILENAME: "Ошибка: Имя нового файла не может быть пустым",
PROMPT_ERROR_DRAWING_CLOSED: "Неизвестная ошибка. Похоже, что ваш чертеж был закрыт или файл чертежа отсутствует",
PROMPT_TITLE_NEW_FILE: "Новый файл",
PROMPT_TITLE_CONFIRMATION: "Подтверждение",
PROMPT_BUTTON_CREATE_EXCALIDRAW: "Создать EX",
PROMPT_BUTTON_CREATE_EXCALIDRAW_ARIA: "Создать чертеж Excalidraw и открыть его в новой вкладке",
PROMPT_BUTTON_CREATE_MARKDOWN: "Создать MD",
PROMPT_BUTTON_CREATE_MARKDOWN_ARIA: "Создать документ в формате markdown и открыть его в новой вкладке",
PROMPT_BUTTON_EMBED_MARKDOWN: "Встроить MD",
PROMPT_BUTTON_EMBED_MARKDOWN_ARIA: "Замена выбранного элемента встроенным документом с разметкой",
PROMPT_BUTTON_NEVERMIND: "Неважно",
PROMPT_BUTTON_OK: "OK",
PROMPT_BUTTON_CANCEL: "Отменить",
PROMPT_BUTTON_INSERT_LINE: "Вставить новую строку",
PROMPT_BUTTON_INSERT_SPACE: "Вставить пробел",
PROMPT_BUTTON_INSERT_LINK: "Вставить ссылку на файл в формате markdown",
PROMPT_BUTTON_UPPERCASE: "Прописные буквы",
PROMPT_SELECT_TEMPLATE: "Выберите шаблон",
//ModifierKeySettings
WEB_BROWSER_DRAG_ACTION: "Действие перетаскивания веб-браузера",
LOCAL_FILE_DRAG_ACTION: "Действие перетаскивания локального файла ОС",
INTERNAL_DRAG_ACTION: "Внутреннее действие перетаскивания в Obsidian",
PANE_TARGET: "Поведение при нажатии на ссылку",
DEFAULT_ACTION_DESC: "Если ни одна из комбинаций не применяется, для этой группы будет действовать действие по умолчанию: ",
//FrameSettings.ts
FRAME_SETTINGS_TITLE: "Настройки кадров",
FRAME_SETTINGS_ENABLE: "Включить кадры",
FRAME_SETTIGNS_NAME: "Отображение имени кадра",
FRAME_SETTINGS_OUTLINE: "Отображение контура кадра",
FRAME_SETTINGS_CLIP: "Включить обрезку кадра",
//InsertPDFModal.ts
IPM_PAGES_TO_IMPORT_NAME: "Страницы для импорта",
IPM_SELECT_PAGES_TO_IMPORT: "Пожалуйста, выберите страницы для импорта",
IPM_ADD_BORDER_BOX_NAME: "Добавить рамку",
IPM_ADD_FRAME_NAME: "Добавить страницу в кадр",
IPM_ADD_FRAME_DESC: "Для удобства работы я рекомендую зафиксировать страницу внутри кадра. " +
"Однако если вы заблокировали страницу внутри кадра, то единственный способ разблокировать ее - щелкнуть правой кнопкой мыши кадр, выбрать пункт «Удалить элементы из кадра», а затем разблокировать страницу.",
IPM_GROUP_PAGES_NAME: "Страницы группы",
IPM_GROUP_PAGES_DESC: "Это позволит объединить все страницы в одну группу. Это рекомендуется делать, если вы блокируете страницы после импорта, потому что группу будет легче разблокировать позже, чем разблокировать каждую по отдельности.",
IPM_SELECT_PDF: "Пожалуйста, выберите файл PDF",
};

View File

@@ -1,12 +1,17 @@
import {
DEVICE,
FRONTMATTER_KEYS,
CJK_FONTS
} from "src/constants/constants";
import { TAG_AUTOEXPORT, TAG_MDREADINGMODE, TAG_PDFEXPORT } from "src/constants/constSettingsTags";
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/ModifierkeyHelper";
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/modifierkeyHelper";
declare const PLUGIN_VERSION:string;
// 简体中文
export default {
// Sugester
SELECT_FILE_TO_INSERT: "选择一个要插入的文件",
// main.ts
CONVERT_URL_TO_FILE: "从 URL 下载图像到本地",
UNZIP_CURRENT_FILE: "解压当前 Excalidraw 文件",
@@ -25,6 +30,7 @@ export default {
"脚本已是最新 - 点击重新安装",
OPEN_AS_EXCALIDRAW: "打开为 Excalidraw 绘图",
TOGGLE_MODE: "在 Excalidraw 和 Markdown 模式之间切换",
DUPLICATE_IMAGE: "复制选定的图像,并分配一个不同的图像 ID",
CONVERT_NOTE_TO_EXCALIDRAW: "转换:空白 Markdown 文档 => Excalidraw 绘图文件",
CONVERT_EXCALIDRAW: "转换: *.excalidraw => *.md",
CREATE_NEW: "新建绘图文件",
@@ -52,8 +58,8 @@ export default {
NEW_IN_POPOUT_WINDOW_EMBED: "新建绘图 - 于新窗口 - 并将其嵌入(形如 ![[drawing]])到当前 Markdown 文档中",
TOGGLE_LOCK: "文本元素原文模式RAW⟺ 预览模式PREVIEW",
DELETE_FILE: "从库中删除所选图像(或以图像形式嵌入绘图中的 Markdown的源文件",
COPY_ELEMENT_LINK: "复制所选元素的链接(形如 [[file#^id]]]",
COPY_DRAWING_LINK: "复制绘图的嵌入链接(形如 ![[darwing]]",
COPY_ELEMENT_LINK: "复制所选元素的链接(形如 [[file#^id]]",
COPY_DRAWING_LINK: "复制绘图的嵌入链接(形如 ![[drawing]]",
INSERT_LINK_TO_ELEMENT:
`复制所选元素为内部链接(形如 [[file#^id]] )。\n按住 ${labelCTRL()} 可复制元素所在分组为内部链接(形如 [[file#^group=id]] )。\n按住 ${labelSHIFT()} 可复制所选元素所在区域为内部链接(形如 [[file#^area=id]] )。`,
INSERT_LINK_TO_ELEMENT_GROUP:
@@ -75,6 +81,7 @@ export default {
IMPORT_SVG_CONTEXTMENU: "转换 SVG 到线条 - 有限制",
INSERT_MD: "插入 Markdown 文档(以图像形式嵌入)到当前绘图中",
INSERT_PDF: "插入 PDF 文档(以图像形式嵌入)到当前绘图中",
INSERT_LAST_ACTIVE_PDF_PAGE_AS_IMAGE: "将最后激活的 PDF 页面插入为图片",
UNIVERSAL_ADD_FILE: "插入任意文件(以交互形式嵌入,或者以图像形式嵌入)到当前绘图中",
INSERT_CARD: "插入“背景笔记”卡片",
CONVERT_CARD_TO_FILE: "将“背景笔记”卡片保存到文件",
@@ -84,21 +91,27 @@ export default {
`插入 LaTeX 公式(例如:\\binom{n}{k} = \\frac{n!}{k!(n-k)!})。`,
ENTER_LATEX: "输入 LaTeX 表达式",
READ_RELEASE_NOTES: "阅读本插件的更新说明",
RUN_OCR: "OCR 整画布:识别涂鸦和图片里的文本并复制到剪贴板和文档属性中",
RERUN_OCR: "重新运行 OCR 完整画笔:识别涂鸦和图片里的文本并复制到剪贴板和文档属性中",
RUN_OCR: "OCR 整画布:识别涂鸦和图片里的文本并复制到剪贴板和文档属性中",
RERUN_OCR: "重新 OCR 整个画布:识别涂鸦和图片里的文本并复制到剪贴板和文档属性中",
RUN_OCR_ELEMENTS: "OCR 选中的元素:识别涂鸦和图片里的文本并复制到剪贴板",
TRAY_MODE: "绘图工具属性页:面板模式 ⟺ 托盘模式",
SEARCH: "搜索文本",
CROP_PAGE: "对所选页面裁剪并添加蒙版",
CROP_IMAGE: "对图片裁剪并添加蒙版",
ANNOTATE_IMAGE : "在 Excalidraw 中标注图像",
INSERT_ACTIVE_PDF_PAGE_AS_IMAGE: "将当前激活的 PDF 页面作为图片插入",
INSERT_ACTIVE_PDF_PAGE_AS_IMAGE: "将当前激活的 PDF 页面作为图片插入",
RESET_IMG_TO_100: "重置图像元素的尺寸为 100%",
RESET_IMG_ASPECT_RATIO: "重置所选图像元素的纵横比",
TEMPORARY_DISABLE_AUTOSAVE: "临时禁用自动保存功能,直到本次 Obsidian 退出(小白慎用!)",
TEMPORARY_ENABLE_AUTOSAVE: "启用自动保存功能",
FONTS_LOADED : "Excalidraw: CJK 字体已加载" ,
FONTS_LOAD_ERROR : "Excalidraw: 在资源文件夹下找不到 CJK 字体\n" ,
//Prompt.ts
SELECT_LINK_TO_OPEN: "选择要打开的链接",
//ExcalidrawView.ts
ERROR_CANT_READ_FILEPATH : "错误,无法读取文件路径。正在改为导入文件",
NO_SEARCH_RESULT: "在绘图中未找到匹配的元素",
FORCE_SAVE_ABORTED: "自动保存被中止,因为文件正在保存中",
LINKLIST_SECOND_ORDER_LINK: "二级链接",
@@ -128,7 +141,10 @@ export default {
OPEN_LINK: "打开所选元素里的链接 \n按住 Shift 在新面板打开)",
EXPORT_EXCALIDRAW: "导出为 .excalidraw 文件(旧版绘图文件格式)",
LINK_BUTTON_CLICK_NO_TEXT:
"请选择一个含有链接的图形或文本元素。",
"请选择一个包含内部或外部链接的元素。\n",
LINEAR_ELEMENT_LINK_CLICK_ERROR:
"箭头和线元素的链接无法通过 " + labelCTRL() + " + 点击元素来导航,因为这也会激活线编辑器。\n" +
"请使用右键上下文菜单打开链接,或点击元素右上角的链接指示器。\n",
FILENAME_INVALID_CHARS:
'文件名不能含有以下符号: * " \\ < > : | ? #',
FORCE_SAVE:
@@ -181,10 +197,10 @@ export default {
NEWVERSION_NOTIFICATION_DESC:
"<b>开启:</b>当本插件存在可用更新时,显示通知。<br>" +
"<b>关闭:</b>您需要手动检查本插件的更新(设置 - 第三方插件 - 检查更新)。",
BASIC_HEAD: "基本",
BASIC_DESC: `包括:更新说明,更新提示,新绘图文件、模板文件、脚本文件的存储路径等的设置。`,
FOLDER_NAME: "Excalidraw 文件夹",
FOLDER_NAME: "Excalidraw 文件夹(區分大小寫!)",
FOLDER_DESC:
"新绘图的默认存储路径。若为空,将在库的根目录中创建新绘图。",
CROP_PREFIX_NAME: "剪贴文件的前缀",
@@ -198,10 +214,10 @@ export default {
ANNOTATE_PRESERVE_SIZE_NAME: "在标注时保留图像尺寸",
ANNOTATE_PRESERVE_SIZE_DESC:
"当在 Markdown 中标注图像时,替换后的图像链接将包含原始图像的宽度。",
CROP_FOLDER_NAME: "剪贴文件文件夹",
CROP_FOLDER_NAME: "剪贴文件文件夹(區分大小寫!)",
CROP_FOLDER_DESC:
"剪贴图像时创建新绘图的默认存储路径。如果留空,将按照 Vault 附件设置创建。",
ANNOTATE_FOLDER_NAME: "图片标注文件文件夹",
ANNOTATE_FOLDER_NAME: "图片标注文件文件夹(區分大小寫!)",
ANNOTATE_FOLDER_DESC:
"创建图片标注是的默认存储路径。如果留空,将按照 Vault 附件设置创建。",
FOLDER_EMBED_NAME:
@@ -210,7 +226,7 @@ export default {
"在命令面板中执行“新建绘图”系列命令时," +
"新建的绘图文件的存储路径。<br>" +
"<b>开启:</b>使用上面的 Excalidraw 文件夹。 <br><b>关闭:</b>使用 Obsidian 设置的新附件默认位置。",
TEMPLATE_NAME: "Excalidraw 模板文件",
TEMPLATE_NAME: "Excalidraw 模板文件(區分大小寫!)",
TEMPLATE_DESC:
"Excalidraw 模板文件(文件夹)的存储路径。<br>" +
"<b>模板文件:</b>比如:如果您的模板在默认的 Excalidraw 文件夹中且文件名是 " +
@@ -220,7 +236,7 @@ export default {
"在这种情况下,创建新绘图时将提示您选择使用哪个模板。<br>" +
"<b>专业提示:</b> 如果您正在使用 Obsidian Templater 插件,您可以将 Templater 代码添加到不同的" +
"Excalidraw 模板中,以自动配置您的绘图",
SCRIPT_FOLDER_NAME: "Excalidraw 自动化脚本的文件夹(大小写敏感",
SCRIPT_FOLDER_NAME: "Excalidraw 自动化脚本的文件夹(區分大小寫",
SCRIPT_FOLDER_DESC:
"此文件夹用于存放 Excalidraw 自动化脚本。" +
"您可以在 Obsidian 命令面板中执行这些脚本," +
@@ -315,7 +331,12 @@ FILENAME_HEAD: "文件名",
"该选项在兼容模式(即非 Excalidraw 专用 Markdown 文件)下不会生效。<br>" +
"<b>开启:</b>使用 .excalidraw.md 作为扩展名。<br><b>关闭:</b>使用 .md 作为扩展名。",
DISPLAY_HEAD: "界面 & 行为",
DISPLAY_DESC: "包括:左手模式,主题匹配,缩放,激光笔工具,修饰键等的设置。",
DISPLAY_DESC: "在 Excalidraw 设置的 '外观和行为' 部分,您可以微调 Excalidraw 的外观和行为。这包括动态样式、左手模式、匹配 Excalidraw 和 Obsidian 主题、默认模式等选项。",
OVERRIDE_OBSIDIAN_FONT_SIZE_NAME : "限制 Obsidian 字体大小为编辑器文本" ,
OVERRIDE_OBSIDIAN_FONT_SIZE_DESC :
"Obsidian 的自定义字体大小设置会影响整个界面,包括 Excalidraw 和依赖默认字体大小的主题。" +
"启用此选项将限制字体大小更改为编辑器文本,这将改善 Excalidraw 的外观。" +
"如果启用后发现界面的某些部分看起来不正确,请尝试关闭此设置。" ,
DYNAMICSTYLE_NAME: "动态样式",
DYNAMICSTYLE_DESC:
"根据画布颜色自动调节 Excalidraw 界面颜色",
@@ -349,6 +370,7 @@ FILENAME_HEAD: "文件名",
DEFAULT_PEN_MODE_DESC:
"打开绘图时,是否自动开启触控笔模式?",
DISABLE_DOUBLE_TAP_ERASER_NAME: "启用手写模式下的双击橡皮擦功能",
DISABLE_SINGLE_FINGER_PANNING_NAME: "启用手写模式下的单指平移功能",
SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_NAME: "在触控笔模式下显示十字准星(+",
SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_DESC:
"在触控笔模式下使用涂鸦功能会显示十字准星 <b><u>打开:</u></b> 显示 <b><u>关闭:</u></b> 隐藏<br>"+
@@ -364,13 +386,14 @@ FILENAME_HEAD: "文件名",
"此设置不会影响您在 Excalidraw 模式下的绘图显示,或者在将绘图嵌入 Markdown 文档时,或在渲染悬停预览时。<br><ul>" +
"<li>请参阅下面‘嵌入和导出’部分的 <a href='#"+TAG_PDFEXPORT+"'>PDF 导出</a> 相关设置。</li></ul><br>" +
"您必须关闭当前的 Excalidraw/Markdown 文件并重新打开,以使此更改生效。",
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_NAME: "在将 Excalidraw 文件导出为 PDF 时将文件渲染为图像",
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_NAME : "在 Obsidian 中导出为 PDF 格式时将 Excalidraw 渲染为图像" ,
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_DESC:
"处于 Markdown 视图模式时,此设置控制 Excalidraw 在使用 Obsidian 的 <b>导出为 PDF</b> 功能,将 Excalidraw 文件导出为 PDF 的行为。<br>" +
"<ul><li><b>启用</b> 时,PDF 将仅显示 Excalidraw 绘图</li>" +
"<li><b>禁用</b> 时,PDF 将显示文档的 Markdown 部分(背景笔记)。</li></ul>" +
"请参阅上面‘外观和行为’部分的 <<a href='#"+TAG_MDREADINGMODE+"'>>Markdown 阅读模式</a> 相关设置。" +
"⚠️ 注意,您必须关闭当前的 Excalidraw/Markdown 文件并重新打开,以使此更改生效。⚠️",
"此设置控制在使用 Obsidian 内置的<b>导出为 PDF</b>功能,如何将 Excalidraw 文件导出为 PDF。<br>" +
"<ul><li><b>启用</b>PDF 将包含图像格式的 Excalidraw 绘图</li>" +
"<li><b>禁用</b>PDF 将包含作为文本的 Markdown 内容。</li></ul>" +
"注意:此设置不会影响 Excalidraw 本身的 PDF 导出功能。<br>" +
"请参阅上方“外观和行为”部分中与<a href='#" + TAG_MDREADINGMODE + "'>Markdown 阅读模式</a>相关的其他设置。<br>" +
"⚠️ 您必须关闭并重新打开 Excalidraw/Markdown 文件,设置更改才会生效。⚠️",
HOTKEY_OVERRIDE_HEAD: "热键覆盖",
HOTKEY_OVERRIDE_DESC: `一些 Excalidraw 的热键,例如 ${labelCTRL()}+Enter 用于编辑文本,或 ${labelCTRL()}+K 用于创建元素链接。` +
"与 Obsidian 的热键设置发生冲突。您在下面添加的热键组合将在使用 Excalidraw 时覆盖 Obsidian 的热键设置," +
@@ -385,7 +408,7 @@ FILENAME_HEAD: "文件名",
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>禁用自动缩放。",
@@ -395,7 +418,16 @@ FILENAME_HEAD: "文件名",
ZOOM_TO_FIT_MAX_LEVEL_NAME: "自动缩放的最高级别",
ZOOM_TO_FIT_MAX_LEVEL_DESC:
"自动缩放画布时,允许放大的最高级别。该值不能低于 0.550%)且不能超过 101000%)。",
LASER_HEAD: "激光笔工具More Tools > Laser pointer",
PEN_HEAD: "手写笔",
GRID_HEAD: "网格",
GRID_DYNAMIC_COLOR_NAME: "动态网格颜色",
GRID_DYNAMIC_COLOR_DESC:
"<b><u>开启:</u></b>更改网格颜色以匹配画布颜色<br><b><u>关闭:</u></b>将以下颜色用作网格颜色",
GRID_COLOR_NAME: "网格颜色",
GRID_OPACITY_NAME: "网格透明度",
GRID_OPACITY_DESC: "网格透明度还将控制将箭头绑定到元素时绑定框的透明度。<br>"+
"设置网格的不透明度。 0 表示完全透明100 表示完全不透明。",
LASER_HEAD: "激光笔工具(更多工具 > 激光笔)",
LASER_COLOR: "激光笔颜色",
LASER_DECAY_TIME_NAME: "激光笔消失时间",
LASER_DECAY_TIME_DESC: "单位是毫秒,默认是 1000即 1 秒)。",
@@ -420,6 +452,7 @@ FILENAME_HEAD: "文件名",
LONG_PRESS_DESKTOP_DESC: "长按(以毫秒为单位)打开在 Markdown 文件中嵌入的 Excalidraw 绘图。",
LONG_PRESS_MOBILE_NAME: "长按打开(移动端)",
LONG_PRESS_MOBILE_DESC: "长按(以毫秒为单位)打开在 Markdown 文件中嵌入的 Excalidraw 绘图。",
DOUBLE_CLICK_LINK_OPEN_VIEW_MODE: "在查看模式下允许双击打开链接",
FOCUS_ON_EXISTING_TAB_NAME: "聚焦于当前标签页",
FOCUS_ON_EXISTING_TAB_DESC: "当打开一个链接时如果该文件已经打开Excalidraw 将会聚焦到现有的标签页上 " +
@@ -545,10 +578,14 @@ FILENAME_HEAD: "文件名",
此外,还有自动导出 SVG 或 PNG 文件并保持与绘图文件状态同步的设置。`,
EMBED_CANVAS: "Obsidian 白板支持",
EMBED_CANVAS_NAME: "沉浸式嵌入",
EMBED_CANVAS_DESC:
EMBED_CANVAS_DESC:
"当嵌入绘图到 Obsidian 白板中时,隐藏元素的边界和背景。" +
"注意:如果想要背景完全透明,您依然需要在 Excalidraw 中设置“导出的图像不包含背景”。",
EMBED_CACHING: "预览图缓存",
EMBED_CACHING : "图像缓存和渲染优化" ,
RENDERING_CONCURRENCY_NAME : "图像渲染并发性" ,
RENDERING_CONCURRENCY_DESC :
"用于图像渲染的并行工作线程数。增加此数值可以加快渲染速度,但可能会减慢系统的其他部分运行速度。" +
"默认值为 3。如果您的系统性能强大可以增加此数值。" ,
EXPORT_SUBHEAD: "导出",
EMBED_SIZING: "图像尺寸",
EMBED_THEME_BACKGROUND: "图像的主题和背景色",
@@ -556,7 +593,7 @@ FILENAME_HEAD: "文件名",
EMBED_IMAGE_CACHE_DESC: "可提高下次嵌入的速度。" +
"但如果绘图中又嵌入了子绘图,当子绘图改变时,您需要打开子绘图并手动保存,才能够更新父绘图的预览图。",
SCENE_IMAGE_CACHE_NAME: "缓存场景中嵌套的 Excalidraw",
SCENE_IMAGE_CACHE_DESC: "缓存场景中嵌套的 Excalidraw 以加快场景渲染速度。这将加快渲染过程,特别是在您的场景中有深度嵌套的 Excalidraw 时。" +
SCENE_IMAGE_CACHE_DESC: "缓存场景中嵌套的 Excalidraw 以加快场景渲染速度。这将加快渲染过程,特别是在您的场景中有深度嵌套的 Excalidraw 时。" +
"Excalidraw 将智能地尝试识别嵌套 Excalidraw 的子元素是否发生变化,并更新缓存。 " +
"如果您怀疑缓存未能正确更新,您可能需要关闭此功能。",
EMBED_IMAGE_CACHE_CLEAR: "清除缓存",
@@ -600,7 +637,7 @@ FILENAME_HEAD: "文件名",
"如果您选择了 PNG 或 SVG 副本,当副本不存在时,该命令将会插入一条损坏的链接,您需要打开绘图文件并手动导出副本才能修复 —— " +
"也就是说,该选项不会自动帮您生成 PNG/SVG 副本,而只会引用已有的 PNG/SVG 副本。",
EMBED_MARKDOWN_COMMENT_NAME: "将链接作为注释嵌入",
EMBED_MARKDOWN_COMMENT_DESC:
EMBED_MARKDOWN_COMMENT_DESC:
"在图像下方以 Markdown 链接的形式嵌入原始 Excalidraw 文件的链接,例如:<code>%%[[drawing.excalidraw]]%%</code>。<br>" +
"除了添加 Markdown 注释之外,您还可以选择嵌入的 SVG 或 PNG并使用命令面板" +
"'<code>Excalidraw: 打开 Excalidraw 绘图</code>'来打开该绘图",
@@ -625,6 +662,7 @@ FILENAME_HEAD: "文件名",
EXPORT_EMBED_SCENE_DESC:
"在导出的图像中嵌入 Excalidraw 场景。可以通过在文件级别添加 <code>excalidraw-export-embed-scene: true/false</code> frontmatter 元数据键来覆盖此设置。" +
"此设置仅在您下次(重新)打开绘图时生效。",
PDF_EXPORT_SETTINGS : "PDF 导出设置",
EXPORT_HEAD: "导出设置",
EXPORT_SYNC_NAME:
"保持 SVG/PNG 文件名与绘图文件同步",
@@ -653,7 +691,7 @@ FILENAME_HEAD: "文件名",
"如果出于某些合理的原因,您决定忽略我的建议并配置了 Excalidraw 文件的自动代码格式化,那么可以使用这个设置<br> " +
"<code>## Text Elements</code> 部分对空行很敏感。一种常见的代码格式化是在章节标题后添加一个空行。但对于 Excalidraw 来说,这将破坏/改变您绘图中的第一个文本元素。" +
"为了解决这个问题,您可以启用这个设置。启用后 Excalidraw 将在 <code>## Text Elements</code> 的开头添加一个虚拟元素,供自动代码格式化工具修改。" ,
PRESERVE_TEXT_AFTER_DRAWING_NAME: "Zotero 兼容性",
PRESERVE_TEXT_AFTER_DRAWING_NAME: "Zotero 和脚注footnotes兼容性",
PRESERVE_TEXT_AFTER_DRAWING_DESC: "保留 Markdown 文件中 <code>## Drawing</code> 部分之后的文本内容。保存非常大的绘图时,这可能会造成微小的性能影响。",
DEBUGMODE_NAME: "开启 debug 信息",
DEBUGMODE_DESC: "我建议在启用/禁用此设置后重新启动 Obsidian。这将在控制台中启用调试消息。这对于排查问题很有帮助。" +
@@ -679,11 +717,13 @@ FILENAME_HEAD: "文件名",
"文件浏览器等创建的绘图都将是旧格式(*.excalidraw。" +
"此外,您打开旧格式绘图文件时将不再收到警告消息。",
MATHJAX_NAME: "MathJax (LaTeX) 的 javascript 库服务器",
MATHJAX_DESC: "如果您在绘图中使用 LaTeX插件需要从服务器获取并加载一个 javascript 库。" +
MATHJAX_DESC: "如果您在绘图中使用 LaTeX插件需要从服务器获取并加载一个 javascript 库。" +
"如果您的网络无法访问某些库服务器,可以尝试通过此选项更换库服务器。"+
"更改此选项后,您可能需要重启 Obsidian 来使其生效。",
LATEX_DEFAULT_NAME: "插入 LaTeX 时的默认表达式",
LATEX_DEFAULT_DESC: "允许留空。允许使用类似 <code>\\color{white}</code> 的格式化表达式。",
LATEX_PREAMBLE_NAME : "LaTeX 前言文件(區分大小寫!)" ,
LATEX_PREAMBLE_DESC : "前言文件的完整路径,留空则使用默认值。如果文件不存在,此选项将被忽略。<br><strong>重要:</strong>更改后需要重新加载 Obsidian 才能生效!" ,
NONSTANDARD_HEAD: "非 Excalidraw.com 官方支持的特性",
NONSTANDARD_DESC: `这些特性不受 Excalidraw.com 官方支持。如果在 Excalidraw.com 导入绘图,这些特性将会发生不可预知的变化。
包括:自定义画笔工具的数量,自定义字体等。`,
@@ -698,7 +738,7 @@ FILENAME_HEAD: "文件名",
EXPERIMENTAL_HEAD: "杂项",
EXPERIMENTAL_DESC: `包括:默认的 LaTeX 公式字段建议绘图文件的类型标识符OCR 等设置。`,
EA_HEAD: "Excalidraw 自动化",
EA_DESC:
EA_DESC:
"ExcalidrawAutomate 是用于 Excalidraw 自动化脚本的 API但是目前说明文档还不够完善" +
"建议阅读 <a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/docs/API/ExcalidrawAutomate.d.ts'>ExcalidrawAutomate.d.ts</a> 文件源码," +
"参考 <a href='https://zsviczian.github.io/obsidian-excalidraw-plugin/'>ExcalidrawAutomate How-to</a> 网页(不过该网页" +
@@ -736,6 +776,8 @@ FILENAME_HEAD: "文件名",
"启用此功能简化了 Excalidraw 前置属性的使用,使您能够利用许多强大的设置。如果您不希望自动加载这些属性," +
"您可以禁用此功能,但您将需要手动从自动提示中移除任何不需要的属性。" +
"请注意,启用此设置需要重启插件,因为属性是在启动时加载的。",
FONTS_HEAD: "字体",
FONTS_DESC: "配置本地字体并下载的 CJK 字体以供 Excalidraw 使用。",
CUSTOM_FONT_HEAD: "本地字体",
ENABLE_FOURTH_FONT_NAME: "为文本元素启用本地字体",
ENABLE_FOURTH_FONT_DESC:
@@ -749,6 +791,20 @@ FILENAME_HEAD: "文件名",
"如果没有选择文件Excalidraw 将默认使用 Virgil 字体。"+
"为了获得最佳性能,建议使用 .woff2 文件,因为当导出到 SVG 格式的图像时Excalidraw 只会编码必要的字形。"+
"其他字体格式将在导出文件中嵌入整个字体,可能会导致文件大小显著增加。<mark>译者注:</mark>您可以在<a href='https://wangchujiang.com/free-font/' target='_blank'>Free Font</a>获取免费商用中文手写字体。",
OFFLINE_CJK_NAME: "离线 CJK 字体支持",
OFFLINE_CJK_DESC:
`<strong>您在这里所做的更改将在重启 Obsidian 后生效。</strong><br>
Excalidraw.com 提供手写风格的 CJK 字体。默认情况下,这些字体并未在插件中本地包含,而是从互联网获取。
如果您希望 Excalidraw 完全本地化,以便在没有互联网连接的情况下使用,可以从 <a href="https://github.com/zsviczian/obsidian-excalidraw-plugin/raw/refs/heads/master/assets/excalidraw-fonts.zip" target="_blank">GitHub 下载所需的字体文件</a>。
下载后,将内容解压到您的 Vault 中的一个文件夹内。<br>
预加载字体会影响启动性能。因此,您可以选择加载哪些字体。`,
CJK_ASSETS_FOLDER_NAME: "CJK 字体文件夹(區分大小寫!)",
CJK_ASSETS_FOLDER_DESC: `您可以在此设置 CJK 字体文件夹的位置。例如,您可以选择将其放置在 <code>Excalidraw/CJK Fonts</code> 下。<br><br>
<strong>重要:</strong> 请勿将此文件夹设置为 Vault 根目录!请勿在此文件夹中放置其他字体。<br><br>
<strong>注意:</strong> 如果您使用 Obsidian Sync 并希望在设备之间同步这些字体文件,请确保 Obsidian Sync 设置为同步“所有其他文件类型”。`,
LOAD_CHINESE_FONTS_NAME: "启动时从文件加载中文字体",
LOAD_JAPANESE_FONTS_NAME: "启动时从文件加载日文字体",
LOAD_KOREAN_FONTS_NAME: "启动时从文件加载韩文字体",
SCRIPT_SETTINGS_HEAD: "已安装脚本的设置",
SCRIPT_SETTINGS_DESC: "有些 Excalidraw 自动化脚本包含设置项,当执行这些脚本时,它们会在该列表下添加设置项。",
TASKBONE_HEAD: "Taskbone OCR光学符号识别",
@@ -760,7 +816,7 @@ FILENAME_HEAD: "文件名",
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_APIKEY_DESC: "Taskbone 的免费 API key 提供了一定数量的每月识别次数。如果您非常频繁地使用此功能,或者想要支持 " +
"Taskbone 的开发者您懂的没有人能用爱发电Taskbone 开发者也需要投入资金来维持这项 OCR 服务)您可以" +
"到 <a href='https://www.taskbone.com/' target='_blank'>taskbone.com</a> 购买一个商用 API key。购买后请将它填写到旁边这个文本框里替换掉原本自动生成的免费 API key。",
@@ -805,6 +861,35 @@ FILENAME_HEAD: "文件名",
//ExcalidrawData.ts
LOAD_FROM_BACKUP: "Excalidraw 文件已损坏。尝试从备份文件中加载。",
FONT_LOAD_SLOW: "正在加载字体...\n\n 这比预期花费的时间更长。如果这种延迟经常发生,您可以将字体下载到您的 Vault 中。\n\n" +
"(点击=忽略提示,右键=更多信息)",
FONT_INFO_TITLE: "从互联网加载 v2.5.3 字体",
FONT_INFO_DETAILED: `
<p>
为了提高 Obsidian 的启动时间并管理大型 <strong>CJK 字体系列</strong>
我已将 CJK 字体移出插件的 <code>main.js</code>。默认情况下CJK 字体将从互联网加载。
这通常不会造成问题,因为 Obsidian 在首次使用后会缓存这些文件。
</p>
<p>
如果您希望 Obsidian 完全离线或遇到性能问题,可以下载字体资源。
</p>
<h3>说明:</h3>
<ol>
<li>从 <a href="https://github.com/zsviczian/obsidian-excalidraw-plugin/raw/refs/heads/master/assets/excalidraw-fonts.zip">GitHub</a> 下载字体。</li>
<li>解压并将文件复制到 Vault 文件夹中(默认:<code>Excalidraw/${CJK_FONTS}</code>; 文件夹名称區分大小寫!)。</li>
<li><mark>请勿</mark>将此文件夹设置为 Vault 根目录或与其他本地字体混合。</li>
</ol>
<h3>对于 Obsidian Sync 用户:</h3>
<p>
确保 Obsidian Sync 设置为同步“所有其他文件类型”,或者在所有设备上下载并解压文件。
</p>
<h3>注意:</h3>
<p>
如果您觉得这个过程繁琐,请向 Obsidian.md 提交功能请求,以支持插件文件夹中的资源。
目前,仅支持(同步)单个 <code>main.js</code>,这导致大型文件和复杂插件(如 Excalidraw启动时间较慢。
对此带来的不便,我深表歉意。
</p>
`,
//ObsidianMenu.tsx
GOTO_FULLSCREEN: "进入全屏模式",
@@ -835,6 +920,8 @@ FILENAME_HEAD: "文件名",
ES_YOUTUBE_START_INVALID: "YouTube 起始时间无效。请检查格式并重试",
ES_FILENAME_VISIBLE: "显示文件名",
ES_BACKGROUND_HEAD: "背景色",
ES_BACKGROUND_DESC_INFO : "点击此处了解更多颜色信息" ,
ES_BACKGROUND_DESC_DETAIL : "背景颜色仅影响 Markdown 嵌入预览模式。在编辑模式下,它会根据场景(通过文档属性设置)或插件设置,遵循 Obsidian 的浅色/深色主题。背景颜色有两层:元素背景颜色(下层)和上层颜色。选择“匹配元素背景”意味着两层都遵循元素颜色。选择“匹配画布”或特定背景颜色时,保留元素背景层。设置透明度(例如 50%)会将画布或选定的颜色与元素背景颜色混合。要移除元素背景层,可以在 Excalidraw 的元素属性编辑器中将元素颜色设置为透明,这样只有上层颜色生效。" ,
ES_BACKGROUND_MATCH_ELEMENT: "匹配元素背景色",
ES_BACKGROUND_MATCH_CANVAS: "匹配画布背景色",
ES_BACKGROUND_COLOR: "背景色",
@@ -894,4 +981,118 @@ FILENAME_HEAD: "文件名",
IPM_GROUP_PAGES_DESC: "这将把所有页面建立为一个单独的组。如果您在导入后锁定页面,建议使用此方法,因为这样可以更方便地解锁整个组,而不是逐个解锁。",
IPM_SELECT_PDF: "请选择一个 PDF 文件",
//Utils.ts
UPDATE_AVAILABLE: `Excalidraw 的新版本已在社区插件中可用。\n\n您正在使用 ${PLUGIN_VERSION}\n最新版本是`,
ERROR_PNG_TOO_LARGE: "导出 PNG 时出错 - PNG 文件过大,请尝试较小的分辨率",
// ModifierkeyHelper.ts
// WebBrowserDragAction
WEB_DRAG_IMPORT_IMAGE : "导入图片到 Vault" ,
WEB_DRAG_IMAGE_URL : "通过 URL 插入图片或 YouTube 缩略图" ,
WEB_DRAG_LINK : "插入链接" ,
WEB_DRAG_EMBEDDABLE : "插入交互框架" ,
// LocalFileDragAction
LOCAL_DRAG_IMPORT : "导入外部文件,或在路径来自 Vault 时复用现有文件" ,
LOCAL_DRAG_IMAGE : "插入图片:使用本地 URI或在路径来自 Vault 时使用内部链接" ,
LOCAL_DRAG_LINK : "插入链接:使用本地 URI或在路径来自 Vault 时使用内部链接" ,
LOCAL_DRAG_EMBEDDABLE : "插入交互框架:使用本地 URI或在路径来自 Vault 时使用内部链接" ,
// InternalDragAction
INTERNAL_DRAG_IMAGE : "插入图片" ,
INTERNAL_DRAG_IMAGE_FULL : "插入图片100% 尺寸)" ,
INTERNAL_DRAG_LINK : "插入链接" ,
INTERNAL_DRAG_EMBEDDABLE : "插入交互框架" ,
// LinkClickAction
LINK_CLICK_ACTIVE : "在当前活动窗口中打开" ,
LINK_CLICK_NEW_PANE : "在相邻的新窗口中打开" ,
LINK_CLICK_POPOUT : "在弹出窗口中打开" ,
LINK_CLICK_NEW_TAB : "在新标签页中打开" ,
LINK_CLICK_MD_PROPS : "显示 Markdown 图片属性对话框(仅在嵌入 Markdown 文档为图片时适用)" ,
// 导出对话框
// 对话框和标签页
EXPORTDIALOG_TITLE: "导出图形",
EXPORTDIALOG_TAB_IMAGE: "图像",
EXPORTDIALOG_TAB_PDF: "PDF",
// 设置持久化
EXPORTDIALOG_SAVE_SETTINGS: "将图像设置保存到文件 doc.properties 吗?",
EXPORTDIALOG_SAVE_SETTINGS_SAVE: "保存为预设",
EXPORTDIALOG_SAVE_SETTINGS_ONETIME: "仅本次使用",
// 图像设置
EXPORTDIALOG_IMAGE_SETTINGS: "图像",
EXPORTDIALOG_IMAGE_DESC: "PNG 支持透明。外部文件可以包含 Excalidraw 场景数据。",
EXPORTDIALOG_PADDING: "边距",
EXPORTDIALOG_SCALE: "缩放",
EXPORTDIALOG_CURRENT_PADDING: "当前边距:",
EXPORTDIALOG_SIZE_DESC: "缩放会影响输出大小",
EXPORTDIALOG_SCALE_VALUE: "缩放:",
EXPORTDIALOG_IMAGE_SIZE: "大小:",
// 主题和背景
EXPORTDIALOG_EXPORT_THEME: "主题",
EXPORTDIALOG_THEME_LIGHT: "浅色",
EXPORTDIALOG_THEME_DARK: "深色",
EXPORTDIALOG_BACKGROUND: "背景",
EXPORTDIALOG_BACKGROUND_TRANSPARENT: "透明",
EXPORTDIALOG_BACKGROUND_USE_COLOR: "使用场景颜色",
// 选择
EXPORTDIALOG_SELECTED_ELEMENTS: "导出",
EXPORTDIALOG_SELECTED_ALL: "整个场景",
EXPORTDIALOG_SELECTED_SELECTED: "仅选中部分",
// 导出选项
EXPORTDIALOG_EMBED_SCENE: "包含场景数据吗?",
EXPORTDIALOG_EMBED_YES: "是",
EXPORTDIALOG_EMBED_NO: "否",
// PDF 设置
EXPORTDIALOG_PDF_SETTINGS: "PDF",
EXPORTDIALOG_PAGE_SIZE: "页面大小",
EXPORTDIALOG_PAGE_ORIENTATION: "方向",
EXPORTDIALOG_ORIENTATION_PORTRAIT: "纵向",
EXPORTDIALOG_ORIENTATION_LANDSCAPE: "横向",
EXPORTDIALOG_PDF_FIT_TO_PAGE: "页面适配",
EXPORTDIALOG_PDF_FIT_OPTION: "适配页面",
EXPORTDIALOG_PDF_FIT_2_OPTION: "适配至最多 2 页" ,
EXPORTDIALOG_PDF_FIT_4_OPTION: "适配至最多 4 页" ,
EXPORTDIALOG_PDF_FIT_6_OPTION: "适配至最多 6 页" ,
EXPORTDIALOG_PDF_FIT_8_OPTION: "适配至最多 8 页" ,
EXPORTDIALOG_PDF_FIT_12_OPTION: "适配至最多 12 页" ,
EXPORTDIALOG_PDF_FIT_16_OPTION: "适配至最多 16 页" ,
EXPORTDIALOG_PDF_SCALE_OPTION: "使用图像缩放(可能跨多页)",
EXPORTDIALOG_PDF_PAPER_COLOR: "纸张颜色",
EXPORTDIALOG_PDF_PAPER_WHITE: "白色",
EXPORTDIALOG_PDF_PAPER_SCENE: "使用场景颜色",
EXPORTDIALOG_PDF_PAPER_CUSTOM: "自定义颜色",
EXPORTDIALOG_PDF_ALIGNMENT: "页面位置",
EXPORTDIALOG_PDF_ALIGN_CENTER: "居中",
EXPORTDIALOG_PDF_ALIGN_CENTER_LEFT: "左对齐居中" ,
EXPORTDIALOG_PDF_ALIGN_CENTER_RIGHT: "右对齐居中" ,
EXPORTDIALOG_PDF_ALIGN_TOP_LEFT: "左上角",
EXPORTDIALOG_PDF_ALIGN_TOP_CENTER: "顶部居中",
EXPORTDIALOG_PDF_ALIGN_TOP_RIGHT: "右上角",
EXPORTDIALOG_PDF_ALIGN_BOTTOM_LEFT: "左下角",
EXPORTDIALOG_PDF_ALIGN_BOTTOM_CENTER: "底部居中",
EXPORTDIALOG_PDF_ALIGN_BOTTOM_RIGHT: "右下角",
EXPORTDIALOG_PDF_MARGIN: "边距",
EXPORTDIALOG_PDF_MARGIN_NONE: "无",
EXPORTDIALOG_PDF_MARGIN_TINY: "小",
EXPORTDIALOG_PDF_MARGIN_NORMAL: "正常",
EXPORTDIALOG_SAVE_PDF_SETTINGS: "保存 PDF 设置",
EXPORTDIALOG_SAVE_CONFIRMATION: "PDF 配置已保存为插件默认设置",
// 按钮
EXPORTDIALOG_PNGTOFILE: "导出 PNG 文件",
EXPORTDIALOG_SVGTOFILE: "导出 SVG 文件",
EXPORTDIALOG_PNGTOVAULT: "PNG 保存到 Vault",
EXPORTDIALOG_SVGTOVAULT: "SVG 保存到 Vault",
EXPORTDIALOG_EXCALIDRAW: "Excalidraw",
EXPORTDIALOG_PNGTOCLIPBOARD: "PNG 复制到剪贴板",
EXPORTDIALOG_SVGTOCLIPBOARD: "SVG 复制到剪贴板",
EXPORTDIALOG_PDF: "导出 PDF 文件",
EXPORTDIALOG_PDF_PROGRESS_NOTICE: "正在导出 PDF。如果图像较大可能需要一些时间。" ,
EXPORTDIALOG_PDF_PROGRESS_DONE: "导出完成" ,
EXPORTDIALOG_PDF_PROGRESS_ERROR: "导出 PDF 时出错,请检查开发者控制台以获取详细信息" ,
// exportUtils.ts
PDF_EXPORT_DESKTOP_ONLY: "PDF 导出功能仅限桌面端使用" ,
};

File diff suppressed because it is too large Load Diff

View File

@@ -3,11 +3,37 @@ import { BinaryFileData } from "@zsviczian/excalidraw/types/excalidraw/types";
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
import { Notice } from "obsidian";
import { getEA } from "src";
import { ExcalidrawAutomate, cloneElement } from "src/ExcalidrawAutomate";
import { ExportSettings } from "src/ExcalidrawView";
import { getEA } from "src/core";
import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
import { cloneElement } from "src/utils/excalidrawAutomateUtils";
import { ExportSettings } from "src/view/ExcalidrawView";
import { nanoid } from "src/constants/constants";
import { svgToBase64 } from "../utils/utils";
/**
* Creates a masked image from an Excalidraw scene.
*
* The scene must contain:
* - One element.type="frame" element that defines the crop area
* - One or more element.type="image" elements
* - Zero or more non-image shape elements (rectangles, ellipses etc) that define the mask
*
* The class splits the scene into two parts:
* 1. Images (managed in imageEA)
* 2. Mask shapes (managed in maskEA)
*
* A transparent rectangle matching the combined bounding box is added to both
* imageEA and maskEA to ensure consistent sizing between image and mask.
*
* For performance, if there is only one image, it is not rotated, and
* its size matches the bounding box,
* the image data is used directly from cache rather than regenerating.
*
* @example
* const cropper = new CropImage(elements, files);
* const pngBlob = await cropper.getCroppedPNG();
* cropper.destroy();
*/
export class CropImage {
private imageEA: ExcalidrawAutomate;
private maskEA: ExcalidrawAutomate;
@@ -105,10 +131,15 @@ export class CropImage {
withTheme: false,
isMask: false,
}
const isRotated = this.imageEA.getElements().some(el=>el.type === "image" && el.angle !== 0);
const images = Object.values(this.imageEA.imagesDict);
if(!isRotated && (images.length === 1)) {
return images[0].dataURL;
const images = this.imageEA.getElements().filter(el=>el.type === "image" && el.isDeleted === false);
const isRotated = images.some(el=>el.angle !== 0);
const imageDataURLs = Object.values(this.imageEA.imagesDict);
if(!isRotated && images.length === 1 && imageDataURLs.length === 1) {
const { width, height } = this.bbox;
if(images[0].width === width && images[0].height === height) {
//get image from the cache if mask is not bigger than the image, and if there is a single image element
return imageDataURLs[0].dataURL;
}
}
return await this.imageEA.createPNGBase64(null,1,exportSettings,null,null,0);
}
@@ -139,7 +170,9 @@ export class CropImage {
const PLUGIN = app.plugins.plugins["obsidian-excalidraw-plugin"];
const svg = await this.buildSVG();
return new Promise((resolve, reject) => {
const svgData = new XMLSerializer().serializeToString(svg);
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2026
const svgData = svg.outerHTML;
//const svgData = new XMLSerializer().serializeToString(svg);
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
@@ -168,7 +201,7 @@ export class CropImage {
1 // image quality (0 - 1)
);
};
image.src = `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svgData)))}`;
image.src = svgToBase64(svgData);
});
}

View File

@@ -1,6 +1,6 @@
import { Setting, ToggleComponent } from "obsidian";
import { EmbeddableMDCustomProps } from "./EmbeddableSettings";
import { fragWithHTML } from "src/utils/Utils";
import { fragWithHTML } from "src/utils/utils";
import { t } from "src/lang/helpers";
export class EmbeddalbeMDFileCustomDataSettingsComponent {
@@ -46,6 +46,16 @@ export class EmbeddalbeMDFileCustomDataSettingsComponent {
);
}
contentEl.createEl("h4",{text: t("ES_BACKGROUND_HEAD")});
const descDiv = contentEl.createDiv({ cls: "excalidraw-setting-desc" });
descDiv.textContent = t("ES_BACKGROUND_DESC_INFO");
descDiv.addEventListener("click", () => {
if (descDiv.textContent === t("ES_BACKGROUND_DESC_INFO")) {
descDiv.textContent = t("ES_BACKGROUND_DESC_DETAIL");
} else {
descDiv.textContent = t("ES_BACKGROUND_DESC_INFO");
}
});
let bgSetting: Setting;
let bgMatchElementToggle: ToggleComponent;

View File

@@ -1,16 +1,16 @@
import { ExcalidrawEmbeddableElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
import { Modal, Notice, Setting, TFile, ToggleComponent } from "obsidian";
import { getEA } from "src";
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
import ExcalidrawView from "src/ExcalidrawView";
import { getEA } from "src/core";
import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
import ExcalidrawView from "src/view/ExcalidrawView";
import { t } from "src/lang/helpers";
import ExcalidrawPlugin from "src/main";
import { getNewUniqueFilepath, getPathWithoutExtension, splitFolderAndFilename } from "src/utils/FileUtils";
import { addAppendUpdateCustomData, fragWithHTML } from "src/utils/Utils";
import ExcalidrawPlugin from "src/core/main";
import { getNewUniqueFilepath, getPathWithoutExtension, splitFolderAndFilename } from "src/utils/fileUtils";
import { addAppendUpdateCustomData, fragWithHTML } from "src/utils/utils";
import { getYouTubeStartAt, isValidYouTubeStart, isYouTube, updateYouTubeStartTime } from "src/utils/YoutTubeUtils";
import { EmbeddalbeMDFileCustomDataSettingsComponent } from "./EmbeddableMDFileCustomDataSettingsComponent";
import { isWinCTRLorMacCMD } from "src/utils/ModifierkeyHelper";
import { isWinCTRLorMacCMD } from "src/utils/modifierkeyHelper";
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
export type EmbeddableMDCustomProps = {
@@ -226,7 +226,7 @@ export class EmbeddableSettings extends Modal {
(async() => {
await this.ea.addElementsToView();
//@ts-ignore
this.ea.viewUpdateScene({appState: {}, storeAction: "update"});
this.ea.viewUpdateScene({appState: {}, captureUpdate: CaptureUpdateAction.NEVER});
this.close(); //close should only run once update scene is done
})();
} else {

Some files were not shown because too many files have changed in this diff Show More