Compare commits

...

147 Commits

Author SHA1 Message Date
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
zsviczian
a910550e34 2.4.2-rc-1 2024-09-06 17:35:17 +02:00
zsviczian
01e53471d7 Merge pull request #1993 from dmscode/master
Update zh-cn.ts to 2.4.1
2024-09-06 13:56:48 +02:00
zsviczian
5569cff20e Update README.md 2024-09-05 18:15:07 +02:00
dmscode
757c12c386 Update zh-cn.ts to 2.4.1 2024-09-04 08:18:05 +08:00
zsviczian
4cfe6cecda Rename Contribution.yml to Contribution.yml.old 2024-09-03 20:02:53 +02:00
zsviczian
a34fbad038 Update Contribution.yml 2024-09-03 18:03:56 +02:00
zsviczian
6138ef03e0 Update Contribution.yml 2024-09-03 18:02:55 +02:00
zsviczian
9eb2821c9b Update Contribution.yml 2024-09-03 17:44:05 +02:00
zsviczian
628bd7bdb2 Update Contribution.yml 2024-09-03 17:41:08 +02:00
zsviczian
a3914da4ff Update Contribution.yml 2024-09-03 17:39:53 +02:00
zsviczian
6a1e7f08f4 Create Contribution.yml 2024-09-03 17:36:32 +02:00
zsviczian
15b400a3d9 2.4.1 2024-09-03 13:11:22 +02:00
zsviczian
e447cc3b67 2.4.0 2024-09-03 11:36:13 +02:00
zsviczian
8240c7e240 2.4.1-rc-1 2024-09-03 11:32:23 +02:00
zsviczian
6f020e9574 Merge pull request #1987 from dmscode/master
Fix
2024-09-03 10:13:32 +02:00
dmscode
c9d5f74ed4 Fix
-I found that the operation to modify custom brushes is a double-click, not a long press;
- Added a website to find free commercial Chinese fonts.
2024-09-03 10:06:31 +08:00
zsviczian
78034f6dea svg 2024-09-02 19:30:45 +02:00
zsviczian
b81cb52614 svg change 2024-09-02 19:25:02 +02:00
zsviczian
56280cd893 reset latex size 2024-09-02 19:02:09 +02:00
zsviczian
e515586e6b Merge pull request #1984 from firai/reset-latex-ea-script
Add script to reset the sizes of LaTeX equations
2024-09-02 11:11:47 +02:00
firai
424af4c60d Add Reset LaTeX Size script 2024-09-02 02:18:52 +08:00
zsviczian
0a7048aca1 update golden ratio 2024-09-01 09:03:34 +02:00
zsviczian
dd812e0684 update golden ratio script 2024-09-01 09:01:11 +02:00
zsviczian
51b7aebbc3 2.4.0 2024-08-31 13:37:27 +02:00
zsviczian
01b1698934 Update bug_report.yml 2024-08-30 14:10:42 +02:00
zsviczian
1025054bf4 Update bug_report.yml 2024-08-30 14:03:31 +02:00
zsviczian
06b4986997 Update bug_report.yml 2024-08-30 14:02:40 +02:00
zsviczian
31716ebcbc Update bug_report.yml 2024-08-30 13:58:45 +02:00
zsviczian
2e1f28f67e Update bug_report.yml 2024-08-30 13:56:20 +02:00
zsviczian
7f2e2b2d45 Update bug_report.yml 2024-08-30 13:52:52 +02:00
zsviczian
b961435e01 Update bug_report.yml 2024-08-30 13:48:09 +02:00
zsviczian
c54fab9603 Update bug_report.yml 2024-08-30 13:46:48 +02:00
zsviczian
cc6832afd6 Rename bug_report.md to bug_report.md.x 2024-08-30 13:44:13 +02:00
zsviczian
29c41cb45a Create bug_report.yml 2024-08-30 13:38:43 +02:00
zsviczian
c18984a26b Merge pull request #1977 from zsviczian/window-onblur
save on window blur
2024-08-30 13:20:30 +02:00
zsviczian
23d1ad0da6 save on window blur 2024-08-30 11:19:43 +00:00
zsviczian
49173dc766 reset autosave timer the first time the drawing changes 2024-08-30 12:49:00 +02:00
zsviczian
03a563856d Merge pull request #1974 from zsviczian/autosave-tweaks
autosave tweaks
2024-08-30 10:33:13 +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
78 changed files with 7874 additions and 1856 deletions

View File

@@ -0,0 +1,50 @@
name: Contribution Request
description: Request access to contribute to the Excalidraw plugin documentation on the wiki
title: '[Contribution] Request to Contribute to Wiki'
labels: [collaboration-request]
assignees: []
body:
- type: markdown
attributes:
value: |
## Contribution Request
Thank you for your interest in contributing to the Excalidraw Plugin wiki! To help me understand how youd like to contribute, please provide the following details:
1. **Contribution Area**: Describe what areas of the wiki youre interested in contributing to (e.g., fixing typos, adding new tutorials, improving existing content).
2. **Experience**: Briefly describe your experience with Excalidraw or Obsidian and any relevant background that will help with the contribution.
3. **Additional Information**: Any other information or questions you may have.
**Example:**
```
- Contribution Area: Adding a tutorial on advanced Excalidraw features
- Experience: Regular user of Excalidraw and experienced in creating tutorials
- Additional Information: Looking forward to contributing!
```
Once I review your request, I will get back to you with instructions on how to proceed. Thank you for helping improve our documentation!
---
Zsolt
- type: textarea
attributes:
label: Contribution Details
description: Provide details about your contribution request.
placeholder: |
- Contribution Area: Adding a tutorial on advanced Excalidraw features
- Experience: Regular user of Excalidraw and experienced in creating tutorials
- Additional Information: Looking forward to contributing!
- type: checkboxes
id: verify_guidelines
attributes:
label: Please read the WIKI Contributors Guidelines before submitting your request!
options:
- label: Yes, I have read and understood the [Contributors Guidelines](https://github.com/zsviczian/obsidian-excalidraw-plugin/wiki/Contributor-Guidelines)

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

@@ -0,0 +1,68 @@
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.
- 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: 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.

85
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,85 @@
name: Bug report
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
attributes:
value: |
⚠️ **Important: Please Read Before Submitting a Bug Report** ⚠️
I am a one-person team working on this plugin as a part-time hobby. I cannot handle a flood of poorly documented issues. **To ensure your report is considered, you must follow these guidelines**. If you don't, I will close the issue without review.
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.
- type: markdown
attributes:
value: |
Please help by providing the following details. Bugs reported without the required information may be closed without review.
- type: markdown
attributes:
value: |
---
- type: checkboxes
id: search_existing_issues
attributes:
label: Have you searched for existing issues (including closed ones)?
description: Please confirm that you have searched the issue tracker before reporting a new issue.
options:
- label: Yes, I have searched the existing issues.
- type: checkboxes
id: verify_bug
attributes:
label: Does this bug persist in a new vault with only Excalidraw installed?
description: Please confirm that you have tested this issue in an empty Obsidian vault with no other plugins or themes installed.
options:
- label: Yes, I have verified the issue persists.
- type: textarea
id: environment
attributes:
label: "Your environment"
description: "Run `Command Palette/Show Debug info` in Obsidian and paste the result here."
placeholder: "Paste your Obsidian debug info here..."
- type: textarea
id: bug_description
attributes:
label: "Describe the bug"
description: "A clear and concise description of what the bug is."
placeholder: "Provide a detailed description of the issue..."
- type: textarea
id: steps_to_reproduce
attributes:
label: "Steps to reproduce"
description: "List the steps to reproduce the behavior."
placeholder: |
1. Go to '...'
2. Click on '...'
3. See error
- type: textarea
id: expected_behavior
attributes:
label: "Expected behavior"
description: "A clear and concise description of what you expected to happen."
placeholder: "Describe what you expected to happen..."
- type: textarea
id: additional_context
attributes:
label: "Additional context"
description: "Add any other context about the problem here."
placeholder: "Include any other information that may be helpful..."
- type: markdown
attributes:
value: |
**Attachments:**
If applicable, please attach any screenshots, screen recordings, or files by dragging and dropping them into the comment area or directly into any of the text fields above.

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.

View File

@@ -1,6 +1,8 @@
# Excalidraw
[简体中文](./README.zh-cn.md)
[简体中文](./docs/zh-cn/README.md)
👉👉👉 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.
@@ -98,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
@@ -225,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;

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:nmuber);
```
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` 中列出的对象进行分组。
## Utility functions
### 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
```
The script:
*使用 <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) 或观看以下视频。

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

@@ -13,6 +13,11 @@ Gravitational point of spiral: $$\left[x,y\right]=\left[ x + \frac{{\text{width}
Dimensions of inner rectangles in case of Double Spiral: $$[width, height] = \left[\frac{width\cdot(\phi^2+1)}{2\phi^2}\;, \;\frac{height\cdot(\phi^2+1)}{2\phi^2}\right]$$
```js*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.4.0")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
const phi = (1 + Math.sqrt(5)) / 2; // Golden Ratio (φ)
const inversePhi = (1-1/phi);
const pointsPerCurve = 20; // Number of points per curve segment
@@ -33,18 +38,16 @@ if(!rect || rect.type !== "rectangle") {
}
window.excalidrawGoldenRatio.timer = setTimeout(()=>{delete window.excalidrawGoldenRatio;},2000);
window.excalidrawGoldenRatio.cycle = (window.excalidrawGoldenRatio.cycle+1)%5;
ea.copyViewElementsToEAforEditing(textEls);
ea.getElements().forEach(el=> {
el.fontSize = window.excalidrawGoldenRatio.cycle === 2
? el.fontSize / Math.pow(phi,4)
: el.fontSize * phi;
const font = ExcalidrawLib.getFontString(el);
const lineHeight = ExcalidrawLib.getDefaultLineHeight(el.fontFamily);
const {width, height, baseline} = ExcalidrawLib.measureText(el.originalText, font, lineHeight);
ea.style.fontFamily = el.fontFamily;
ea.style.fontSize = el.fontSize;
const {width, height } = ea.measureText(el.originalText);
el.width = width;
el.height = height;
el.baseline = baseline;
});
ea.addElementsToView();
return;
@@ -631,7 +634,7 @@ modal.onOpen = async () => {
.addDropdown(dropdown=>dropdown
.addOption("none","None")
.addOption("top-down","Top down")
.addOption("bottom-up","Bottom up")
.addOption("bottom-up","Bootom up")
.addOption("center-out","Center out")
.addOption("center-in","Center in")
.setValue(vDirection)

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

@@ -61,6 +61,7 @@ Open the script you are interested in and save it to your Obsidian Vault includi
|[OCR - Optical Character Recognition](OCR%20-%20Optical%20Character%20Recognition.md)|The script will 1) send the selected image file to [taskbone.com](https://taskbone.com) to extract the text from the image, and 2) will add the text to your drawing as a text element.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-ocr.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Organic Line](Organic%20Line.md)|Converts selected freedraw lines such that pencil pressure will decrease from maximum to minimum from the beginning of the line to its end. The resulting line is placed at the back of the layers, under all other items. Helpful when drawing organic mindmaps.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-organic-line.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Repeat Elements](Repeat%20Elements.md)|This script will detect the difference between 2 selected elements, including position, size, angle, stroke and background color, and create several elements that repeat these differences based on the number of repetitions entered by the user.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-repeat-elements.png)|[@1-2-3](https://github.com/1-2-3)|
|[Reset LaTeX Size](Reset%20LaTeX%20Size.md)|Reset the sizes of embedded LaTeX equations to the default sizes or a multiple of the default sizes.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-reset-latex.jpg)|[@firai](https://github.com/firai)|
|[Reverse arrows](Reverse%20arrows.md)|Reverse the direction of **arrows** within the scope of selected elements.|![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-reverse-arrow.jpg)|[@zsviczian](https://github.com/zsviczian)|
|[Scribble Helper](Scribble%20Helper.md)|iOS scribble helper for better handwriting experience with text elements. If no elements are selected then the creates a text element at pointer position and you can use the edit box to modify the text with scribble. If a text element is selected then opens the input prompt where you can modify this text with scribble.|![]('https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-scribble-helper.jpg')|[@zsviczian](https://github.com/zsviczian)|
|[Select Elements of Type](Select%20Elements%20of%20Type.md)|Prompts you with a list of the different element types in the active image. Only elements of the selected type will be selected on the canvas. If nothing is selected when running the script, then the script will process all the elements on the canvas. If some elements are selected when the script is executed, then the script will only process the selected elements.<br>The script is useful when, for example, you want to bring to front all the arrows, or want to change the color of all the text elements, etc.|![]('https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-select-element-of-type.jpg')|[@zsviczian](https://github.com/zsviczian)|

View File

@@ -0,0 +1,32 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-reset-latex.jpg)
Reset the sizes of embedded LaTeX equations to the default sizes or a multiple of the default sizes.
```javascript
*/
if (!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.4.0")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
let elements = ea.getViewSelectedElements().filter((el)=>["image"].includes(el.type));
if (elements.length === 0) return;
scale = await utils.inputPrompt("Scale?", "Number", "1");
if (!scale) return;
scale = parseFloat(scale);
ea.copyViewElementsToEAforEditing(elements);
for (el of elements) {
equation = ea.targetView.excalidrawData.getEquation(el.fileId)?.latex;
if (!equation) return;
eqData = await ea.tex2dataURL(equation);
ea.getElement(el.id).width = eqData.size.width * scale;
ea.getElement(el.id).height = eqData.size.height * scale;
};
ea.addElementsToView(false, false);

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-linecap="round" stroke-linejoin="round"><rect stroke-width="2" width="20" height="16" x="2" y="4" rx="2"/><path stroke-width="2" d="M12 9v11"/><path stroke-width="2" d="M2 9h13a2 2 0 0 1 2 2v9"/></svg>

After

Width:  |  Height:  |  Size: 338 B

File diff suppressed because one or more lines are too long

View File

@@ -89,6 +89,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/Modify%20background%20color%20opacity.svg"/></div>|[[#Modify background color opacity]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Organic%20Line.svg"/></div>|[[#Organic Line]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Organic%20Line%20Legacy.svg"/></div>|[[#Organic Line Legacy]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Reset%20LaTeX%20Size.svg"/></div>|[[#Reset LaTeX Size]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20background%20color%20of%20unclosed%20line%20object%20by%20adding%20a%20shadow%20clone.svg"/></div>|[[#Set background color of unclosed line object by adding a shadow clone]]|
|<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]]|
@@ -114,14 +115,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]]|
@@ -131,6 +130,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
@@ -155,6 +155,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
@@ -183,12 +184,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
@@ -274,6 +269,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
@@ -402,12 +399,11 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/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
@@ -517,6 +513,14 @@ 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/Select%20Similar%20Elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script allows you to streamline your Obsidian-Excalidraw workflows by enabling the selection of elements based on similar properties. you can precisely define which attributes such as stroke color, fill style, font family, and more, should match for selection. It's perfect for large canvases where manual selection would be cumbersome. You can either run the script to find and select matching elements across the entire scene, or define a specific group of elements to apply the selection criteria within a defined timeframe.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-select-similar-elements.png'></td></tr></table>
## Reset LaTeX Size
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Reset%20LaTeX%20Size.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/firai'>@firai</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/Reset%20LaTeX%20Size.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Reset the sizes of embedded LaTeX equations to the default sizes or a multiple of the default sizes.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-reset-latex.jpg'></td></tr></table>
## Set background color of unclosed line object by adding a shadow clone
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20background%20color%20of%20unclosed%20line%20object%20by%20adding%20a%20shadow%20clone.md

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-excalidraw-plugin",
"name": "Excalidraw",
"version": "2.4.0-rc-2",
"version": "2.7.0-beta-1",
"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.3.0",
"version": "2.6.8",
"minAppVersion": "1.1.6",
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
"author": "Zsolt Viczian",

View File

@@ -19,7 +19,7 @@
"license": "MIT",
"dependencies": {
"@popperjs/core": "^2.11.8",
"@zsviczian/excalidraw": "0.17.1-obsidian-45",
"@zsviczian/excalidraw": "0.17.6-19",
"chroma-js": "^2.4.2",
"clsx": "^2.0.0",
"@zsviczian/colormaster": "^1.2.2",
@@ -34,9 +34,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",

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

@@ -8,6 +8,7 @@ import fs from 'fs';
import LZString from 'lz-string';
import postprocess from '@zsviczian/rollup-plugin-postprocess';
import cssnano from 'cssnano';
import jsesc from 'jsesc';
// Load environment variables
import dotenv from 'dotenv';
@@ -16,7 +17,7 @@ dotenv.config();
const DIST_FOLDER = 'dist';
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
? fs.readFileSync("./node_modules/@zsviczian/excalidraw/dist/excalidraw.production.min.js", "utf8")
@@ -51,12 +52,15 @@ 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, { quotes: 'backtick' }) +
'`;\n' +
'let EXCALIDRAW_PACKAGE = ""; const unpackExcalidraw = () => {EXCALIDRAW_PACKAGE = LZString.decompressFromBase64("' + LZString.compressToBase64(excalidraw_pkg) + '");};\n' +
'let {react, reactDOM } = window.eval.call(window, `(function() {' + '${REACT_PACKAGES};' + 'return {react: React, reactDOM: ReactDOM};})();`);\n' +
`let excalidrawLib = {};\n` +
'const PLUGIN_VERSION="' + manifest.version + '";';
const BASE_CONFIG = {
input: 'src/main.ts',

View File

@@ -1,6 +1,7 @@
import { Extension } from "@codemirror/state";
import ExcalidrawPlugin from "src/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

@@ -6,13 +6,16 @@ import {
EditorSuggestTriggerInfo,
TFile,
} from "obsidian";
import { FRONTMATTER_KEYS_INFO } from "./SuggesterInfo";
import { FRONTMATTER_KEYS_INFO } from "../../dialogs/SuggesterInfo";
import {
EXCALIDRAW_AUTOMATE_INFO,
EXCALIDRAW_SCRIPTENGINE_INFO,
} from "./SuggesterInfo";
import type ExcalidrawPlugin from "../main";
} from "../../dialogs/SuggesterInfo";
import type ExcalidrawPlugin from "../../main";
/**
* The field suggester recommends document properties in source mode, ea and utils function and attribute names.
*/
export class FieldSuggester extends EditorSuggest<string> {
plugin: ExcalidrawPlugin;
suggestType: "ea" | "excalidraw" | "utils";

View File

@@ -0,0 +1,142 @@
import {
FuzzyMatch,
TFile,
CachedMetadata,
TextComponent,
App,
setIcon,
} from "obsidian";
import { SuggestionModal } from "./SuggestionModal";
import { t } from "src/lang/helpers";
import { LinkSuggestion } from "src/types/types";
import ExcalidrawPlugin from "src/main";
import { AUDIO_TYPES, CODE_TYPES, ICON_NAME, IMAGE_TYPES, VIDEO_TYPES } from "src/constants/constants";
export class FileSuggestionModal extends SuggestionModal<LinkSuggestion> {
text: TextComponent;
cache: CachedMetadata;
filesAndAliases: LinkSuggestion[];
file: TFile;
constructor(app: App, input: TextComponent, items: TFile[], private plugin: ExcalidrawPlugin) {
const filesAndAliases = [];
for (const file of items) {
const path = file.path;
filesAndAliases.push({ file, path, alias: "" });
const metadata = app.metadataCache.getFileCache(file); // Get metadata for the file
const aliases = metadata?.frontmatter?.aliases || []; // Check for frontmatter aliases
for (const alias of aliases) {
if(!alias) continue; // Skip empty aliases
filesAndAliases.push({ file, path, alias });
}
}
super(app, input.inputEl, filesAndAliases);
this.limit = 20;
this.filesAndAliases = filesAndAliases;
this.text = input;
this.suggestEl.style.maxWidth = "100%";
this.suggestEl.style.width = `${input.inputEl.clientWidth}px`;
this.inputEl.addEventListener("input", () => this.getFile());
this.setPlaceholder(t("SELECT_FILE_TO_INSERT"));
this.emptyStateText = t("NO_MATCH");
}
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: LinkSuggestion) {
return `${item.file.path}${item.alias ? `|${item.alias}` : ""}`;
}
onChooseItem(item: LinkSuggestion) {
this.file = item.file;
this.text.setValue(this.getItemText(item));
this.text.onChanged();
}
selectSuggestion({ item }: FuzzyMatch<LinkSuggestion>) {
this.file = item.file;
this.text.setValue(this.getItemText(item));
this.onClose();
this.text.onChanged();
this.close();
}
renderSuggestion(result: FuzzyMatch<LinkSuggestion>, itemEl: HTMLElement) {
const { item, match: matches } = result || {};
itemEl.addClass("mod-complex");
const contentEl = itemEl.createDiv("suggestion-content");
const auxEl = itemEl.createDiv("suggestion-aux");
const titleEl = contentEl.createDiv("suggestion-title");
const noteEl = contentEl.createDiv("suggestion-note");
//el.style.flexDirection = "column";
//content.style.flexDirection = "initial";
if (!item) {
titleEl.setText(this.emptyStateText);
itemEl.addClass("is-selected");
return;
}
const path = item.file?.path ?? item.path;
const pathLength = path.length - item.file.name.length;
const matchElements = matches.matches.map((m) => {
return createSpan("suggestion-highlight");
});
const itemText = this.getItemText(item);
for (let i = pathLength; i < itemText.length; i++) {
const match = matches.matches.find((m) => m[0] === i);
if (match) {
const element = matchElements[matches.matches.indexOf(match)];
titleEl.appendChild(element);
element.appendText(itemText.substring(match[0], match[1]));
i += match[1] - match[0] - 1;
continue;
}
titleEl.appendText(itemText[i]);
}
noteEl.setText(path);
if(this.plugin.isExcalidrawFile(item.file)) {
setIcon(auxEl, ICON_NAME);
} else if (item.file.extension === "md") {
setIcon(auxEl, "square-pen");
} else if (IMAGE_TYPES.includes(item.file.extension)) {
setIcon(auxEl, "image");
} else if (VIDEO_TYPES.includes(item.file.extension)) {
setIcon(auxEl, "monitor-play");
} else if (AUDIO_TYPES.includes(item.file.extension)) {
setIcon(auxEl, "file-audio");
} else if (CODE_TYPES.includes(item.file.extension)) {
setIcon(auxEl, "file-code");
} else if (item.file.extension === "canvas") {
setIcon(auxEl, "layout-dashboard");
} else if (item.file.extension === "pdf") {
setIcon(auxEl, "book-open-text");
} else {
auxEl.setText(item.file.extension);
}
}
getItems() {
return this.filesAndAliases;
}
}

View File

@@ -0,0 +1,87 @@
import {
FuzzyMatch,
CachedMetadata,
TextComponent,
App,
TFolder,
} from "obsidian";
import { SuggestionModal } from "./SuggestionModal";
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;
}
}

View File

@@ -0,0 +1,163 @@
import {
FuzzyMatch,
TFile,
BlockCache,
HeadingCache,
CachedMetadata,
TextComponent,
App,
} from "obsidian";
import { SuggestionModal } from "./SuggestionModal";
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;
}
}

View File

@@ -0,0 +1,119 @@
import {
SuggestModal,
Scope,
} from "obsidian";
export 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);
}
}
}

View File

@@ -0,0 +1,128 @@
import {
FuzzyMatch,
App,
FuzzySuggestModal,
Scope,
} from "obsidian";
import { createPopper, Instance as PopperInstance } from "@popperjs/core";
import { Suggester } from "./Suggester";
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[];
}

View File

@@ -36,6 +36,8 @@ import {
isMaskFile,
getEmbeddedFilenameParts,
cropCanvas,
promiseTry,
PromisePool,
} from "./utils/Utils";
import { ValueOf } from "./types/types";
import { getMermaidImageElements, getMermaidText, shouldRenderMermaid } from "./utils/MermaidUtils";
@@ -593,6 +595,7 @@ export class EmbeddedFilesLoader {
addFiles: (files: FileData[], isDark: boolean, final?: boolean) => void,
depth:number,
isThemeChange:boolean = false,
fileIDWhiteList?: Set<FileId>,
) {
if(depth > 7) {
@@ -605,124 +608,173 @@ export class EmbeddedFilesLoader {
this.isDark = excalidrawData?.scene?.appState?.theme === "dark";
}
let entry: IteratorResult<[FileId, EmbeddedFile]>;
const files: FileData[] = [];
while (!this.terminate && !(entry = entries.next()).done) {
const embeddedFile: EmbeddedFile = entry.value[1];
if (!embeddedFile.isLoaded(this.isDark)) {
//debug({where:"EmbeddedFileLoader.loadSceneFiles",uid:this.uid,status:"embedded Files are not loaded"});
const data = await this._getObsidianImage(embeddedFile, depth);
if (data) {
const fileData: FileData = {
mimeType: data.mimeType,
id: entry.value[0],
dataURL: data.dataURL,
created: data.created,
size: data.size,
hasSVGwithBitmap: data.hasSVGwithBitmap,
shouldScale: embeddedFile.shouldScale()
};
try {
addFiles([fileData], this.isDark, false);
}
catch(e) {
errorlog({ where: "EmbeddedFileLoader.loadSceneFiles", error: e });
}
//files.push(fileData);
}
} else if (embeddedFile.isSVGwithBitmap && (depth !== 0 || isThemeChange)) {
//this will reload the image in light/dark mode when switching themes
const fileData = {
mimeType: embeddedFile.mimeType,
id: entry.value[0],
dataURL: embeddedFile.getImage(this.isDark) as DataURL,
created: embeddedFile.mtime,
size: embeddedFile.size,
hasSVGwithBitmap: embeddedFile.isSVGwithBitmap,
shouldScale: embeddedFile.shouldScale()
};
//files.push(fileData);
try {
addFiles([fileData], this.isDark, false);
}
catch(e) {
errorlog({ where: "EmbeddedFileLoader.loadSceneFiles", error: e });
}
}
}
const files: FileData[][] = [];
files.push([]);
let batch = 0;
let equation;
const equations = excalidrawData.getEquationEntries();
while (!this.terminate && !(equation = equations.next()).done) {
if (!excalidrawData.getEquation(equation.value[0]).isLoaded) {
const latex = equation.value[1].latex;
const data = await tex2dataURL(latex);
if (data) {
const fileData = {
mimeType: data.mimeType,
id: equation.value[0],
dataURL: data.dataURL,
created: data.created,
size: data.size,
hasSVGwithBitmap: false,
shouldScale: true
};
files.push(fileData);
}
}
}
if(shouldRenderMermaid()) {
const mermaidElements = getMermaidImageElements(excalidrawData.scene.elements);
for(const element of mermaidElements) {
if(this.terminate) {
continue;
}
const data = getMermaidText(element);
const result = await mermaidToExcalidraw(data, {fontSize: 20}, true);
if(!result) {
continue;
}
if(result?.files) {
for (const key in result.files) {
function* loadIterator():Generator<Promise<void>> {
while (!(entry = entries.next()).done) {
if(fileIDWhiteList && !fileIDWhiteList.has(entry.value[0])) continue;
const embeddedFile: EmbeddedFile = entry.value[1];
const id = entry.value[0];
yield promiseTry(async () => {
if(this.terminate) {
return;
}
if (!embeddedFile.isLoaded(this.isDark)) {
//debug({where:"EmbeddedFileLoader.loadSceneFiles",uid:this.uid,status:"embedded Files are not loaded"});
const data = await this._getObsidianImage(embeddedFile, depth);
if (data) {
const fileData: FileData = {
mimeType: data.mimeType,
id: id,
dataURL: data.dataURL,
created: data.created,
size: data.size,
hasSVGwithBitmap: data.hasSVGwithBitmap,
shouldScale: embeddedFile.shouldScale()
};
files[batch].push(fileData);
/* try {
addFiles([fileData], this.isDark, false);
}
catch(e) {
errorlog({ where: "EmbeddedFileLoader.loadSceneFiles", error: e });
}*/
}
} else if (embeddedFile.isSVGwithBitmap && (depth !== 0 || isThemeChange)) {
//this will reload the image in light/dark mode when switching themes
const fileData = {
...result.files[key],
id: element.fileId,
created: Date.now(),
hasSVGwithBitmap: false,
shouldScale: true,
size: await getImageSize(result.files[key].dataURL),
mimeType: embeddedFile.mimeType,
id: id,
dataURL: embeddedFile.getImage(this.isDark) as DataURL,
created: embeddedFile.mtime,
size: embeddedFile.size,
hasSVGwithBitmap: embeddedFile.isSVGwithBitmap,
shouldScale: embeddedFile.shouldScale()
};
files.push(fileData);
files[batch].push(fileData);
/* try {
addFiles([fileData], this.isDark, false);
}
catch(e) {
errorlog({ where: "EmbeddedFileLoader.loadSceneFiles", error: e });
}*/
}
continue;
}
if(result?.elements) {
//handle case that mermaidToExcalidraw has implemented this type of diagram in the mean time
const res = await this.getExcalidrawSVG({
isDark: this.isDark,
file: null,
depth,
inFile: null,
hasSVGwithBitmap: false,
elements: result.elements
});
if(res?.dataURL) {
const size = await getImageSize(res.dataURL);
const fileData:FileData = {
mimeType: "image/svg+xml",
id: element.fileId,
dataURL: res.dataURL,
created: Date.now(),
hasSVGwithBitmap: res.hasSVGwithBitmap,
size,
shouldScale: true,
};
files.push(fileData);
}
continue;
}
});
}
};
let equationItem;
const equations = excalidrawData.getEquationEntries();
while (!(equationItem = equations.next()).done) {
if(fileIDWhiteList && !fileIDWhiteList.has(equationItem.value[0])) continue;
const equation = equationItem.value[1];
const id = equationItem.value[0];
yield promiseTry(async () => {
if (this.terminate) {
return;
}
if (!excalidrawData.getEquation(id).isLoaded) {
const latex = equation.latex;
const data = await tex2dataURL(latex);
if (data) {
const fileData = {
mimeType: data.mimeType,
id: id,
dataURL: data.dataURL,
created: data.created,
size: data.size,
hasSVGwithBitmap: false,
shouldScale: true
};
files[batch].push(fileData);
}
}
});
}
if(shouldRenderMermaid()) {
const mermaidElements = getMermaidImageElements(excalidrawData.scene.elements);
for(const element of mermaidElements) {
yield promiseTry(async () => {
if(this.terminate) {
return;
}
const data = getMermaidText(element);
const result = await mermaidToExcalidraw(
data,
{ themeVariables: { fontSize: "20" } },
true
);
if(!result) {
return;
}
if(result?.files) {
for (const key in result.files) {
const fileData = {
...result.files[key],
id: element.fileId,
created: Date.now(),
hasSVGwithBitmap: false,
shouldScale: true,
size: await getImageSize(result.files[key].dataURL),
};
files[batch].push(fileData);
}
return;
}
if(result?.elements) {
//handle case that mermaidToExcalidraw has implemented this type of diagram in the mean time
if (this.terminate) {
return;
}
const res = await this.getExcalidrawSVG({
isDark: this.isDark,
file: null,
depth,
inFile: null,
hasSVGwithBitmap: false,
elements: result.elements
});
if(res?.dataURL) {
const size = await getImageSize(res.dataURL);
const fileData:FileData = {
mimeType: "image/svg+xml",
id: element.fileId,
dataURL: res.dataURL,
created: Date.now(),
hasSVGwithBitmap: res.hasSVGwithBitmap,
size,
shouldScale: true,
};
files[batch].push(fileData);
}
return;
}
});
}
};
}
const addFilesTimer = setInterval(() => {
if(files[batch].length === 0) {
return;
}
try {
addFiles(files[batch], this.isDark, false);
}
catch(e) {
errorlog({ where: "EmbeddedFileLoader.loadSceneFiles", error: e });
}
files.push([]);
batch++;
}, 1200);
const iterator = loadIterator.bind(this)();
const concurency = 3;
await new PromisePool(iterator, concurency).all();
clearInterval(addFilesTimer);
this.emptyPDFDocsMap();
if (this.terminate) {
@@ -731,7 +783,7 @@ export class EmbeddedFilesLoader {
//debug({where:"EmbeddedFileLoader.loadSceneFiles",uid:this.uid,status:"add Files"});
try {
//in try block because by the time files are loaded the user may have closed the view
addFiles(files, this.isDark, true);
addFiles(files[batch], this.isDark, true);
} catch (e) {
errorlog({ where: "EmbeddedFileLoader.loadSceneFiles", error: e });
}
@@ -1058,27 +1110,10 @@ export class EmbeddedFilesLoader {
}
const getSVGData = async (app: App, file: TFile, colorMap: ColorMap | null): Promise<DataURL> => {
const svg = replaceSVGColors(await app.vault.read(file), colorMap) as string;
return svgToBase64(svg) as DataURL;
const svgString = replaceSVGColors(await app.vault.read(file), colorMap) as string;
return svgToBase64(svgString) as DataURL;
};
/*export const generateIdFromFile = async (file: ArrayBuffer): Promise<FileId> => {
let id: FileId;
try {
const hashBuffer = await window.crypto.subtle.digest("SHA-1", file);
id =
// convert buffer to byte array
Array.from(new Uint8Array(hashBuffer))
// convert to hex string
.map((byte) => byte.toString(16).padStart(2, "0"))
.join("") as FileId;
} catch (error) {
errorlog({ where: "EmbeddedFileLoader.generateIdFromFile", error });
id = fileid() as FileId;
}
return id;
};*/
export const generateIdFromFile = async (file: ArrayBuffer, key?: string): Promise<FileId> => {
let id: FileId;
try {

View File

@@ -17,11 +17,10 @@ import { MimeType } from "./EmbeddedFileLoader";
import { Editor, normalizePath, Notice, OpenViewState, RequestUrlResponse, TFile, TFolder, WorkspaceLeaf } from "obsidian";
import * as obsidian_module from "obsidian";
import ExcalidrawView, { ExportSettings, TextMode, getTextMode } from "src/ExcalidrawView";
import { ExcalidrawData, getMarkdownDrawingSection, REGEX_LINK } from "src/ExcalidrawData";
import { ExcalidrawData, getExcalidrawMarkdownHeaderSection, getMarkdownDrawingSection, REGEX_LINK } from "src/ExcalidrawData";
import {
FRONTMATTER,
nanoid,
VIEW_TYPE_EXCALIDRAW,
MAX_IMAGE_SIZE,
COLOR_NAMES,
fileid,
@@ -55,14 +54,14 @@ import {
wrapTextAtCharLength,
arrayToMap,
} from "src/utils/Utils";
import { getAttachmentsFolderAndFilePath, getLeaf, getNewOrAdjacentLeaf, isObsidianThemeDark, mergeMarkdownFiles, openLeaf } from "src/utils/ObsidianUtils";
import { AppState, BinaryFileData, DataURL, ExcalidrawImperativeAPI, Point } from "@zsviczian/excalidraw/types/excalidraw/types";
import { getAttachmentsFolderAndFilePath, getExcalidrawViews, getLeaf, getNewOrAdjacentLeaf, isObsidianThemeDark, mergeMarkdownFiles, openLeaf } from "src/utils/ObsidianUtils";
import { AppState, BinaryFileData, DataURL, ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
import { EmbeddedFile, EmbeddedFilesLoader, FileData } from "src/EmbeddedFileLoader";
import { tex2dataURL } from "src/LaTeX";
import { GenericInputPrompt, NewFileActions } from "src/dialogs/Prompt";
import { t } from "src/lang/helpers";
import { ScriptEngine } from "src/Scripts";
import { ConnectionPoint, DeviceType } from "src/types/types";
import { ConnectionPoint, DeviceType, Point } from "src/types/types";
import CM, { ColorMaster, extendPlugins } from "@zsviczian/colormaster";
import HarmonyPlugin from "@zsviczian/colormaster/plugins/harmony";
import MixPlugin from "@zsviczian/colormaster/plugins/mix"
@@ -94,6 +93,7 @@ import { EXCALIDRAW_AUTOMATE_INFO, EXCALIDRAW_SCRIPTENGINE_INFO } from "./dialog
import { addBackOfTheNoteCard, getFrameBasedOnFrameNameOrId } from "./utils/ExcalidrawViewUtils";
import { log } from "./utils/DebugHelper";
import { ExcalidrawLib } from "./ExcalidrawLib";
import { GlobalPoint } from "@zsviczian/excalidraw/types/math/types";
extendPlugins([
HarmonyPlugin,
@@ -133,6 +133,10 @@ export class ExcalidrawAutomate {
return DEVICE;
}
public printStartupBreakdown() {
this.plugin.printStarupBreakdown();
}
public help(target: Function | string) {
if (!target) {
log("Usage: ea.help(ea.functionName) or ea.help('propertyName') or ea.help('utils.functionName') - notice property name and utils function name is in quotes");
@@ -282,8 +286,17 @@ export class ExcalidrawAutomate {
return LZString.compressToBase64(str);
}
public decompressFromBase64(str:string): string {
return LZString.decompressFromBase64(str);
public decompressFromBase64(data:string): string {
if (!data) throw new Error("No input string provided for decompression.");
let cleanedData = '';
const length = data.length;
for (let i = 0; i < length; i++) {
const char = data[i];
if (char !== '\\n' && char !== '\\r') {
cleanedData += char;
}
}
return LZString.decompressFromBase64(cleanedData);
}
/**
@@ -427,7 +440,9 @@ export class ExcalidrawAutomate {
* @returns
*/
public getAPI(view?:ExcalidrawView):ExcalidrawAutomate {
return new ExcalidrawAutomate(this.plugin, view);
const ea = new ExcalidrawAutomate(this.plugin, view);
this.plugin.eaInstances.push(ea);
return ea;
}
/**
@@ -639,6 +654,13 @@ export class ExcalidrawAutomate {
0
)
: null;
if (template?.plaintext) {
if(params.plaintext) {
params.plaintext = params.plaintext + "\n\n" + template.plaintext;
} else {
params.plaintext = template.plaintext;
}
}
let elements = template ? template.elements : [];
elements = elements.concat(this.getElements());
let frontmatter: string;
@@ -664,7 +686,13 @@ export class ExcalidrawAutomate {
: FRONTMATTER;
}
frontmatter += params.plaintext ? params.plaintext + "\n\n" : "";
frontmatter += params.plaintext
? (params.plaintext.endsWith("\n\n")
? params.plaintext
: (params.plaintext.endsWith("\n")
? params.plaintext + "\n"
: params.plaintext + "\n\n"))
: "";
if(template?.frontmatter && params?.frontmatterKeys) {
//the frontmatter tags supplyed to create take priority
frontmatter = mergeMarkdownFiles(template.frontmatter,frontmatter);
@@ -741,18 +769,18 @@ export class ExcalidrawAutomate {
outString += Object.keys(this.imagesDict).length > 0
? `\n## Embedded Files\n`
: "";
: "\n";
Object.keys(this.imagesDict).forEach((key: FileId)=> {
const item = this.imagesDict[key];
if(item.latex) {
outString += `${key}: $$${item.latex}$$\n`;
outString += `${key}: $$${item.latex}$$\n\n`;
} else {
if(item.file) {
if(item.file instanceof TFile) {
outString += `${key}: [[${item.file.path}]]\n`;
outString += `${key}: [[${item.file.path}]]\n\n`;
} else {
outString += `${key}: [[${item.file}]]\n`;
outString += `${key}: [[${item.file}]]\n\n`;
}
} else {
const hyperlinkSplit = item.hyperlink.split("#");
@@ -760,15 +788,15 @@ export class ExcalidrawAutomate {
if(file && file instanceof TFile) {
const hasFileRef = hyperlinkSplit.length === 2
outString += hasFileRef
? `${key}: [[${file.path}#${hyperlinkSplit[1]}]]\n`
: `${key}: [[${file.path}]]\n`;
? `${key}: [[${file.path}#${hyperlinkSplit[1]}]]\n\n`
: `${key}: [[${file.path}]]\n\n`;
} else {
outString += `${key}: ${item.hyperlink}\n`;
outString += `${key}: ${item.hyperlink}\n\n`;
}
}
}
})
return outString + "\n%%\n";
return outString + "%%\n";
}
const filename = params?.filename
@@ -1392,8 +1420,8 @@ export class ExcalidrawAutomate {
): string {
const box = getLineBox(points);
id = id ?? nanoid();
const startPoint = points[0];
const endPoint = points[points.length - 1];
const startPoint = points[0] as GlobalPoint;
const endPoint = points[points.length - 1] as GlobalPoint;
this.elementsDict[id] = {
points: normalizeLinePoints(points),
lastCommittedPoint: null,
@@ -1461,7 +1489,12 @@ export class ExcalidrawAutomate {
diagram: string,
groupElements: boolean = true,
): Promise<string[]|string> {
const result = await mermaidToExcalidraw(diagram, {fontSize: this.style.fontSize});
const result = await mermaidToExcalidraw(
diagram, {
themeVariables: {fontSize: `${this.style.fontSize}`},
flowchart: {curve: this.style.roundness===null ? "linear" : "basis"},
}
);
const ids:string[] = [];
if(!result) return null;
if(result?.error) return result.error;
@@ -1536,6 +1569,10 @@ export class ExcalidrawAutomate {
: imageFile.path + (scale || !anchor ? "":"|100%"),
hasSVGwithBitmap: image.hasSVGwithBitmap,
latex: null,
size: { //must have the natural size here (e.g. for PDF cropping)
height: image.size.height,
width: image.size.width,
},
};
if (scale && (Math.max(image.size.width, image.size.height) > MAX_IMAGE_SIZE)) {
const scale =
@@ -1683,8 +1720,8 @@ export class ExcalidrawAutomate {
if (!connectionA) {
const intersect = intersectElementWithLine(
elA,
[bCenterX, bCenterY],
[aCenterX, aCenterY],
[bCenterX, bCenterY] as GlobalPoint,
[aCenterX, aCenterY] as GlobalPoint,
GAP,
);
if (intersect.length === 0) {
@@ -1697,8 +1734,8 @@ export class ExcalidrawAutomate {
if (!connectionB) {
const intersect = intersectElementWithLine(
elB,
[aCenterX, aCenterY],
[bCenterX, bCenterY],
[aCenterX, aCenterY] as GlobalPoint,
[bCenterX, bCenterY] as GlobalPoint,
GAP,
);
if (intersect.length === 0) {
@@ -1805,7 +1842,7 @@ export class ExcalidrawAutomate {
viewBackgroundColor: "#FFFFFF",
gridSize: 0
};
};
};
/**
* returns true if MD file is an Excalidraw file
@@ -1824,33 +1861,23 @@ export class ExcalidrawAutomate {
*/
setView(view?: ExcalidrawView | "first" | "active"): ExcalidrawView {
if(!view) {
const v = app.workspace.getActiveViewOfType(ExcalidrawView);
const v = this.plugin.app.workspace.getActiveViewOfType(ExcalidrawView);
if (v instanceof ExcalidrawView) {
this.targetView = v;
}
else {
const leaves =
app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
if (!leaves || leaves.length == 0) {
return;
}
this.targetView = leaves[0].view as ExcalidrawView;
this.targetView = getExcalidrawViews(this.plugin.app)[0];
}
}
if (view == "active") {
const v = app.workspace.getActiveViewOfType(ExcalidrawView);
const v = this.plugin.app.workspace.getActiveViewOfType(ExcalidrawView);
if (!(v instanceof ExcalidrawView)) {
return;
}
this.targetView = v;
}
if (view == "first") {
const leaves =
app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
if (!leaves || leaves.length == 0) {
return;
}
this.targetView = leaves[0].view as ExcalidrawView;
this.targetView = getExcalidrawViews(this.plugin.app)[0];
}
if (view instanceof ExcalidrawView) {
this.targetView = view;
@@ -2421,7 +2448,12 @@ export class ExcalidrawAutomate {
b: readonly [number, number],
gap?: number,
): Point[] {
return intersectElementWithLine(element, a, b, gap);
return intersectElementWithLine(
element,
a as GlobalPoint,
b as GlobalPoint,
gap
);
};
/**
@@ -2615,26 +2647,31 @@ export class ExcalidrawAutomate {
return null;
}
const size = await this.getOriginalImageSize(imgEl, true);
if (size) {
const originalArea = imgEl.width * imgEl.height;
const originalAspectRatio = size.width / size.height;
let newWidth = Math.sqrt(originalArea * originalAspectRatio);
let newHeight = Math.sqrt(originalArea / originalAspectRatio);
const centerX = imgEl.x + imgEl.width / 2;
const centerY = imgEl.y + imgEl.height / 2;
let originalArea, originalAspectRatio;
if(imgEl.crop) {
originalArea = imgEl.width * imgEl.height;
originalAspectRatio = imgEl.crop.width / imgEl.crop.height;
} else {
const size = await this.getOriginalImageSize(imgEl, true);
if (!size) { return false; }
originalArea = imgEl.width * imgEl.height;
originalAspectRatio = size.width / size.height;
}
let newWidth = Math.sqrt(originalArea * originalAspectRatio);
let newHeight = Math.sqrt(originalArea / originalAspectRatio);
const centerX = imgEl.x + imgEl.width / 2;
const centerY = imgEl.y + imgEl.height / 2;
if (newWidth !== imgEl.width || newHeight !== imgEl.height) {
if(!this.getElement(imgEl.id)) {
this.copyViewElementsToEAforEditing([imgEl]);
}
const eaEl = this.getElement(imgEl.id);
eaEl.width = newWidth;
eaEl.height = newHeight;
eaEl.x = centerX - newWidth / 2;
eaEl.y = centerY - newHeight / 2;
return true;
if (newWidth !== imgEl.width || newHeight !== imgEl.height) {
if(!this.getElement(imgEl.id)) {
this.copyViewElementsToEAforEditing([imgEl]);
}
const eaEl = this.getElement(imgEl.id);
eaEl.width = newWidth;
eaEl.height = newHeight;
eaEl.x = centerX - newWidth / 2;
eaEl.y = centerY - newHeight / 2;
return true;
}
return false;
}
@@ -2839,10 +2876,9 @@ export class ExcalidrawAutomate {
}
};
export async function initExcalidrawAutomate(
export function initExcalidrawAutomate(
plugin: ExcalidrawPlugin,
): Promise<ExcalidrawAutomate> {
await initFonts();
): ExcalidrawAutomate {
const ea = new ExcalidrawAutomate(plugin);
//@ts-ignore
window.ExcalidrawAutomate = ea;
@@ -2877,14 +2913,6 @@ function getFontFamily(id: number):string {
return getFontFamilyString({fontFamily:id})
}
export async function initFonts():Promise<void> {
await excalidrawLib.registerFontsInCSS();
const fonts = excalidrawLib.getFontFamilies();
for(let i=0;i<fonts.length;i++) {
if(fonts[i] !== "Local Font") await (document as any).fonts.load(`16px ${fonts[i]}`);
};
}
export function _measureText(
newText: string,
fontSize: number,
@@ -2920,6 +2948,7 @@ async function getTemplate(
frontmatter: string;
files: any;
hasSVGwithBitmap: boolean;
plaintext: string; //markdown data above Excalidraw data and below YAML frontmatter
}> {
const app = plugin.app;
const vault = app.vault;
@@ -2945,6 +2974,7 @@ async function getTemplate(
frontmatter: "",
files: excalidrawData.scene.files,
hasSVGwithBitmap,
plaintext: "",
};
}
@@ -2961,27 +2991,6 @@ async function getTemplate(
}
let scene = excalidrawData.scene;
if (loadFiles) {
//debug({where:"getTemplate",template:file.name,loader:loader.uid});
await loader.loadSceneFiles(excalidrawData, (fileArray: FileData[]) => {
//, isDark: boolean) => {
if (!fileArray || fileArray.length === 0) {
return;
}
for (const f of fileArray) {
if (f.hasSVGwithBitmap) {
hasSVGwithBitmap = true;
}
excalidrawData.scene.files[f.id] = {
mimeType: f.mimeType,
id: f.id,
dataURL: f.dataURL,
created: f.created,
};
}
scene = scaleLoadedImage(excalidrawData.scene, fileArray).scene;
}, depth);
}
let groupElements:ExcalidrawElement[] = scene.elements;
if(filenameParts.hasGroupref) {
@@ -3008,8 +3017,50 @@ async function getTemplate(
));
}
let fileIDWhiteList:Set<FileId>;
if(groupElements.length < scene.elements.length) {
fileIDWhiteList = new Set<FileId>();
groupElements.filter(el=>el.type==="image").forEach((el:ExcalidrawImageElement)=>fileIDWhiteList.add(el.fileId));
}
if (loadFiles) {
//debug({where:"getTemplate",template:file.name,loader:loader.uid});
await loader.loadSceneFiles(excalidrawData, (fileArray: FileData[]) => {
//, isDark: boolean) => {
if (!fileArray || fileArray.length === 0) {
return;
}
for (const f of fileArray) {
if (f.hasSVGwithBitmap) {
hasSVGwithBitmap = true;
}
excalidrawData.scene.files[f.id] = {
mimeType: f.mimeType,
id: f.id,
dataURL: f.dataURL,
created: f.created,
};
}
scene = scaleLoadedImage(excalidrawData.scene, fileArray).scene;
}, depth, false, fileIDWhiteList);
}
excalidrawData.destroy();
const filehead = data.substring(0, trimLocation);
const filehead = getExcalidrawMarkdownHeaderSection(data); // data.substring(0, trimLocation);
let files:any = {};
const sceneFilesSize = Object.values(scene.files).length;
if (sceneFilesSize > 0) {
if(fileIDWhiteList && (sceneFilesSize > fileIDWhiteList.size)) {
Object.values(scene.files).filter((f: any) => fileIDWhiteList.has(f.id)).forEach((f: any) => {
files[f.id] = f;
});
} else {
files = scene.files;
}
}
const frontmatter = filehead.match(/^---\n.*\n---\n/ms)?.[0] ?? filehead;
return {
elements: convertMarkdownLinksToObsidianURLs
? updateElementLinksToObsidianLinks({
@@ -3017,8 +3068,11 @@ async function getTemplate(
hostFile: file,
}) : groupElements,
appState: scene.appState,
frontmatter: filehead.match(/^---\n.*\n---\n/ms)?.[0] ?? filehead,
files: scene.files,
frontmatter,
plaintext: frontmatter !== filehead
? (filehead.split(/^---\n.*\n---\n/ms)?.[1] ?? "")
: "",
files,
hasSVGwithBitmap,
};
}
@@ -3028,6 +3082,7 @@ async function getTemplate(
frontmatter: null,
files: [],
hasSVGwithBitmap,
plaintext: "",
};
}
@@ -3414,7 +3469,7 @@ export const getFrameElementsMatchingQuery = (
el.type === "frame" &&
query.some((q) => {
if (exactMatch) {
const text = el.name.toLowerCase().split("\n")[0].trim();
const text = el.name?.toLowerCase().split("\n")[0].trim() ?? "";
const m = text.match(/^#*(# .*)/);
if (!m || m.length !== 2) {
return false;
@@ -3495,4 +3550,10 @@ export const cloneElement = (el: ExcalidrawElement):any => {
export const verifyMinimumPluginVersion = (requiredVersion: string): boolean => {
return PLUGIN_VERSION === requiredVersion || isVersionNewerThanOther(PLUGIN_VERSION,requiredVersion);
}
}
export const getBoundTextElementId = (container: ExcalidrawElement | null) => {
return container?.boundElements?.length
? container?.boundElements?.find((ele) => ele.type === "text")?.id || null
: null;
};

View File

@@ -17,6 +17,7 @@ import {
FRONTMATTER_KEYS,
refreshTextDimensions,
getContainerElement,
loadSceneFonts,
} from "./constants/constants";
import ExcalidrawPlugin from "./main";
import { TextMode } from "./ExcalidrawView";
@@ -51,6 +52,10 @@ import { getMermaidImageElements, getMermaidText, shouldRenderMermaid } from "./
import { DEBUGGING, debug } from "./utils/DebugHelper";
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
import { updateElementIdsInScene } from "./utils/ExcalidrawSceneUtils";
import { getNewUniqueFilepath } from "./utils/FileUtils";
import { t } from "./lang/helpers";
import { displayFontMessage } from "./utils/ExcalidrawViewUtils";
import { getPDFRect } from "./utils/PDFUtils";
type SceneDataWithFiles = SceneData & { files: BinaryFiles };
@@ -744,6 +749,15 @@ export class ExcalidrawData {
this.deletedElements = this.scene.elements.filter((el:ExcalidrawElement)=>el.isDeleted);
this.scene.elements = this.scene.elements.filter((el:ExcalidrawElement)=>!el.isDeleted);
const timer = window.setTimeout(()=>{
const notice = new Notice(t("FONT_LOAD_SLOW"),15000);
notice.noticeEl.oncontextmenu = () => {
displayFontMessage(this.app);
}
},5000);
const fontFaces = await loadSceneFonts(this.scene.elements);
clearTimeout(timer);
if (!this.scene.files) {
this.scene.files = {}; //loading legacy scenes that do not yet have the files attribute.
@@ -1434,12 +1448,11 @@ export class ExcalidrawData {
if (this.elementLinks.size > 0 || textElementLinks.size > 0) {
outString += `## Element Links\n`;
for (const key of this.elementLinks.keys()) {
outString += `${key}: ${this.elementLinks.get(key)}\n`;
outString += `${key}: ${this.elementLinks.get(key)}\n\n`;
}
for (const key of textElementLinks.keys()) {
outString += `${key}: ${textElementLinks.get(key)}\n`;
outString += `${key}: ${textElementLinks.get(key)}\n\n`;
}
outString += "\n";
}
// deliberately not adding mermaids to here. It is enough to have the mermaidText in the image element's customData
@@ -1449,7 +1462,7 @@ export class ExcalidrawData {
: "";
if (this.equations.size > 0) {
for (const key of this.equations.keys()) {
outString += `${key}: $$${this.equations.get(key).latex}$$\n`;
outString += `${key}: $$${this.equations.get(key).latex}$$\n\n`;
}
}
if (this.files.size > 0) {
@@ -1457,18 +1470,18 @@ export class ExcalidrawData {
const PATHREG = /(^[^#\|]*)/;
const ef = this.files.get(key);
if(ef.isHyperLink || ef.isLocalLink) {
outString += `${key}: ${ef.hyperlink}\n`;
outString += `${key}: ${ef.hyperlink}\n\n`;
} else {
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/829
const path = ef.file
? ef.linkParts.original.replace(PATHREG,this.app.metadataCache.fileToLinktext(ef.file,this.file.path))
: ef.linkParts.original;
const colorMap = ef.colorMap ? " " + JSON.stringify(ef.colorMap) : "";
outString += `${key}: [[${path}]]${colorMap}\n`;
outString += `${key}: [[${path}]]${colorMap}\n\n`;
}
}
}
outString += this.equations.size > 0 || this.files.size > 0 ? "\n" : "";
//outString += this.equations.size > 0 || this.files.size > 0 ? "\n" : "";
const sceneJSONstring = JSON.stringify({
type: this.scene.type,
@@ -1507,31 +1520,40 @@ export class ExcalidrawData {
return result;
}
public async saveDataURLtoVault(dataURL: DataURL, mimeType: MimeType, key: FileId) {
public async saveDataURLtoVault(dataURL: DataURL, mimeType: MimeType, key: FileId, name?:string) {
const scene = this.scene as SceneDataWithFiles;
let fname = `Pasted Image ${window
.moment()
.format("YYYYMMDDHHmmss_SSS")}`;
switch (mimeType) {
case "image/png":
fname += ".png";
break;
case "image/jpeg":
fname += ".jpg";
break;
case "image/svg+xml":
fname += ".svg";
break;
case "image/gif":
fname += ".gif";
break;
default:
fname += ".png";
let fname = name;
if(!fname) {
fname = `Pasted Image ${window
.moment()
.format("YYYYMMDDHHmmss_SSS")}`;
switch (mimeType) {
case "image/png":
fname += ".png";
break;
case "image/jpeg":
fname += ".jpg";
break;
case "image/svg+xml":
fname += ".svg";
break;
case "image/gif":
fname += ".gif";
break;
default:
fname += ".png";
}
}
const x = await getAttachmentsFolderAndFilePath(this.app, this.file.path, fname);
const filepath = getNewUniqueFilepath(this.app.vault,fname,x.folder);
/*
const filepath = (
await getAttachmentsFolderAndFilePath(this.app, this.file.path, fname)
).filepath;
).filepath;*/
const arrayBuffer = await getBinaryFileFromDataURL(dataURL);
if(!arrayBuffer) return null;
@@ -1558,6 +1580,26 @@ export class ExcalidrawData {
return file;
}
private syncCroppedPDFs() {
let dirty = false;
const scene = this.scene as SceneDataWithFiles;
const pdfScale = this.plugin.settings.pdfScale;
scene.elements
.filter(el=>el.type === "image" && el.crop && !el.isDeleted)
.forEach((el: Mutable<ExcalidrawImageElement>)=>{
const ef = this.getFile(el.fileId);
if(!ef.file) return;
if(ef.file.extension !== "pdf") return;
const pageRef = ef.linkParts.original.split("#")?.[1];
if(!pageRef || !pageRef.startsWith("page=") || pageRef.includes("rect")) return;
const restOfLink = el.link ? el.link.match(/&rect=\d*,\d*,\d*,\d*(.*)/)?.[1] : "";
const link = ef.linkParts.original + getPDFRect(el.crop, pdfScale) + (restOfLink ? restOfLink : "]]");
el.link = `[[${link}`;
this.elementLinks.set(el.id, el.link);
dirty = true;
});
}
/**
* deletes fileIds from Excalidraw data for files no longer in the scene
* @returns
@@ -1658,7 +1700,9 @@ export class ExcalidrawData {
await this.saveDataURLtoVault(
scene.files[key].dataURL,
scene.files[key].mimeType,
key as FileId
key as FileId,
//@ts-ignore
scene.files[key].name,
);
}
}
@@ -1676,6 +1720,7 @@ export class ExcalidrawData {
this.updateElementLinksFromScene();
result =
result ||
this.syncCroppedPDFs() ||
this.setLinkPrefix() ||
this.setUrlPrefix() ||
this.setShowLinkBrackets() ||

View File

@@ -3,8 +3,41 @@ import { ImportedDataState } from "@zsviczian/excalidraw/types/excalidraw/data/t
import { BoundingBox } from "@zsviczian/excalidraw/types/excalidraw/element/bounds";
import { ElementsMap, ExcalidrawBindableElement, ExcalidrawElement, ExcalidrawFrameElement, ExcalidrawFrameLikeElement, ExcalidrawTextContainer, ExcalidrawTextElement, FontFamilyValues, FontString, NonDeleted, NonDeletedExcalidrawElement, Theme } from "@zsviczian/excalidraw/types/excalidraw/element/types";
import { FontMetadata } from "@zsviczian/excalidraw/types/excalidraw/fonts/metadata";
import { AppState, BinaryFiles, DataURL, GenerateDiagramToCode, Point, Zoom } from "@zsviczian/excalidraw/types/excalidraw/types";
import { AppState, BinaryFiles, DataURL, GenerateDiagramToCode, Zoom } from "@zsviczian/excalidraw/types/excalidraw/types";
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
import { GlobalPoint } from "@zsviczian/excalidraw/types/math/types";
interface MermaidConfig {
/**
* Whether to start the diagram automatically when the page loads.
* @default false
*/
startOnLoad?: boolean;
/**
* The flowchart curve style.
* @default "linear"
*/
flowchart?: {
curve?: "linear" | "basis";
};
/**
* Theme variables
* @default { fontSize: "25px" }
*/
themeVariables?: {
fontSize?: string;
};
/**
* Maximum number of edges to be rendered.
* @default 1000
*/
maxEdges?: number;
/**
* Maximum number of characters to be rendered.
* @default 1000
*/
maxTextSize?: number;
}
type EmbeddedLink =
| ({
@@ -75,16 +108,16 @@ declare namespace ExcalidrawLib {
function determineFocusDistance(
element: ExcalidrawBindableElement,
a: Point,
b: Point,
a: GlobalPoint,
b: GlobalPoint,
): number;
function intersectElementWithLine(
element: ExcalidrawBindableElement,
a: Point,
b: Point,
a: GlobalPoint,
b: GlobalPoint,
gap?: number,
): Point[];
): GlobalPoint[];
function getCommonBoundingBox(
elements: ExcalidrawElement[] | readonly NonDeleted<ExcalidrawElement>[],
@@ -157,7 +190,7 @@ declare namespace ExcalidrawLib {
function mermaidToExcalidraw(
mermaidDefinition: string,
opts: {fontSize: number},
opts: MermaidConfig,
forceSVG?: boolean,
): Promise<{
elements?: ExcalidrawElement[];
@@ -186,5 +219,6 @@ declare namespace ExcalidrawLib {
separator?: string,
): string;
function safelyParseJSON (json: string): Record<string, any> | null;
function loadSceneFonts(elements: NonDeletedExcalidrawElement[]): Promise<void>;
}

View File

@@ -9,6 +9,8 @@ import {
MarkdownView,
request,
requireApiVersion,
HoverParent,
HoverPopover,
} from "obsidian";
//import * as React from "react";
//import * as ReactDOM from "react-dom";
@@ -63,7 +65,8 @@ import {
cloneElement,
getFrameElementsMatchingQuery,
getElementsWithLinkMatchingQuery,
getImagesMatchingQuery
getImagesMatchingQuery,
getBoundTextElementId
} from "./ExcalidrawAutomate";
import { t } from "./lang/helpers";
import {
@@ -145,6 +148,7 @@ import { Packages } from "./types/types";
import React from "react";
import { diagramToHTML } from "./utils/matic";
import { IS_WORKER_SUPPORTED } from "./workers/compression-worker";
import { getPDFCropRect } from "./utils/PDFUtils";
const EMBEDDABLE_SEMAPHORE_TIMEOUT = 2000;
const PREVENT_RELOAD_TIMEOUT = 2000;
@@ -215,6 +219,27 @@ export const addFiles = async (
if (isDark === undefined) {
isDark = s.scene.appState.theme;
}
// update element.crop naturalWidth and naturalHeight in case scale of PDF loading has changed
// update crop.x crop.y, crop.width, crop.height according to the new scale
files
.filter((f:FileData) => view.excalidrawData.getFile(f.id)?.file?.extension === "pdf")
.forEach((f:FileData) => {
s.scene.elements
.filter((el:ExcalidrawElement)=>el.type === "image" && el.fileId === f.id && el.crop && el.crop.naturalWidth !== f.size.width)
.forEach((el:Mutable<ExcalidrawImageElement>) => {
s.dirty = true;
const scale = f.size.width / el.crop.naturalWidth;
el.crop = {
x: el.crop.x * scale,
y: el.crop.y * scale,
width: el.crop.width * scale,
height: el.crop.height * scale,
naturalWidth: f.size.width,
naturalHeight: f.size.height,
};
});
});
if (s.dirty) {
//debug({where:"ExcalidrawView.addFiles",file:view.file.name,dataTheme:view.excalidrawData.scene.appState.theme,before:"updateScene",state:scene.appState})
view.updateScene({
@@ -251,7 +276,8 @@ type ActionButtons = "save" | "isParsed" | "isRaw" | "link" | "scriptInstall";
let windowMigratedDisableZoomOnce = false;
export default class ExcalidrawView extends TextFileView {
export default class ExcalidrawView extends TextFileView implements HoverParent{
public hoverPopover: HoverPopover;
private freedrawLastActiveTimestamp: number = 0;
public exportDialog: ExportDialog;
public excalidrawData: ExcalidrawData;
@@ -269,7 +295,7 @@ export default class ExcalidrawView extends TextFileView {
private lastLoadedFile: TFile = null;
//store key state for view mode link resolution
private modifierKeyDown: ModifierKeys = {shiftKey:false, metaKey: false, ctrlKey: false, altKey: false}
public currentPosition: {x:number,y:number} = { x: 0, y: 0 };
public currentPosition: {x:number,y:number} = { x: 0, y: 0 }; //these are scene coord thus would be more apt to call them sceneX and sceneY, however due to scrits already using x and y, I will keep it as is
//Obsidian 0.15.0
private draginfoDiv: HTMLDivElement;
public canvasNodeFactory: CanvasNodeFactory;
@@ -277,6 +303,7 @@ export default class ExcalidrawView extends TextFileView {
private embeddableLeafRefs = new Map<ExcalidrawElement["id"], any>();
public semaphores: {
warnAboutLinearElementLinkClick: boolean;
//flag to prevent overwriting the changes the user makes in an embeddable view editing the back side of the drawing
embeddableIsEditingSelf: boolean;
popoutUnload: boolean; //the unloaded Excalidraw view was the last leaf in the popout window
@@ -313,6 +340,7 @@ export default class ExcalidrawView extends TextFileView {
hoverSleep: boolean; //flag with timer to prevent hover preview from being triggered dozens of times
wheelTimeout:number; //used to avoid hover preview while zooming
} | null = {
warnAboutLinearElementLinkClick: true,
embeddableIsEditingSelf: false,
popoutUnload: false,
viewunload: false,
@@ -520,8 +548,8 @@ export default class ExcalidrawView extends TextFileView {
if (!svg) {
return;
}
const serializer = new XMLSerializer();
const svgString = serializer.serializeToString(svg);
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2026
const svgString = svg.outerHTML;
if (file && file instanceof TFile) {
await this.app.vault.modify(file, svgString);
} else {
@@ -763,6 +791,8 @@ export default class ExcalidrawView extends TextFileView {
this.clearPreventReloadTimer();
this.semaphores.preventReload = preventReload;
await this.prepareGetViewData();
//added this to avoid Electron crash when terminating a popout window and saving the drawing, need to check back
//can likely be removed once this is resolved: https://github.com/electron/electron/issues/40607
if(this.semaphores?.viewunload) {
@@ -774,10 +804,10 @@ export default class ExcalidrawView extends TextFileView {
await plugin.app.vault.modify(file,d);
await imageCache.addBAKToCache(file.path,d);
},200)
this.semaphores.saving = false;
return;
}
await this.prepareGetViewData();
await super.save();
if (process.env.NODE_ENV === 'development') {
if (DEBUGGING) {
@@ -931,6 +961,10 @@ export default class ExcalidrawView extends TextFileView {
}
async openLaTeXEditor(eqId: string) {
if(await this.excalidrawData.syncElements(this.getScene())) {
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1994
await this.forceSave(true);
}
const el = this.getViewElements().find((el:ExcalidrawElement)=>el.id === eqId && el.type==="image") as ExcalidrawImageElement;
if(!el) {
return;
@@ -1135,61 +1169,111 @@ export default class ExcalidrawView extends TextFileView {
private getLinkTextForElement(
selectedText:SelectedElementWithLink,
selectedElementWithLink?:SelectedElementWithLink
selectedElementWithLink?:SelectedElementWithLink,
allowLinearElementClick: boolean = false,
): {
linkText: string,
selectedElement: ExcalidrawElement,
isLinearElement: boolean,
} {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.getLinkTextForElement, "ExcalidrawView.getLinkTextForElement", selectedText, selectedElementWithLink);
if (selectedText?.id || selectedElementWithLink?.id) {
const selectedTextElement: ExcalidrawTextElement = selectedText.id
let selectedTextElement: ExcalidrawTextElement = selectedText.id
? this.excalidrawAPI.getSceneElements().find((el:ExcalidrawElement)=>el.id === selectedText.id)
: null;
const selectedElement = selectedElementWithLink.id
? this.excalidrawAPI.getSceneElements().find((el:ExcalidrawElement)=>el.id === selectedElementWithLink.id)
let selectedElement = selectedElementWithLink.id
? this.excalidrawAPI.getSceneElements().find((el:ExcalidrawElement)=>
el.id === selectedElementWithLink.id)
: null;
//if the user clicked on the label of an arrow then the label will be captured in selectedElement, because
//Excalidraw returns the container as the selected element. But in this case we want this to be treated as the
//text element, as the assumption is, if the user wants to invoke the linear element editor for an arrow that has
//a label with a link, then he/she should rather CTRL+click on the arrow line, not the label. CTRL+Click on
//the label is an indication of wanting to navigate.
if (!Boolean(selectedTextElement) && selectedElement?.type === "text") {
const container = getContainerElement(selectedElement, arrayToMap(this.excalidrawAPI.getSceneElements()));
if(container?.type === "arrow") {
const x = getTextElementAtPointer(this.currentPosition,this);
if(x?.id === selectedElement.id) {
selectedTextElement = selectedElement;
selectedElement = null;
}
}
}
//CTRL click on a linear element with a link will navigate instead of line editor
if(!allowLinearElementClick && ["arrow", "line"].includes(selectedElement?.type)) {
return {linkText: selectedElement.link, selectedElement: selectedElement, isLinearElement: true};
}
if (!selectedTextElement && selectedElement?.type === "text") {
if(!allowLinearElementClick) {
//CTRL click on a linear element with a link will navigate instead of line editor
const container = getContainerElement(selectedElement, arrayToMap(this.excalidrawAPI.getSceneElements()));
if(container?.type !== "arrow") {
selectedTextElement = selectedElement as ExcalidrawTextElement;
selectedElement = null;
} else {
const x = this.processLinkText(selectedElement.rawText, selectedElement as ExcalidrawTextElement, container, false);
return {linkText: x.linkText, selectedElement: container, isLinearElement: true};
}
} else {
selectedTextElement = selectedElement as ExcalidrawTextElement;
selectedElement = null;
}
}
let linkText =
selectedElementWithLink?.text ??
(this.textMode === TextMode.parsed
? this.excalidrawData.getRawText(selectedText.id)
: selectedText.text);
if(linkText.startsWith("#")) {
return {linkText, selectedElement: selectedTextElement ?? selectedElement};
}
return {...this.processLinkText(linkText, selectedTextElement, selectedElement), isLinearElement: false};
}
return {linkText: null, selectedElement: null, isLinearElement: false};
}
const maybeObsidianLink = parseObsidianLink(linkText, this.app);
if(typeof maybeObsidianLink === "string") {
linkText = maybeObsidianLink;
}
processLinkText(linkText: string, selectedTextElement: ExcalidrawTextElement, selectedElement: ExcalidrawElement, shouldOpenLink: boolean = true) {
if(!linkText) {
return {linkText: null, selectedElement: null};
}
const partsArray = REGEX_LINK.getResList(linkText);
if (!linkText || partsArray.length === 0) {
//the container link takes precedence over the text link
if(selectedTextElement?.containerId) {
const container = _getContainerElement(selectedTextElement, {elements: this.excalidrawAPI.getSceneElements()});
if(container) {
linkText = container.link;
if(linkText?.startsWith("#")) {
return {linkText, selectedElement: selectedTextElement ?? selectedElement};
}
const maybeObsidianLink = parseObsidianLink(linkText, this.app);
if(typeof maybeObsidianLink === "string") {
linkText = maybeObsidianLink;
}
}
}
if(!linkText || partsArray.length === 0) {
linkText = selectedTextElement?.link;
}
}
if(linkText.startsWith("#")) {
return {linkText, selectedElement: selectedTextElement ?? selectedElement};
}
return {linkText: null, selectedElement: null};
const maybeObsidianLink = parseObsidianLink(linkText, this.app, shouldOpenLink);
if(typeof maybeObsidianLink === "string") {
linkText = maybeObsidianLink;
}
const partsArray = REGEX_LINK.getResList(linkText);
if (!linkText || partsArray.length === 0) {
//the container link takes precedence over the text link
if(selectedTextElement?.containerId) {
const container = _getContainerElement(selectedTextElement, {elements: this.excalidrawAPI.getSceneElements()});
if(container) {
linkText = container.link;
if(linkText?.startsWith("#")) {
return {linkText, selectedElement: selectedTextElement ?? selectedElement};
}
const maybeObsidianLink = parseObsidianLink(linkText, this.app, shouldOpenLink);
if(typeof maybeObsidianLink === "string") {
linkText = maybeObsidianLink;
}
}
}
if(!linkText || partsArray.length === 0) {
linkText = selectedTextElement?.link;
}
}
return {linkText, selectedElement: selectedTextElement ?? selectedElement};
}
async linkClick(
@@ -1197,7 +1281,8 @@ export default class ExcalidrawView extends TextFileView {
selectedText: SelectedElementWithLink,
selectedImage: SelectedImage,
selectedElementWithLink: SelectedElementWithLink,
keys?: ModifierKeys
keys?: ModifierKeys,
allowLinearElementClick: boolean = false,
) {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.linkClick, "ExcalidrawView.linkClick", ev, selectedText, selectedImage, selectedElementWithLink, keys);
if(!selectedText) selectedText = {id:null, text: null};
@@ -1210,10 +1295,17 @@ export default class ExcalidrawView extends TextFileView {
let file = null;
let subpath: string = null;
let {linkText, selectedElement} = this.getLinkTextForElement(selectedText, selectedElementWithLink);
let {linkText, selectedElement, isLinearElement} = this.getLinkTextForElement(selectedText, selectedElementWithLink, allowLinearElementClick);
//if (selectedText?.id || selectedElementWithLink?.id) {
if (selectedElement) {
if (!allowLinearElementClick && linkText && isLinearElement) {
if(this.semaphores.warnAboutLinearElementLinkClick) {
new Notice(t("LINEAR_ELEMENT_LINK_CLICK_ERROR"), 20000);
this.semaphores.warnAboutLinearElementLinkClick = false;
}
return;
}
if (!linkText) {
return;
}
@@ -1292,6 +1384,9 @@ export default class ExcalidrawView extends TextFileView {
}
if (!linkText) {
if(allowLinearElementClick) {
return;
}
new Notice(t("LINK_BUTTON_CLICK_NO_TEXT"), 20000);
return;
}
@@ -1342,7 +1437,7 @@ export default class ExcalidrawView extends TextFileView {
//if link will open in the same pane I want to save the drawing before opening the link
await this.forceSaveIfRequired();
const {leaf, promise} = openLeaf({
const { promise } = openLeaf({
plugin: this.plugin,
fnGetLeaf: () => getLeaf(this.plugin,this.leaf,keys),
file,
@@ -1358,7 +1453,7 @@ export default class ExcalidrawView extends TextFileView {
}
}
async handleLinkClick(ev: MouseEvent | ModifierKeys) {
async handleLinkClick(ev: MouseEvent | ModifierKeys, allowLinearElementClick: boolean = false) {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.handleLinkClick, "ExcalidrawView.handleLinkClick", ev);
this.removeLinkTooltip();
@@ -1376,6 +1471,7 @@ export default class ExcalidrawView extends TextFileView {
selectedImage,
selectedElementWithLink,
ev instanceof MouseEvent ? null : ev,
allowLinearElementClick,
);
}
@@ -1431,6 +1527,10 @@ export default class ExcalidrawView extends TextFileView {
onload() {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onload, "ExcalidrawView.onload");
if(this.plugin.settings.overrideObsidianFontSize) {
document.documentElement.style.fontSize = "";
}
const apiMissing = Boolean(typeof this.containerEl.onWindowMigrated === "undefined")
this.packages = this.plugin.getPackage(this.ownerWindow);
@@ -1501,6 +1601,7 @@ export default class ExcalidrawView extends TextFileView {
if(!this.plugin) {
return;
}
await this.plugin.awaitInit();
//implemented to overcome issue that activeLeafChangeEventHandler is not called when view is initialized from a saved workspace, since Obsidian 1.6.0
let counter = 0;
while(counter++<50 && (!Boolean(this?.plugin?.activeLeafChangeEventHandler) || !Boolean(this.canvasNodeFactory))) {
@@ -1535,16 +1636,19 @@ export default class ExcalidrawView extends TextFileView {
}
};
const onMouseLeave = () => {
const onBlurOrLeave = () => {
if(!this.excalidrawAPI || !this.excalidrawData.loaded || !this.isDirty()) {
return;
}
this.save();
if((this.excalidrawAPI as ExcalidrawImperativeAPI).getAppState().activeTool.type !== "image") {
this.forceSave(true);
}
};
this.registerDomEvent(this.ownerWindow, "keydown", onKeyDown, false);
this.registerDomEvent(this.ownerWindow, "keyup", onKeyUp, false);
this.registerDomEvent(this.contentEl, "mouseleave", onMouseLeave, false);
//this.registerDomEvent(this.contentEl, "mouseleave", onBlurOrLeave, false); //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2004
this.registerDomEvent(this.ownerWindow, "blur", onBlurOrLeave, false);
});
this.setupAutosaveTimer();
@@ -1780,7 +1884,22 @@ export default class ExcalidrawView extends TextFileView {
async onUnloadFile(file: TFile): Promise<void> {
//deliberately not calling super.onUnloadFile() to avoid autosave (saved in unload)
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onUnloadFile,`ExcalidrawView.onUnloadFile, file:${this.file?.name}`);
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onUnloadFile,`ExcalidrawView.onUnloadFile, file:${this.file?.name}`);
let counter = 0;
while (this.semaphores.saving && (counter++ < 200)) {
await sleep(50); //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1988
if(counter++ === 15) {
new Notice(t("SAVE_IS_TAKING_LONG"));
}
if(counter === 80) {
new Notice(t("SAVE_IS_TAKING_VERY_LONG"));
}
}
if(counter >= 200) {
new Notice("Unknown error, save is taking too long");
return;
}
await this.forceSaveIfRequired();
}
private async forceSaveIfRequired():Promise<boolean> {
@@ -1789,9 +1908,9 @@ export default class ExcalidrawView extends TextFileView {
let dirty = false;
//if saving was already in progress
//the function awaits the save to finish.
while (this.semaphores.saving && watchdog++ < 10) {
while (this.semaphores.saving && watchdog++ < 200) {
dirty = true;
await sleep(20);
await sleep(40);
}
if(this.excalidrawAPI) {
this.checkSceneVersion(this.excalidrawAPI.getSceneElements());
@@ -2127,6 +2246,7 @@ export default class ExcalidrawView extends TextFileView {
// clear the view content
clear() {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.clear, "ExcalidrawView.clear");
this.semaphores.warnAboutLinearElementLinkClick = true;
this.viewSaveData = "";
this.canvasNodeFactory.purgeNodes();
this.embeddableRefs.clear();
@@ -2152,6 +2272,7 @@ export default class ExcalidrawView extends TextFileView {
//I am using last loaded file to control when the view reloads.
//It seems text file view gets the modified file event after sync before the modifyEventHandler in main.ts
//reload can only be triggered via reload()
await this.plugin.awaitInit();
if(this.lastLoadedFile === this.file) return;
this.isLoaded = false;
if(!this.file) return;
@@ -2182,6 +2303,7 @@ export default class ExcalidrawView extends TextFileView {
if(!this?.app) {
return;
}
await this.plugin.awaitInit();
let counter = 0;
while ((!this.file || !this.plugin.fourthFontLoaded) && counter++<50) await sleep(50);
if(!this.file) return;
@@ -2304,14 +2426,35 @@ export default class ExcalidrawView extends TextFileView {
});
}
private getGridColor(bgColor: string, st: AppState):{Bold: string, Regular: string} {
private getGridColor(bgColor: string, st: AppState): { Bold: string, Regular: string } {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.getGridColor, "ExcalidrawView.getGridColor", bgColor, st);
const cm = this.plugin.ea.getCM(bgColor);
const isDark = cm.isDark();
const Regular = (isDark ? cm.lighterBy(7) : cm.darkerBy(7)).stringHEX({alpha: false});
const Bold = (isDark ? cm.lighterBy(14) : cm.darkerBy(14)).stringHEX({alpha: false});
return {Bold, Regular};
let Regular: string;
let Bold: string;
const opacity = this.plugin.settings.gridSettings.OPACITY/100;
if (this.plugin.settings.gridSettings.DYNAMIC_COLOR) {
// Dynamic color: concatenate opacity to the HEX string
Regular = (isDark ? cm.lighterBy(10) : cm.darkerBy(10)).alphaTo(opacity).stringRGB({ alpha: true });
Bold = (isDark ? cm.lighterBy(5) : cm.darkerBy(5)).alphaTo(opacity).stringRGB({ alpha: true });
} else {
// Custom color handling
const customCM = this.plugin.ea.getCM(this.plugin.settings.gridSettings.COLOR);
const customIsDark = customCM.isDark();
// Regular uses the custom color directly
Regular = customCM.alphaTo(opacity).stringRGB({ alpha: true });
// Bold is 7 shades lighter or darker based on the custom color's darkness
Bold = (customIsDark ? customCM.lighterBy(10) : customCM.darkerBy(10)).alphaTo(opacity).stringRGB({ alpha: true });
}
return { Bold, Regular };
}
public activeLoader: EmbeddedFilesLoader = null;
private nextLoader: EmbeddedFilesLoader = null;
@@ -2619,6 +2762,10 @@ export default class ExcalidrawView extends TextFileView {
public setDirty(location?:number) {
if(this.semaphores.saving) return; //do not set dirty if saving
if(!this.isDirty()) {
//the autosave timer should start when the first stroke was made... thus avoiding an immediate impact by saving right then
this.resetAutosaveTimer();
}
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.setDirty,`ExcalidrawView.setDirty, location:${location}`);
this.semaphores.dirty = this.file?.path;
this.actionButtons['save'].querySelector("svg").addClass("excalidraw-dirty");
@@ -2627,6 +2774,8 @@ export default class ExcalidrawView extends TextFileView {
}
if(!DEVICE.isMobile) {
if(requireApiVersion("0.16.0")) {
//@ts-ignore
this.leaf.tabHeaderInnerIconEl.style.color="var(--color-accent)"
//@ts-ignore
this.leaf.tabHeaderInnerTitleEl.style.color="var(--color-accent)"
}
@@ -2655,6 +2804,8 @@ export default class ExcalidrawView extends TextFileView {
this.actionButtons['save'].querySelector("svg").removeClass("excalidraw-dirty");
if(!DEVICE.isMobile) {
if(requireApiVersion("0.16.0")) {
//@ts-ignore
this.leaf.tabHeaderInnerIconEl.style.color=""
//@ts-ignore
this.leaf.tabHeaderInnerTitleEl.style.color=""
}
@@ -3088,6 +3239,16 @@ export default class ExcalidrawView extends TextFileView {
};
}
const textId = getBoundTextElementId(selectedElement[0]);
if (textId) {
const textElement = api
.getSceneElements()
.filter((el: any) => el.id === textId && el.link);
if (textElement.length > 0) {
return { id: textElement[0].id, text: textElement[0].text };
}
}
if (selectedElement[0].groupIds.length === 0) {
return { id: null, text: null };
} //is the selected element part of a group?
@@ -3106,6 +3267,7 @@ export default class ExcalidrawView extends TextFileView {
markdownlink: string,
path: string,
alias: string,
originalLink?: string,
) {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.addLink, "ExcalidrawView.addLink", markdownlink, path, alias);
const api = this.excalidrawAPI as ExcalidrawImperativeAPI;
@@ -3119,16 +3281,28 @@ export default class ExcalidrawView extends TextFileView {
}
const selectedElementId = Object.keys(api.getAppState().selectedElementIds)[0];
const selectedElement = api.getSceneElements().find(el=>el.id === selectedElementId);
if(!selectedElement || (selectedElement && selectedElement.link !== null)) {
if(!selectedElement || (!Boolean(originalLink) && (selectedElement && selectedElement.link !== null) )) {
if(selectedElement) new Notice("Selected element already has a link. Inserting link as text.");
this.addText(markdownlink);
return;
}
const ea = getEA(this) as ExcalidrawAutomate;
ea.copyViewElementsToEAforEditing([selectedElement]);
if(originalLink?.match(/\[\[(.*?)\]\]/)?.[1]) {
markdownlink = originalLink.replace(/(\[\[.*?\]\])/,markdownlink);
}
ea.getElement(selectedElementId).link = markdownlink;
await ea.addElementsToView(false, true);
ea.destroy();
if(Boolean(originalLink)) {
this.updateScene({
appState: {
showHyperlinkPopup: {
newValue : "info", oldValue : "editor"
}
}
});
}
}
public async addText (
@@ -3316,6 +3490,13 @@ export default class ExcalidrawView extends TextFileView {
toDelete.forEach((k) => delete files[k]);
}
const activeTool = {...st.activeTool};
if(!["freedraw","hand"].includes(activeTool.type)) {
activeTool.type = "selection";
}
activeTool.customType = null;
activeTool.lastActiveTool = null;
return {
type: "excalidraw",
version: 2,
@@ -3350,7 +3531,7 @@ export default class ExcalidrawView extends TextFileView {
currentStrokeOptions: st.currentStrokeOptions,
frameRendering: st.frameRendering,
objectsSnapModeEnabled: st.objectsSnapModeEnabled,
activeTool: st.activeTool,
activeTool,
},
prevTextMode: this.prevTextMode,
files,
@@ -3378,7 +3559,18 @@ export default class ExcalidrawView extends TextFileView {
}
private clearHoverPreview() {
if (this.hoverPreviewTarget) {
//@ts-ignore
const hoverContainerEl = this.hoverPopover?.containerEl;
//don't auto hide hover-editor
if (this.hoverPopover && !hoverContainerEl?.parentElement?.hasClass("hover-editor")) {
this.hoverPreviewTarget = null;
//@ts-ignore
if(this.hoverPopover.embed?.editor) {
return;
}
//@ts-ignore
this.hoverPopover?.hide();
} else if (this.hoverPreviewTarget) {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.clearHoverPreview, "ExcalidrawView.clearHoverPreview", this);
const event = new MouseEvent("click", {
view: this.ownerWindow,
@@ -3564,7 +3756,7 @@ export default class ExcalidrawView extends TextFileView {
this.app.workspace.trigger("hover-link", {
event: this.lastMouseEvent,
source: VIEW_TYPE_EXCALIDRAW,
hoverParent: this.hoverPreviewTarget,
hoverParent: this,
targetEl: this.hoverPreviewTarget, //null //0.15.0 hover editor!!
linktext: this.plugin.hover.linkText,
sourcePath: this.plugin.hover.sourcePath,
@@ -3593,6 +3785,7 @@ export default class ExcalidrawView extends TextFileView {
private excalidrawDIVonKeyDown(event: KeyboardEvent) {
//(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.excalidrawDIVonKeyDown, "ExcalidrawView.excalidrawDIVonKeyDown", event);
if (this.semaphores?.viewunload) return;
if (event.target === this.excalidrawWrapperRef.current) {
return;
} //event should originate from the canvas
@@ -3611,6 +3804,9 @@ export default class ExcalidrawView extends TextFileView {
if (!this.plugin.settings.allowCtrlClick && !isWinMETAorMacCTRL(e)) {
return;
}
if (Boolean((this.excalidrawAPI as ExcalidrawImperativeAPI)?.getAppState().contextMenu)) {
return;
}
//added setTimeout when I changed onClick(e: MouseEvent) to onPointerDown() in 1.7.9.
//Timeout is required for Excalidraw to first complete the selection action before execution
//of the link click continues
@@ -3700,12 +3896,14 @@ export default class ExcalidrawView extends TextFileView {
return;
}
//dobule click
const now = Date.now();
if ((now - this.doubleClickTimestamp) < 600 && (now - this.doubleClickTimestamp) > 40) {
this.identifyElementClicked();
if(this.plugin.settings.doubleClickLinkOpenViewMode) {
//dobule click
const now = Date.now();
if ((now - this.doubleClickTimestamp) < 600 && (now - this.doubleClickTimestamp) > 40) {
this.identifyElementClicked();
}
this.doubleClickTimestamp = now;
}
this.doubleClickTimestamp = now;
return;
}
if (p.button === "up") {
@@ -3719,10 +3917,18 @@ export default class ExcalidrawView extends TextFileView {
}
}
public updateGridColor(canvasColor?: string, st?: any) {
if(!canvasColor) {
st = (this.excalidrawAPI as ExcalidrawImperativeAPI).getAppState();
canvasColor = canvasColor ?? st.viewBackgroundColor === "transparent" ? "white" : st.viewBackgroundColor;
}
window.setTimeout(()=>this.updateScene({appState:{gridColor: this.getGridColor(canvasColor, st)}, storeAction: "update"}));
}
private canvasColorChangeHook(st: AppState) {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.canvasColorChangeHook, "ExcalidrawView.canvasColorChangeHook", st);
const canvasColor = st.viewBackgroundColor === "transparent" ? "white" : st.viewBackgroundColor;
window.setTimeout(()=>this.updateScene({appState:{gridColor: this.getGridColor(canvasColor, st)}, storeAction: "update"}));
this.updateGridColor(canvasColor,st);
setDynamicStyle(this.plugin.ea,this,canvasColor,this.plugin.settings.dynamicStyling);
if(this.plugin.ea.onCanvasColorChangeHook) {
try {
@@ -3821,6 +4027,11 @@ export default class ExcalidrawView extends TextFileView {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onPaste, "ExcalidrawView.onPaste", data, event);
const api = this.excalidrawAPI as ExcalidrawImperativeAPI;
const ea = this.getHookServer();
if(data?.elements) {
data.elements
.filter(el=>el.type==="text" && !el.hasOwnProperty("rawText"))
.forEach(el=>(el as Mutable<ExcalidrawTextElement>).rawText = (el as ExcalidrawTextElement).originalText);
};
if(data && ea.onPasteHook) {
const res = ea.onPasteHook({
ea,
@@ -3867,7 +4078,20 @@ export default class ExcalidrawView extends TextFileView {
} else {
if(link.match(/^[^#]*#page=\d*(&\w*=[^&]+){0,}&rect=\d*,\d*,\d*,\d*/g)) {
const ea = getEA(this) as ExcalidrawAutomate;
await ea.addImage(this.currentPosition.x, this.currentPosition.y,link);
const imgID = await ea.addImage(this.currentPosition.x, this.currentPosition.y,link.split("&rect=")[0]);
const el = ea.getElement(imgID) as Mutable<ExcalidrawImageElement>;
const fd = ea.imagesDict[el.fileId] as FileData;
el.crop = getPDFCropRect({
scale: this.plugin.settings.pdfScale,
link,
naturalHeight: fd.size.height,
naturalWidth: fd.size.width,
});
if(el.crop) {
el.width = el.crop.width/this.plugin.settings.pdfScale;
el.height = el.crop.height/this.plugin.settings.pdfScale;
}
el.link = `[[${link}]]`;
ea.addElementsToView(false,false).then(()=>ea.destroy());
} else {
const modal = new UniversalInsertFileModal(this.plugin, this);
@@ -4449,8 +4673,20 @@ export default class ExcalidrawView extends TextFileView {
//returns the raw text of the element which is the original text without parsing
//in compatibility mode, returns the original text, and for backward compatibility the text if originalText is not available
private onBeforeTextEdit (textElement: ExcalidrawTextElement) {
private onBeforeTextEdit (textElement: ExcalidrawTextElement, isExistingElement: boolean): string {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onBeforeTextEdit, "ExcalidrawView.onBeforeTextEdit", textElement);
/*const api = this.excalidrawAPI as ExcalidrawImperativeAPI;
const st = api.getAppState();
setDynamicStyle(
this.plugin.ea,
this,
st.viewBackgroundColor === "transparent" ? "white" : st.viewBackgroundColor,
this.plugin.settings.dynamicStyling,
api.getColorAtScenePoint({sceneX: this.currentPosition.x, sceneY: this.currentPosition.y})
);*/
if(!isExistingElement) {
return;
}
window.clearTimeout(this.isEditingTextResetTimer);
this.isEditingTextResetTimer = null;
this.semaphores.isEditingText = true; //to prevent autoresize on mobile when keyboard pops up
@@ -4675,6 +4911,7 @@ export default class ExcalidrawView extends TextFileView {
null,
{id: element.id, text: link},
event,
true,
);
return;
}
@@ -4870,6 +5107,15 @@ export default class ExcalidrawView extends TextFileView {
new Notice("Image successfully converted to local file");
}
private insertLinkAction(linkVal: string) {
let link = linkVal.match(/\[\[(.*?)\]\]/)?.[1];
if(!link) {
link = linkVal.replaceAll("[","").replaceAll("]","");
link = link.split("|")[0].trim();
}
this.plugin.insertLinkDialog.start(this.file.path, (markdownlink: string, path:string, alias:string) => this.addLink(markdownlink, path, alias, linkVal), link);
}
private onContextMenu(elements: readonly ExcalidrawElement[], appState: AppState, onClose: (callback?: () => void) => void) {
const React = this.packages.react;
const contextMenuActions = [];
@@ -4884,7 +5130,7 @@ export default class ExcalidrawView extends TextFileView {
t("OPEN_LINK_CLICK"),
() => {
const event = emulateKeysForLinkClick("new-tab");
this.handleLinkClick(event);
this.handleLinkClick(event, true);
},
onClose
),
@@ -5018,7 +5264,8 @@ export default class ExcalidrawView extends TextFileView {
React,
t("COPY_DRAWING_LINK"),
() => {
navigator.clipboard.writeText(`![[${this.file.path}]]`);
const path = this.file.path.match(/(.*)(\.md)$/)?.[1];
navigator.clipboard.writeText(`![[${path ?? this.file.path}]]`);
},
onClose
),
@@ -5254,14 +5501,14 @@ export default class ExcalidrawView extends TextFileView {
);
}
private renderWelcomeScreen () {
if(!this.plugin.settings.showSplashscreen) return null;
private renderWelcomeScreen() {
if (!this.plugin.settings.showSplashscreen) return null;
const React = this.packages.react;
const {WelcomeScreen} = this.packages.excalidrawLib;
const filecount = this.app.vault.getFiles().filter(f=>this.plugin.isExcalidrawFile(f)).length;
const { WelcomeScreen } = this.packages.excalidrawLib;
const filecount = this.app.vault.getFiles().filter(f => this.plugin.isExcalidrawFile(f)).length;
const rank = filecount < 200 ? "Bronze" : filecount < 750 ? "Silver" : filecount < 2000 ? "Gold" : "Platinum";
const nextRankDelta = filecount < 200 ? 200 - filecount : filecount < 750 ? 750 - filecount : filecount < 2000 ? 2000 - filecount : 0;
const {decoration, title} = SwordColors[rank as Rank];
const { decoration, title } = SwordColors[rank as Rank];
return React.createElement(
WelcomeScreen,
{},
@@ -5281,20 +5528,22 @@ export default class ExcalidrawView extends TextFileView {
WelcomeScreen.Center.Heading,
{
color: decoration,
message: nextRankDelta > 0 ? `${rank}: ${nextRankDelta} more drawings until the next rank!` : `${rank}: You're at the top. Keep on being legendary!`,
message: nextRankDelta > 0
? `${rank}: ${nextRankDelta} ${t("WELCOME_RANK_NEXT")}`
: `${rank}: ${t("WELCOME_RANK_LEGENDARY")}`,
},
title,
),
React.createElement(
WelcomeScreen.Center.Heading,
{},
"Type \"Excalidraw\" in the Command Palette",
t("WELCOME_COMMAND_PALETTE"),
React.createElement("br"),
"Explore the Obsidian Menu in the top right",
t("WELCOME_OBSIDIAN_MENU"),
React.createElement("br"),
"Visit the Script Library",
t("WELCOME_SCRIPT_LIBRARY"),
React.createElement("br"),
"Find help in the hamburger-menu",
t("WELCOME_HELP_MENU"),
),
React.createElement(
WelcomeScreen.Center.Menu,
@@ -5305,9 +5554,9 @@ export default class ExcalidrawView extends TextFileView {
icon: ICONS.YouTube,
href: "https://www.youtube.com/@VisualPKM",
shortcut: null,
"aria-label": "Visual PKM YouTube Channel",
"aria-label": t("WELCOME_YOUTUBE_ARIA"),
},
" Check out the Visual PKM YouTube channel."
t("WELCOME_YOUTUBE_LINK")
),
React.createElement(
WelcomeScreen.Center.MenuItemLink,
@@ -5315,9 +5564,9 @@ export default class ExcalidrawView extends TextFileView {
icon: ICONS.Discord,
href: "https://discord.gg/DyfAXFwUHc",
shortcut: null,
"aria-label": "Join the Discord Server",
"aria-label": t("WELCOME_DISCORD_ARIA"),
},
" Join the Discord Server"
t("WELCOME_DISCORD_LINK")
),
React.createElement(
WelcomeScreen.Center.MenuItemLink,
@@ -5325,9 +5574,9 @@ export default class ExcalidrawView extends TextFileView {
icon: ICONS.twitter,
href: "https://twitter.com/zsviczian",
shortcut: null,
"aria-label": "Follow me on Twitter",
"aria-label": t("WELCOME_TWITTER_ARIA"),
},
" Follow me on Twitter"
t("WELCOME_TWITTER_LINK")
),
React.createElement(
WelcomeScreen.Center.MenuItemLink,
@@ -5335,9 +5584,9 @@ export default class ExcalidrawView extends TextFileView {
icon: ICONS.Learn,
href: "https://visual-thinking-workshop.com",
shortcut: null,
"aria-label": "Learn Visual PKM",
"aria-label": t("WELCOME_LEARN_ARIA"),
},
" Sign up for the Visual Thinking Workshop"
t("WELCOME_LEARN_LINK")
),
React.createElement(
WelcomeScreen.Center.MenuItemLink,
@@ -5345,9 +5594,9 @@ export default class ExcalidrawView extends TextFileView {
icon: ICONS.heart,
href: "https://ko-fi.com/zsolt",
shortcut: null,
"aria-label": "Donate to support Excalidraw-Obsidian",
"aria-label": t("WELCOME_DONATE_ARIA"),
},
" Say \"Thank You\" & support the plugin."
t("WELCOME_DONATE_LINK")
),
)
)
@@ -5688,8 +5937,8 @@ export default class ExcalidrawView extends TextFileView {
renderWebview: DEVICE.isDesktop,
renderEmbeddable: this.renderEmbeddable.bind(this),
renderMermaid: shouldRenderMermaid,
obsidianHostPlugin: new WeakRef(this.plugin),
showDeprecatedFonts: true,
insertLinkAction: this.insertLinkAction.bind(this),
},
this.renderCustomActionsMenu(),
this.renderWelcomeScreen(),
@@ -5711,6 +5960,7 @@ export default class ExcalidrawView extends TextFileView {
}
) {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.instantiateExcalidraw, "ExcalidrawView.instantiateExcalidraw", initdata);
await this.plugin.awaitInit();
while(!this.semaphores.scriptsReady) {
await sleep(50);
}

View File

@@ -29,6 +29,7 @@ import { FILENAMEPARTS, PreviewImageType } from "./utils/UtilTypes";
import { CustomMutationObserver, debug, DEBUGGING } from "./utils/DebugHelper";
import { getExcalidrawFileForwardLinks } from "./utils/ExcalidrawViewUtils";
import { linkPrompt } from "./dialogs/Prompt";
import { isHTMLElement } from "./utils/typechecks";
interface imgElementAttributes {
file?: TFile;
@@ -352,7 +353,8 @@ const getIMG = async (
);
const cacheReady = imageCache.isReady();
await plugin.awaitInit();
switch (plugin.settings.previewImageType) {
case PreviewImageType.PNG: {
const img = createEl("img");
@@ -374,7 +376,9 @@ const getIMG = async (
const addSVGToImgSrc = (img: HTMLImageElement, svg: SVGSVGElement, cacheReady: boolean, cacheKey: ImageKey):HTMLImageElement => {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(addSVGToImgSrc, `MarkdownPostProcessor.ts > addSVGToImgSrc`);
const svgString = new XMLSerializer().serializeToString(svg);
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2026
//const svgString = new XMLSerializer().serializeToString(svg);
const svgString = svg.outerHTML;
const blob = new Blob([svgString], { type: 'image/svg+xml' });
const blobUrl = URL.createObjectURL(blob);
img.setAttribute("src", blobUrl);
@@ -403,12 +407,13 @@ const createImgElement = async (
let timer:number;
const clickEvent = (ev:PointerEvent) => {
if(!(ev.target instanceof Element)) {
if (!isHTMLElement(ev.target)) {
return;
}
const containerElement = ev.target.hasClass("excalidraw-embedded-img")
const targetElement = ev.target as HTMLElement;
const containerElement = targetElement.hasClass("excalidraw-embedded-img")
? ev.target
: getParentOfClass(ev.target, "excalidraw-embedded-img");
: getParentOfClass(targetElement, "excalidraw-embedded-img");
if (!containerElement) {
return;
}
@@ -587,6 +592,102 @@ const processInternalEmbed = async (internalEmbedEl: Element, file: TFile ):Prom
return await createImageDiv(attr);
}
function getDimensionsFromAliasString(data: string) {
const dimensionRegex = /^(?<width>\d+%|\d+)(x(?<height>\d+%|\d+))?$/;
const heightOnlyRegex = /^x(?<height>\d+%|\d+)$/;
const match = data.match(dimensionRegex) || data.match(heightOnlyRegex);
if (match) {
const { width, height } = match.groups;
// Ensure width and height do not start with '0'
if ((width && width.startsWith('0') && width !== '0') ||
(height && height.startsWith('0') && height !== '0')) {
return null;
}
return {
width: width || undefined,
height: height || undefined,
};
}
// If the input starts with a 0 or is a decimal, return null
if (/^0\d|^\d+\.\d+/.test(data)) {
return null;
}
return null;
}
type AliasParts = { alias?: string, width?: string, height?: string, style?: string };
function parseAlias(input: string):AliasParts {
const result:AliasParts = {};
const parts = input.split('|').map(part => part.trim());
switch (parts.length) {
case 1:
const singleMatch = getDimensionsFromAliasString(parts[0]);
if (singleMatch) {
return singleMatch; // Return dimensions if valid
}
result.style = parts[0]; // Otherwise, return as style
break;
case 2:
const firstDim = getDimensionsFromAliasString(parts[0]);
const secondDim = getDimensionsFromAliasString(parts[1]);
if (secondDim) {
result.alias = parts[0];
result.width = secondDim.width;
result.height = secondDim.height;
} else if (firstDim) {
result.width = firstDim.width;
result.height = firstDim.height;
result.style = parts[1]; // Second part is style
} else {
result.alias = parts[0];
result.style = parts[1]; // Assuming second part is style
}
break;
case 3:
const middleMatch = getDimensionsFromAliasString(parts[1]);
if (middleMatch) {
result.alias = parts[0];
result.width = middleMatch.width;
result.height = middleMatch.height;
result.style = parts[2];
} else {
result.alias = parts[0];
result.style = parts[2]; // Last part is style
}
break;
default:
const secondValue = getDimensionsFromAliasString(parts[1]);
if (secondValue) {
result.alias = parts[0];
result.width = secondValue.width;
result.height = secondValue.height;
result.style = parts[parts.length - 1]; // Last part is style
} else {
result.alias = parts[0];
result.style = parts[parts.length - 1]; // Last part is style
}
break;
}
// Clean up the result to remove undefined properties
Object.keys(result).forEach((key: keyof AliasParts) => {
if (result[key] === undefined) {
delete result[key];
}
});
return result;
}
const processAltText = (
fname: string,
alt:string,
@@ -594,19 +695,11 @@ const processAltText = (
) => {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(processAltText, `MarkdownPostProcessor.ts > processAltText`);
if (alt && !alt.startsWith(fname)) {
//2:width, 3:height, 4:style 12 3 4
const parts = alt.match(/[^\|\d]*\|?((\d*%?)x?(\d*%?))?\|?(.*)/);
attr.fwidth = parts[2] ?? attr.fwidth;
attr.fheight = parts[3] ?? attr.fheight;
if (parts[4] && !parts[4].startsWith(fname)) {
attr.style = [`excalidraw-svg${`-${parts[4]}`}`];
}
if (
(!parts[4] || parts[4]==="") &&
(!parts[2] || parts[2]==="") &&
parts[0] && parts[0] !== ""
) {
attr.style = [`excalidraw-svg${`-${parts[0]}`}`];
const aliasParts = parseAlias(alt);
attr.fwidth = aliasParts.width ?? attr.fwidth;
attr.fheight = aliasParts.height ?? attr.fheight;
if (aliasParts.style && !aliasParts.style.startsWith(fname)) {
attr.style = [`excalidraw-svg${`-${aliasParts.style}`}`];
}
}
}
@@ -793,9 +886,12 @@ export const markdownPostProcessor = async (
el: HTMLElement,
ctx: MarkdownPostProcessorContext,
) => {
await plugin.awaitSettings();
const isPrinting = Boolean(document.body.querySelectorAll("body > .print").length>0);
//firstElementChild: https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1956
const isFrontmatter = el.hasClass("mod-frontmatter") || el.firstElementChild?.hasClass("frontmatter");
const isFrontmatter = el.hasClass("mod-frontmatter") ||
el.firstElementChild?.hasClass("frontmatter") ||
el.firstElementChild?.hasClass("block-language-yaml");
if(isPrinting && isFrontmatter) {
return;
}

View File

@@ -1,11 +1,12 @@
import {
App,
Instruction,
normalizePath,
TAbstractFile,
TFile,
WorkspaceLeaf,
} from "obsidian";
import { PLUGIN_ID, VIEW_TYPE_EXCALIDRAW } from "./constants/constants";
import { PLUGIN_ID } from "./constants/constants";
import ExcalidrawView from "./ExcalidrawView";
import ExcalidrawPlugin from "./main";
import { ButtonDefinition, GenericInputPrompt, GenericSuggester } from "./dialogs/Prompt";
@@ -14,6 +15,7 @@ import { splitFolderAndFilename } from "./utils/FileUtils";
import { getEA } from "src";
import { ExcalidrawAutomate } from "./ExcalidrawAutomate";
import { WeakArray } from "./utils/WeakArray";
import { getExcalidrawViews } from "./utils/ObsidianUtils";
export type ScriptIconMap = {
[key: string]: { name: string; group: string; svgString: string };
@@ -21,6 +23,7 @@ export type ScriptIconMap = {
export class ScriptEngine {
private plugin: ExcalidrawPlugin;
private app: App;
private scriptPath: string;
//https://stackoverflow.com/questions/60218638/how-to-force-re-render-if-map-value-changes
public scriptIconMap: ScriptIconMap;
@@ -28,6 +31,7 @@ export class ScriptEngine {
constructor(plugin: ExcalidrawPlugin) {
this.plugin = plugin;
this.app = plugin.app;
this.scriptIconMap = {};
this.loadScripts();
this.registerEventHandlers();
@@ -57,7 +61,7 @@ export class ScriptEngine {
if (!path.endsWith(".svg")) {
return;
}
const scriptFile = app.vault.getAbstractFileByPath(
const scriptFile = this.app.vault.getAbstractFileByPath(
getIMGFilename(path, "md"),
);
if (scriptFile && scriptFile instanceof TFile) {
@@ -106,19 +110,19 @@ export class ScriptEngine {
registerEventHandlers() {
this.plugin.registerEvent(
this.plugin.app.vault.on(
this.app.vault.on(
"delete",
(file: TFile)=>this.deleteEventHandler(file)
),
);
this.plugin.registerEvent(
this.plugin.app.vault.on(
this.app.vault.on(
"create",
(file: TFile)=>this.createEventHandler(file)
),
);
this.plugin.registerEvent(
this.plugin.app.vault.on(
this.app.vault.on(
"rename",
(file: TAbstractFile, oldPath: string)=>this.renameEventHandler(file, oldPath)
),
@@ -137,15 +141,16 @@ export class ScriptEngine {
public getListofScripts(): TFile[] {
this.scriptPath = this.plugin.settings.scriptFolderPath;
if (!app.vault.getAbstractFileByPath(this.scriptPath)) {
//this.scriptPath = null;
if(!this.scriptPath) return;
this.scriptPath = normalizePath(this.scriptPath);
if (!this.app.vault.getAbstractFileByPath(this.scriptPath)) {
return;
}
return app.vault
return this.app.vault
.getFiles()
.filter(
(f: TFile) =>
f.path.startsWith(this.scriptPath) && f.extension === "md",
f.path.startsWith(this.scriptPath+"/") && f.extension === "md",
);
}
@@ -165,7 +170,10 @@ export class ScriptEngine {
}
const subpath = path.split(`${this.scriptPath}/`)[1];
const lastSlash = subpath.lastIndexOf("/");
if(!subpath) {
console.warn(`ScriptEngine.getScriptName unexpected basename: ${basename}; path: ${path}`)
}
const lastSlash = subpath?.lastIndexOf("/");
if (lastSlash > -1) {
return subpath.substring(0, lastSlash + 1) + basename;
}
@@ -174,10 +182,10 @@ export class ScriptEngine {
async addScriptIconToMap(scriptPath: string, name: string) {
const svgFilePath = getIMGFilename(scriptPath, "svg");
const file = app.vault.getAbstractFileByPath(svgFilePath);
const file = this.app.vault.getAbstractFileByPath(svgFilePath);
const svgString: string =
file && file instanceof TFile
? await app.vault.read(file)
? await this.app.vault.read(file)
: null;
this.scriptIconMap = {
...this.scriptIconMap,
@@ -198,12 +206,12 @@ export class ScriptEngine {
name: `(Script) ${scriptName}`,
checkCallback: (checking: boolean) => {
if (checking) {
return Boolean(app.workspace.getActiveViewOfType(ExcalidrawView));
return Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView));
}
const view = app.workspace.getActiveViewOfType(ExcalidrawView);
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
if (view) {
(async()=>{
const script = await app.vault.read(f);
const script = await this.app.vault.read(f);
if(script) {
//remove YAML frontmatter if present
this.executeScript(view, script, scriptName,f);
@@ -217,7 +225,7 @@ export class ScriptEngine {
}
unloadScripts() {
const scripts = app.vault
const scripts = this.app.vault
.getFiles()
.filter((f: TFile) => f.path.startsWith(this.scriptPath));
scripts.forEach((f) => {
@@ -235,11 +243,11 @@ export class ScriptEngine {
const commandId = `${PLUGIN_ID}:${basename}`;
// @ts-ignore
if (!this.plugin.app.commands.commands[commandId]) {
if (!this.app.commands.commands[commandId]) {
return;
}
// @ts-ignore
delete this.plugin.app.commands.commands[commandId];
delete this.app.commands.commands[commandId];
}
async executeScript(view: ExcalidrawView, script: string, title: string, file: TFile) {
@@ -270,7 +278,7 @@ export class ScriptEngine {
ScriptEngine.inputPrompt(
view,
this.plugin,
this.plugin.app,
this.app,
header,
placeholder,
value,
@@ -287,7 +295,7 @@ export class ScriptEngine {
instructions?: Instruction[],
) =>
ScriptEngine.suggester(
this.plugin.app,
this.app,
displayItems,
items,
hint,
@@ -303,10 +311,8 @@ export class ScriptEngine {
}
private updateToolPannels() {
const leaves =
this.plugin.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
leaves.forEach((leaf: WorkspaceLeaf) => {
const excalidrawView = leaf.view as ExcalidrawView;
const excalidrawViews = getExcalidrawViews(this.app);
excalidrawViews.forEach(excalidrawView => {
excalidrawView.toolsPanelRef?.current?.updateScriptIconMap(
this.scriptIconMap,
);

View File

@@ -82,7 +82,7 @@ export const obsidianToExcalidrawMap: { [key: string]: string } = {
};
export const {
export let {
sceneCoordsToViewportCoords,
viewportCoordsToSceneCoords,
determineFocusDistance,
@@ -103,9 +103,37 @@ export const {
getContainerElement,
refreshTextDimensions,
getCSSFontDefinition,
loadSceneFonts,
} = 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,
} = 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;", "["));
@@ -167,6 +195,10 @@ 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"},
@@ -194,6 +226,7 @@ export const FRONTMATTER_KEYS:{[key:string]: {name: string, type: string, depric
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;

View File

@@ -0,0 +1,48 @@
import { App, FileView, WorkspaceLeaf } from "obsidian";
import { VIEW_TYPE_EXCALIDRAW_LOADING } from "src/constants/constants";
import ExcalidrawPlugin from "src/main";
import { setExcalidrawView } from "src/utils/ObsidianUtils";
export function switchToExcalidraw(app: App) {
const leaves = app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW_LOADING).filter(l=>l.view instanceof ExcalidrawLoading);
leaves.forEach(l=>(l.view as ExcalidrawLoading).switchToeExcalidraw());
}
export class ExcalidrawLoading extends FileView {
constructor(leaf: WorkspaceLeaf, private plugin: ExcalidrawPlugin) {
super(leaf);
this.displayLoadingText();
}
public onload() {
super.onload();
this.displayLoadingText();
}
public switchToeExcalidraw() {
setExcalidrawView(this.leaf);
}
getViewType(): string {
return VIEW_TYPE_EXCALIDRAW_LOADING;
}
getDisplayText() {
return "Loading Excalidraw... " + (this.file?.basename ?? "");
}
private displayLoadingText() {
// Create a div element for displaying the text
const loadingTextEl = this.contentEl.createEl("div", {
text: this.getDisplayText()
});
// Apply styling to center the text
loadingTextEl.style.display = "flex";
loadingTextEl.style.alignItems = "center";
loadingTextEl.style.justifyContent = "center";
loadingTextEl.style.height = "100%";
loadingTextEl.style.fontSize = "1.5em"; // Adjust size as needed
}
}

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,10 +1,12 @@
import { App, FuzzySuggestModal, TFile } from "obsidian";
import { REG_LINKINDEX_INVALIDCHARS } from "../constants/constants";
import { FuzzyMatch, FuzzySuggestModal, setIcon } from "obsidian";
import { AUDIO_TYPES, CODE_TYPES, ICON_NAME, IMAGE_TYPES, REG_LINKINDEX_INVALIDCHARS, VIDEO_TYPES } from "../constants/constants";
import { t } from "../lang/helpers";
import ExcalidrawPlugin from "src/main";
import { getLink } from "src/utils/FileUtils";
import { LinkSuggestion } from "src/types/types";
export class InsertLinkDialog extends FuzzySuggestModal<TFile> {
export class InsertLinkDialog extends FuzzySuggestModal<LinkSuggestion> {
private addText: Function;
private drawingPath: string;
@@ -28,7 +30,7 @@ export class InsertLinkDialog extends FuzzySuggestModal<TFile> {
this.emptyStateText = t("NO_MATCH");
}
getItems(): any[] {
getItems(): LinkSuggestion[] {
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/422
return (
this.app.metadataCache
@@ -39,11 +41,11 @@ export class InsertLinkDialog extends FuzzySuggestModal<TFile> {
);
}
getItemText(item: any): string {
getItemText(item: LinkSuggestion): string {
return item.path + (item.alias ? `|${item.alias}` : "");
}
onChooseItem(item: any): void {
onChooseItem(item: LinkSuggestion): void {
let filepath = item.path;
if (item.file) {
filepath = this.app.metadataCache.fileToLinktext(
@@ -56,6 +58,65 @@ export class InsertLinkDialog extends FuzzySuggestModal<TFile> {
this.addText(getLink(this.plugin,{embed: false, path: filepath, alias: item.alias}), filepath, item.alias);
}
renderSuggestion(result: FuzzyMatch<LinkSuggestion>, itemEl: HTMLElement) {
const { item, match: matches } = result || {};
itemEl.addClass("mod-complex");
const contentEl = itemEl.createDiv("suggestion-content");
const auxEl = itemEl.createDiv("suggestion-aux");
const titleEl = contentEl.createDiv("suggestion-title");
const noteEl = contentEl.createDiv("suggestion-note");
if (!item) {
titleEl.setText(this.emptyStateText);
itemEl.addClass("is-selected");
return;
}
const path = item.file?.path ?? item.path;
const pathLength = path.length - (item.file?.name.length ?? 0);
const matchElements = matches.matches.map((m) => {
return createSpan("suggestion-highlight");
});
const itemText = this.getItemText(item);
for (let i = pathLength; i < itemText.length; i++) {
const match = matches.matches.find((m) => m[0] === i);
if (match) {
const element = matchElements[matches.matches.indexOf(match)];
titleEl.appendChild(element);
element.appendText(itemText.substring(match[0], match[1]));
i += match[1] - match[0] - 1;
continue;
}
titleEl.appendText(itemText[i]);
}
noteEl.setText(path);
if(!item.file) {
setIcon(auxEl, "ghost");
} else if(this.plugin.isExcalidrawFile(item.file)) {
setIcon(auxEl, ICON_NAME);
} else if (item.file.extension === "md") {
setIcon(auxEl, "square-pen");
} else if (IMAGE_TYPES.includes(item.file.extension)) {
setIcon(auxEl, "image");
} else if (VIDEO_TYPES.includes(item.file.extension)) {
setIcon(auxEl, "monitor-play");
} else if (AUDIO_TYPES.includes(item.file.extension)) {
setIcon(auxEl, "file-audio");
} else if (CODE_TYPES.includes(item.file.extension)) {
setIcon(auxEl, "file-code");
} else if (item.file.extension === "canvas") {
setIcon(auxEl, "layout-dashboard");
} else if (item.file.extension === "pdf") {
setIcon(auxEl, "book-open-text");
} else {
auxEl.setText(item.file.extension);
}
}
onClose(): void {
window.setTimeout(()=>{
this.addText = null
@@ -63,9 +124,19 @@ export class InsertLinkDialog extends FuzzySuggestModal<TFile> {
super.onClose();
}
public start(drawingPath: string, addText: Function) {
private inLink: string;
onOpen(): void {
super.onOpen();
if(this.inLink) {
this.inputEl.value = this.inLink;
this.inputEl.dispatchEvent(new Event('input'));
}
}
public start(drawingPath: string, addText: Function, link?: string) {
this.addText = addText;
this.drawingPath = drawingPath;
this.inLink = link;
this.open();
}
}

View File

@@ -3,7 +3,7 @@ import ExcalidrawView from "../ExcalidrawView";
import ExcalidrawPlugin from "../main";
import { getPDFDoc } from "src/utils/FileUtils";
import { Modal, Setting, TextComponent } from "obsidian";
import { FileSuggestionModal } from "./FolderSuggester";
import { FileSuggestionModal } from "../Components/Suggesters/FileSuggestionModal";
import { getEA } from "src";
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
@@ -206,7 +206,11 @@ export class InsertPDFModal extends Modal {
const search = new TextComponent(ce);
search.inputEl.style.width = "100%";
const suggester = new FileSuggestionModal(this.app, search,app.vault.getFiles().filter((f: TFile) => f.extension.toLowerCase() === "pdf"));
const suggester = new FileSuggestionModal(
this.app,
search,this.app.vault.getFiles().filter((f: TFile) => f.extension.toLowerCase() === "pdf"),
this.plugin
);
search.onChange(async () => {
const file = suggester.getSelectedItem();
await setFile(file);

View File

@@ -15,7 +15,228 @@ export const RELEASE_NOTES: { [k: string]: string } = {
I develop this plugin as a hobby, spending my free time doing this. If you find it valuable, then please say THANK YOU or...
<div class="ex-coffee-div"><a href="https://ko-fi.com/zsolt"><img src="https://cdn.ko-fi.com/cdn/kofi3.png?v=3" height=45></a></div>
<div class="ex-coffee-div"><a href="https://ko-fi.com/zsolt"><img src="https://storage.ko-fi.com/cdn/kofi6.png?v=6" border="0" alt="Buy Me a Coffee at ko-fi.com" height=45></a></div>
`,
"2.6.8":`
## New
- **QoL improvements**:
- Obsidian-link search button in Element Link Editor.
- Add Any File now searches file aliases as well.
- Cosmetic changes to file search modals (display path, show file type icon).
- Text Element cursor-color matches the text color.
- New script in script store: [Image Occlusion](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Image%20Occlusion.md) by [@TrillStones](https://github.com/TrillStones) 🙏
## Fixed
- Excalidraw icon on the **ribbon menu kept reappearing** every time you reopen Obsidian [#2115](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2115)
- In pen mode, when **single-finger panning** is enabled, Excalidraw should still **allow actions with the mouse**.
- When **editing a drawing in split mode** (drawing is on one side, markdown view is on the other), editing the markdown note sometimes causes the drawing to re-zoom and jump away from the selected area.
- Hover-Editor compatibility resolved [2041](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2041)
- ${String.fromCharCode(96)}ExcalidrawAutomate.create() ${String.fromCharCode(96)} will now correctly include the markdown text in templates above Excalidraw Data and below YAML front matter. This also fixes the same issue with the **Deconstruct Selected Element script**.
`,
"2.6.7":`
Hoping to finally move on to 2.7.0... but still have one last bug to fix in 2.6.x!
## Fixed
I misread a line in the Excalidraw package code... ended up breaking image loading in 2.6.6. The icon library script didn't work right, and updating nested drawings caused all images in the scene to be dropped from memory. This led to image-placeholders in exports and broke copy-paste to Excalidraw.com and between drawings. I am surprised no one reported it! 😳
`,
"2.6.6":`
## Fixed
- Images and LaTeX formulas did not update in the scene when the source was changed until the Excalidraw drawing was closed and reopened. [#2105](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2105)
`,
"2.6.5":`
## Fixed
- Text sizing issue in the drawing that is first loaded after Obsidian restarts [#2086](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2086)
- Excalidraw didn't load if there was a file in the Excalidraw folder with a name that starts the same way as the Scripts folder name. [#2095](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2095)
- **OVERSIZED EXCALIDRAW TOOLBAR**: Added a new setting under "Excalidraw Appearance and Behavior > Theme and Styling" called "Limit Obsidian Font Size to Editor Text." This setting is off by default. When enabled, it restricts Obsidian's custom font size adjustments to editor text only, preventing unintended scaling of Excalidraw UI elements and other themes that rely on the default interface font size. Feel free to experiment with this setting to improve Excalidraw UI consistency. However, because this change affects the broader Obsidian UI, it's recommended to turn it off if any layout issues arise. [#2087](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2087)`,
"2.6.4":`
## Fixed
- Error saving when cropping images embedded from a URL (not from a file in the Vault) [#2096](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2096)
`,
"2.6.3":`
<div class="excalidraw-videoWrapper"><div>
<iframe src="https://www.youtube.com/embed/OfUWAvCgbXk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div></div>
## New
- **Cropping PDF Pages**
- Improved PDF++ cropping: You can now double-click cropped images in Excalidraw to adjust the crop area, which will also appear as a highlight in PDF++. This feature applies to PDF cut-outs created in version 2.6.3 and beyond.
- **Insert Last Active PDF Page as Image**
- New command palette action lets you insert the currently active PDF page into Excalidraw. Ideal for setups with PDF and Excalidraw side-by-side. You can assign a hotkey for quicker access. Cropped areas in Excalidraw will show as highlights in PDF++.
## Fixed
- Fixed **Close Settings** button toggle behavior [#2085](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2085)
- Resolved text wrapping issues causing layout shifts due to trailing whitespaces [#8714](https://github.com/excalidraw/excalidraw/pull/8714)
- **Aspect Ratio and Size Reset** commands now function correctly with cropped images.
- **Cropped Drawings**: Adjustments to cropped Excalidraw drawings are now supported. However, for nested Excalidraw drawings, it's recommended to use area, group, and frame references instead of cropping.
## Refactoring
- Further font loading optimizations on Excalidraw.com; no impact expected in Obsidian [#8693](https://github.com/excalidraw/excalidraw/pull/8693)
- Text wrapping improvements [#8715](https://github.com/excalidraw/excalidraw/pull/8715)
- Plugin initiation and error handling
`,
"2.6.2":`
## Fixed
- Image scaling issue with SVGs that miss the width and height property. [#8729](https://github.com/excalidraw/excalidraw/issues/8729)
`,
"2.6.1":`
## New
- Pen-mode single-finger panning enabled also for the "Selection" tool.
- You can disable pen-mode single-finger panning in Plugin Settings under Excalidraw Appearance and Behavior [#2080](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2080)
## Fixed
- Text tool did not work in pen-mode using finger [#2080](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2080)
- Pasting images to Excalidraw from the web resulted in filenames of "image_1.png", "image_2.png" instead of "Pasted Image TIMESTAMP" [#2081](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2081)
`,
"2.6.0":`
## Performance
- Much faster plugin initialization. Down from 1000-3000ms to 100-300ms. According to my testing speed varies on a wide spectrum depending on device, size of Vault and other plugins being loaded. I measured values ranging from 84ms up to 782ms [#2068](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2068)
- Faster loading of scenes with many embedded illustrations or PDF pages.
- SVG export results in even smaller files by further optimizing which characters are included in the embedded fonts. [#8641](https://github.com/excalidraw/excalidraw/pull/8641)
## New
- Image cropping tool. Double click the image to crop it. [#8613](https://github.com/excalidraw/excalidraw/pull/8613)
- Single finger panning in pen mode.
- Native handwritten CJK Font support [8530](https://github.com/excalidraw/excalidraw/pull/8530)
- Created a new **Fonts** section in settings. This includes configuration of the "Local Font" and downloading of the CJK fonts in case you need them offline.
- Option under **Appearance and Behavior / Link Click** to disable double-click link navigation in view mode. [#2075](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2075)
- New RU translation 🙏[@tovBender](https://github.com/tovBender)
## Updated
- CN translation 🙏[@dmscode](https://github.com/dmscode)
`,
"2.5.2": `
## Fixed
- Text became disconnected from sticky notes (rectangle/ellipse/diamond + text) if the sticky note contained a link (e.g., URL or wiki link), and in some cases, triggered a save error warning. [#2054](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2054)
- Long-clicking to open an Excalidraw drawing from a markdown note did not work when the note was in an Obsidian pop-out window.
- Active tool was deactivated after autosave, requiring the user to reselect the tool.
## Minor changes to default settings
- I adjusted some of the default settings. This change only affects new installs of Excalidraw; existing installs and settings remain unchanged:
- **Reuse Adjacent Pane** is now the default for opening new drawings. Excalidraw will try to open the drawing in the most recently used adjacent pane, if available.
- **Focus on Existing Tab** is the default for reopening an already open drawing. Excalidraw will switch to the existing tab where the drawing is open, instead of creating a new one.
- **Autosave Interval** is now set to a default value of 1 minute on Desktop and 30 seconds on mobile platforms.
`,
"2.5.1": `
## New
- Excalidraw will now save images using the filename from the file system when adding an image via the image tool (in the top toolbar).
- Increased the maximum image size from a width/height of 1440 to 2880 when adding an image via the image tool in the top toolbar.
- Flip arrowheads: If you have an arrow bound to elements, select only the arrow (not the bound elements) and press SHIFT+H or SHIFT+V to swap the arrowheads. [#8525](https://github.com/excalidraw/excalidraw/pull/8525)
## Fixed
- Zoom
- "Zoom to Fit" did not work correctly when multiple Obsidian tabs were open, and Excalidraw was in a lower tab. Additionally, there was an offset when the left side panel was open, especially if the panel was relatively large compared to the canvas area. [#2039](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2039)
- SHIFT+1 and SHIFT+2 will now honor the max zoom setting in Plugin Settings.
- Adding images using the image tool in the toolbar was unreliable. Sometimes it worked, sometimes it didn't, depending on whether the drawing had unsaved changes. Autosave was causing the issue with the image tool. [#1992](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1992)
- Frame related issues
- Fixed an issue where links to the back of the note were broken if an unnamed frame was present in the scene. [#2027](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2027)
- Frame transclusion was not working when there was a LaTeX equation anywhere in the scene. [#2028](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2028)
- Frame settings and rounded image corners were not honored when exporting (and auto-exporting) SVGs. [#2026](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2026)
- Resolved issues with the width, height, and style parsing of Excalidraw drawings embedded in Markdown notes. ${String.fromCharCode(96)}![[my file|10 - my alias]]${String.fromCharCode(96)} was incorrectly parsed as a width of 10 and a style of "- my alias."
- Links
- When navigating element links, selecting a #tag from the link-list did not open the Obsidian tag in the search.
- False-positive tag results in second-order links list.
- Arrow label links did not work as expected. Since CTRL/CMD+Click is used in Excalidraw to start the line editor, the solution is not straightforward from a UX perspective. [#2023](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2023)
- You can open arrow links by ctrl+clicking on the label itself. If the arrow or line element contains the link, ctrl+click on the link indicator in the top right.
- You can also right-click the linear element and select "Open Link" from the context menu.
- Various elbow-arrow fixes and QoL improvements from excalidraw.com [#8324](https://github.com/excalidraw/excalidraw/pull/8324), [#8448](https://github.com/excalidraw/excalidraw/pull/8448), [#8440](https://github.com/excalidraw/excalidraw/pull/8440)
`,
"2.5.0": `
The new [Community Wiki](https://excalidraw-obsidian.online/Hobbies/Excalidraw+Blog/WIKI/Welcome+to+the+WIKI) is waiting for your contribution!
## Fixed
- Regression from 2.4.3: Text flickers when editing text in a container [#2015](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2015).
- Significantly improved the performance of group and frame [image fragments](https://youtu.be/sjZfdqpxqsg) when the source drawing includes many images, but the fragment does not.
- Minor styling tweaks. Note that with Obsidian 1.7.1, the font size and zoom settings in Obsidian > Appearance will affect the size of buttons and menu items in Excalidraw as well.
## New
- New Canvas Search from Excalidraw.com (CTRL/CMD+F). The "old" search is still available in the Obsidian Command Palette _"Search for text in drawing"_. The old search will also search in image-file names and frame titles, but the result set is not as sophisticated as the one built by Excalidraw.com. If you want to use the old search, you can set up a hotkey in Obsidian settings, e.g., CTRL+ALT/CMD+OPT+F. [#8438](https://github.com/excalidraw/excalidraw/pull/8438)
- Grid Color settings under **Excalidraw Appearance and Behavior**. Note that the grid color and opacity also affect the color and transparency of the binding box when using the arrow tool. [#2007](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2007)
- Refactoring the code to be compatible with the upcoming Obsidian 1.7.2.
- ${String.fromCharCode(96)}ExcalidrawAutomate.decompressFromBase64()${String.fromCharCode(96)} will now remove line breaks from the input string so you can directly supply the compressed JSON string for decompression by script.
`,
"2.4.3": `
Check out the [Excalidraw Plugin's Community WIKI](https://excalidraw-obsidian.online/Hobbies/Excalidraw+Blog/WIKI/Welcome+to+the+WIKI) and help with your content contribution.
## Fixed
- In some situations Excalidraw hangs indefinitely when opening a different file in the same tab
- Can't exit arrow tool on phone [#2006](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2006)
- Save is triggered every few seconds, leading to glitches in handwriting [#2004](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2004)
- Canvas shifts when editing text reaches right hand side of the canvas, especially at higher zoom values
- Minor styling tweaks to adapt to Obsidian 1.7.1 new stylesheet, in particular to scale Excalidraw properly in line with Obsidian Appearance Setting Font-Size value.
- Tweaked Compatibilty Setting description to mention Obsidian 1.7.1 Footnotes support
`,
"2.4.2": `
This release addresses critical issues for all Obsidian Mobile users.
#### 😞 I made two mistakes...
- I expedited the 2.4.1 release to resolve a major bug, but in doing so, I skippes some of my usual rigorous testing
- I included a new feature in 2.4.1 fresh from Excalidraw.com because I believe it brings significant end user benefits and did not want to wait until October. However, a small part of this feature was designed for server-side execution on Excalidraw.com, not for local use on Obsidian Mobile.
Despite the recent two (2.4.1 and 2.4.2) emergency bug-fix releases, this doesn't deviate from the [monthly release schedule](https://youtu.be/2poSS-Z91lY). The next feature update is still planned for early October.
## Fixes:
- **Excalidraw rendering issues on Obsidian Mobile:** [#1995](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1995), [#1997](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1997)
- Nested Excalidraw drawings with text failed to render.
- Drawings in Markdown view didn't render if they contained text and were set to SVG Image or SVG Native (they worked with PNG).
- SVG export failed for drawings containing text.
- **LaTeX equation duplication:** After using ALT+Drag to duplicate a LaTeX equation, editing the duplicate modified the original instead. [#1984](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1994)
- **Unreadable Obsidian search results:** When drawings contained numerous Element Links and Embedded Files Links, search results became unreadable. This fix will apply to files saved after installing the update. [#1999](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1999)
`,
"2.4.1": `
This release includes an important fix that can result in your drawing being overwritten by another drawing. Please update to this version as soon as possible.
## Fixed
- A problem where switching between two Excalidraw documents in the same tab could result in the content from the first document overwriting the second one, particularly when the first document was large. [#1988](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1988)
- Styling issue when Obsidian font size is set to the non-default value.
- Embedding a block reference ${String.fromCharCode(96)}![[file#^block]]${String.fromCharCode(96)} where the file is an excalidraw file incorrectly replaced the embedding with the image instead of the block of text.
## New
- Improved SVG export to include only the necessary glyphs for .woff2 fonts, minimizing file sizes. Note that this feature is currently supported only for .woff2 files; other font formats like .ttf and .otf will be fully embedded, leading to larger SVG files. I recommend using .woff2 files whenever possible.
`,
"2.4.0": `
<div class="excalidraw-videoWrapper"><div>
<iframe src="https://www.youtube.com/embed/LtuAaqY_DNc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div></div>
## New
- Flowcharts with CTRL/CMD+Arrow and ALT/OPT+Arrow keys
- Improved PDF Support
- PDF++ cropped area paste
- Import PDF into frames
- Element links with metadata
- Obisidan Hotkey overrides
- Support for Zotero style markdown links
## QoL
- Much improved freedraw flow, less autosave glitches
- Link editor CTRL+Meta/(CTRL+CMD) + click or via the command palette "Open the image-link or LaTeX-formula editor.
- Improved search and search results
- Disable double tap ereaser activation in pen mode
- Single click editing of markdown embeddables
- Set grid size and frequency
- Improved paste
- Pan & Zoom while editing Text
- Save active too-state (e.g. tool-lock) with the drawing
- Show/hide "sword" splashscreen in new drawings
## Fixed
- Duplicate line points when Alt+click adding new points in line editor- - Excalidraw Automate measureText, impacting gate placement in ExcaliBrain
- If a group includes a frame, the image reference will include all the elements in the frame, not just the frame
- Excalidraw rendering issues in markdown preview
- Markdown pages embedded in Excalidraw were broken
- Drawing did not save arrow type
- Fixed rendering of newly pasted links
## ExcalidrawAutomate
- new functions
- tex2dataURL
- addElementsToFrame
- resetImageAspectRatio
- Changed
- getViewSelectedElements(includeFrameChildren: boolean = true);
- getOriginalImageSize with option to wait for the image to load
`,
"2.3.0": `
I am moving to a new release approach aiming to publish one update per month to the Obsidian script store. If you want to continue to receive more frequent updates with new features and minor bug fixes, then join the beta testing team. [#1912](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1912)

View File

@@ -1,12 +1,13 @@
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
import { ColorComponent, Modal, Setting, SliderComponent, TextComponent, ToggleComponent } from "obsidian";
import { COLOR_NAMES, VIEW_TYPE_EXCALIDRAW } from "src/constants/constants";
import { ColorComponent, Modal, Setting, TextComponent, ToggleComponent } from "obsidian";
import { COLOR_NAMES } from "src/constants/constants";
import ExcalidrawView from "src/ExcalidrawView";
import ExcalidrawPlugin from "src/main";
import { setPen } from "src/menu/ObsidianMenu";
import { ExtendedFillStyle, PenStyle, PenType } from "src/PenTypes";
import { ExtendedFillStyle, PenType } from "src/PenTypes";
import { getExcalidrawViews } from "src/utils/ObsidianUtils";
import { PENS } from "src/utils/Pens";
import { fragWithHTML, getExportPadding, getExportTheme, getPNGScale, getWithBackground } from "src/utils/Utils";
import { fragWithHTML } from "src/utils/Utils";
import { __values } from "tslib";
const EASINGFUNCTIONS: Record<string,string> = {
@@ -65,9 +66,7 @@ export class PenSettingsModal extends Modal {
async onClose() {
if(this.dirty) {
app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> {
if (v.view instanceof ExcalidrawView) v.view.updatePinnedCustomPens()
})
getExcalidrawViews(this.app).forEach(excalidrawView=>excalidrawView.updatePinnedCustomPens());
this.plugin.saveSettings();
const pen = this.plugin.settings.customPens[this.pen]
const api = this.view.excalidrawAPI;

View File

@@ -713,27 +713,70 @@ export class ConfirmationPrompt extends Modal {
}
}
export async function linkPrompt (
linkText:string,
export async function linkPrompt(
linkText: string,
app: App,
view?: ExcalidrawView,
message: string = "Select link to open",
):Promise<[file:TFile, linkText:string, subpath: string]> {
const linksArray = REGEX_LINK.getResList(linkText);
const tagsArray = REGEX_TAGS.getResList(linkText);
message: string = t("SELECT_LINK_TO_OPEN"),
): Promise<[file: TFile, linkText: string, subpath: string]> {
const linksArray = REGEX_LINK.getResList(linkText).filter(x => Boolean(x.value));
const links = linksArray.map(x => REGEX_LINK.getLink(x));
// Create a map to track duplicates by base link (without rect reference)
const linkMap = new Map<string, number[]>();
links.forEach((link, i) => {
const linkBase = link.split("&rect=")[0];
if (!linkMap.has(linkBase)) linkMap.set(linkBase, []);
linkMap.get(linkBase).push(i);
});
// Determine indices to keep
const indicesToKeep = new Set<number>();
linkMap.forEach(indices => {
if (indices.length === 1) {
// Only one link, keep it
indicesToKeep.add(indices[0]);
} else {
// Multiple links: prefer the one with rect reference, if available
const rectIndex = indices.find(i => links[i].includes("&rect="));
if (rectIndex !== undefined) {
indicesToKeep.add(rectIndex);
} else {
// No rect reference in duplicates, add the first one
indicesToKeep.add(indices[0]);
}
}
});
// Final validation to ensure each duplicate group has at least one entry
linkMap.forEach(indices => {
const hasKeptEntry = indices.some(i => indicesToKeep.has(i));
if (!hasKeptEntry) {
// Add the first index if none were kept
indicesToKeep.add(indices[0]);
}
});
// Filter linksArray, links, itemsDisplay, and items based on indicesToKeep
const filteredLinksArray = linksArray.filter((_, i) => indicesToKeep.has(i));
const tagsArray = REGEX_TAGS.getResList(linkText.replaceAll(/([^\s])#/g, "$1 ")).filter(x => Boolean(x.value));
let subpath: string = null;
let file: TFile = null;
let parts = linksArray[0] ?? tagsArray[0];
let parts = filteredLinksArray[0] ?? tagsArray[0];
// Generate filtered itemsDisplay and items arrays
const itemsDisplay = [
...linksArray.filter(p=> Boolean(p.value)).map(p => {
...filteredLinksArray.map(p => {
const alias = REGEX_LINK.getAliasOrLink(p);
return alias === "100%" ? REGEX_LINK.getLink(p) : alias;
}),
...tagsArray.filter(x=> Boolean(x.value)).map(x => REGEX_TAGS.getTag(x)),
...tagsArray.map(x => REGEX_TAGS.getTag(x)),
];
const items = [
...linksArray.filter(p=>Boolean(p.value)),
...tagsArray.filter(x=> Boolean(x.value)),
...filteredLinksArray,
...tagsArray,
];
if (items.length>1) {

View File

@@ -22,7 +22,7 @@ export class ScriptInstallPrompt extends Modal {
searchBar.type = "text";
searchBar.id = "search-bar";
searchBar.placeholder = "Search...";
searchBar.style.width = "calc(100% - 120px)"; // space for the buttons and hit count
//searchBar.style.width = "calc(100% - 120px)"; // space for the buttons and hit count
const nextButton = document.createElement("button");
nextButton.textContent = "→";

View File

@@ -654,6 +654,12 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
"the function will use ea.targetView.file",
after: "",
},
{
field: "getAPI",
code: "public getAPI(view?:ExcalidrawView):ExcalidrawAutomate",
desc: "Returns a new instance of ExcalidrawAutomate.",
after: "",
},
{
field: "getAttachmentFilepath",
code: "async getAttachmentFilepath(filename: string): Promise<string>",
@@ -907,7 +913,7 @@ export const FRONTMATTER_KEYS_INFO: SuggesterInfo[] = [
after: ": 1",
},
{
field: "excalidraw-export-embed-scene",
field: "export-embed-scene",
code: null,
desc: "If this key is present it will override the default excalidraw embed and export setting.",
after: ": false",

View File

@@ -2,7 +2,7 @@ import { ButtonComponent, DropdownComponent, TFile, ToggleComponent } from "obsi
import ExcalidrawView from "../ExcalidrawView";
import ExcalidrawPlugin from "../main";
import { Modal, Setting, TextComponent } from "obsidian";
import { FileSuggestionModal } from "./FolderSuggester";
import { FileSuggestionModal } from "../Components/Suggesters/FileSuggestionModal";
import { IMAGE_TYPES, sceneCoordsToViewportCoords, viewportCoordsToSceneCoords, MAX_IMAGE_SIZE, ANIMATED_IMAGE_TYPES, MD_EX_SECTIONS } from "src/constants/constants";
import { insertEmbeddableToView, insertImageToView } from "src/utils/ExcalidrawViewUtils";
import { getEA } from "src";
@@ -146,7 +146,9 @@ export class UniversalInsertFileModal extends Modal {
const suggester = new FileSuggestionModal(
this.app,
search,
this.app.vault.getFiles().filter((f: TFile) => sections?.length > 0 || f!==this.view.file));
this.app.vault.getFiles().filter((f: TFile) => sections?.length > 0 || f!==this.view.file),
this.plugin
);
search.onChange(() => {
file = suggester.getSelectedItem();
updateForm();

View File

@@ -3,7 +3,7 @@ import "obsidian";
//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 type { Point } from "src/types/types";
export const getEA = (view?:any): any => {
try {
return window.ExcalidrawAutomate.getAPI(view);

View File

@@ -1,3 +1,4 @@
import { FILE } from "dns";
import {
DEVICE,
FRONTMATTER_KEYS,
@@ -5,8 +6,13 @@ import {
import { TAG_AUTOEXPORT, TAG_MDREADINGMODE, TAG_PDFEXPORT } from "src/constants/constSettingsTags";
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/ModifierkeyHelper";
const CJK_FONTS = "CJK Fonts";
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",
@@ -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,6 +104,11 @@ 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
NO_SEARCH_RESULT: "Didn't find a matching element in the drawing",
@@ -128,7 +140,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:
@@ -153,6 +168,24 @@ export default {
REMOVE_LINK: "Remove text element link",
LASER_ON: "Enable laser pointer",
LASER_OFF: "Disable laser pointer",
WELCOME_RANK_NEXT: "more drawings until the next rank!",
WELCOME_RANK_LEGENDARY: "You're at the top. Keep on being legendary!",
WELCOME_COMMAND_PALETTE: 'Type "Excalidraw" in the Command Palette',
WELCOME_OBSIDIAN_MENU: "Explore the Obsidian Menu in the top right",
WELCOME_SCRIPT_LIBRARY: "Visit the Script Library",
WELCOME_HELP_MENU: "Find help in the hamburger-menu",
WELCOME_YOUTUBE_ARIA: "Visual PKM YouTube Channel",
WELCOME_YOUTUBE_LINK: "Check out the Visual PKM YouTube channel.",
WELCOME_DISCORD_ARIA: "Join the Discord Server",
WELCOME_DISCORD_LINK: "Join the Discord Server",
WELCOME_TWITTER_ARIA: "Follow me on Twitter",
WELCOME_TWITTER_LINK: "Follow me on Twitter",
WELCOME_LEARN_ARIA: "Learn Visual PKM",
WELCOME_LEARN_LINK: "Sign up for the Visual Thinking Workshop",
WELCOME_DONATE_ARIA: "Donate to support Excalidraw-Obsidian",
WELCOME_DONATE_LINK: 'Say "Thank You" & support the plugin.',
SAVE_IS_TAKING_LONG: "Saving your previous file is taking a long time. Please wait...",
SAVE_IS_TAKING_VERY_LONG: "For better performance, consider splitting large drawings into several smaller files.",
//settings.ts
RELEASE_NOTES_NAME: "Display Release Notes after update",
@@ -166,7 +199,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",
@@ -180,10 +213,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:
@@ -192,7 +225,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 " +
@@ -298,6 +331,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",
@@ -331,6 +369,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>"+
@@ -377,6 +416,14 @@ 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%).",
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",
@@ -402,6 +449,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. " +
@@ -635,7 +683,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. " +
@@ -718,17 +766,35 @@ 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:
"By turning this on, you will see a local font in the font list on the properties panel for text elements. " +
"Files that use this local font will (partly) lose their platform independence. " +
"Depending on the custom font set in settings, they will look differently when loaded in another vault, or at a later time. " +
"Also the 4th font will display as system default font on excalidraw.com, or other Excalidraw versions.",
"Enabling this option will add a local font to the font list in the properties panel for text elements. " +
"Be aware that using this local font may compromise platform independence. " +
"Files using the custom font might render differently when opened in a different vault or at a later time, depending on the font settings. " +
"Additionally, the 4th font will default to the system font on excalidraw.com or other Excalidraw versions.",
FOURTH_FONT_NAME: "Local font file",
FOURTH_FONT_DESC:
"Select a .ttf, .woff or .woff2 font file from your vault to use as the local font. " +
"If no file is selected, Excalidraw will use the Virgil font by default.",
"Select a .otf, .ttf, .woff, or .woff2 font file from your vault to use as the local font. " +
"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",
@@ -785,6 +851,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",
@@ -874,4 +969,7 @@ 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",
};

View File

@@ -1,3 +1,859 @@
// русский
import {
DEVICE,
FRONTMATTER_KEYS,
} 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,3 +1,4 @@
import { FILE } from "dns";
import {
DEVICE,
FRONTMATTER_KEYS,
@@ -5,8 +6,13 @@ import {
import { TAG_AUTOEXPORT, TAG_MDREADINGMODE, TAG_PDFEXPORT } from "src/constants/constSettingsTags";
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/ModifierkeyHelper";
const CJK_FONTS = "CJK Fonts";
declare const PLUGIN_VERSION:string;
// 简体中文
export default {
// Sugester
SELECT_FILE_TO_INSERT: "选择一个要插入的文件",
// main.ts
CONVERT_URL_TO_FILE: "从 URL 下载图像到本地",
UNZIP_CURRENT_FILE: "解压当前 Excalidraw 文件",
@@ -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: "将“背景笔记”卡片保存到文件",
@@ -97,6 +104,11 @@ export default {
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
NO_SEARCH_RESULT: "在绘图中未找到匹配的元素",
@@ -128,7 +140,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:
@@ -153,6 +168,24 @@ export default {
REMOVE_LINK: "移除文字元素链接",
LASER_ON: "启用激光笔",
LASER_OFF: "关闭激光笔",
WELCOME_RANK_NEXT: "张绘图之后,可以到达下一等级!",
WELCOME_RANK_LEGENDARY: "您已是绘图大师,请续写传奇~",
WELCOME_COMMAND_PALETTE: '在命令面板中输入 "Excalidraw"',
WELCOME_OBSIDIAN_MENU: "探索右上角的 Obsidian 菜单",
WELCOME_SCRIPT_LIBRARY: "访问脚本库",
WELCOME_HELP_MENU: "在汉堡菜单(三横线)中寻找帮助",
WELCOME_YOUTUBE_ARIA: "可视化个人知识管理的 YouTube 频道",
WELCOME_YOUTUBE_LINK: "查看可视化个人知识管理的 YouTube 频道",
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: "显示更新说明",
@@ -166,7 +199,7 @@ export default {
BASIC_HEAD: "基本",
BASIC_DESC: `包括:更新说明,更新提示,新绘图文件、模板文件、脚本文件的存储路径等的设置。`,
FOLDER_NAME: "Excalidraw 文件夹",
FOLDER_NAME: "Excalidraw 文件夹(區分大小寫!)",
FOLDER_DESC:
"新绘图的默认存储路径。若为空,将在库的根目录中创建新绘图。",
CROP_PREFIX_NAME: "剪贴文件的前缀",
@@ -180,10 +213,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:
@@ -192,7 +225,7 @@ export default {
"在命令面板中执行“新建绘图”系列命令时," +
"新建的绘图文件的存储路径。<br>" +
"<b>开启:</b>使用上面的 Excalidraw 文件夹。 <br><b>关闭:</b>使用 Obsidian 设置的新附件默认位置。",
TEMPLATE_NAME: "Excalidraw 模板文件",
TEMPLATE_NAME: "Excalidraw 模板文件(區分大小寫!)",
TEMPLATE_DESC:
"Excalidraw 模板文件(文件夹)的存储路径。<br>" +
"<b>模板文件:</b>比如:如果您的模板在默认的 Excalidraw 文件夹中且文件名是 " +
@@ -297,7 +330,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 界面颜色",
@@ -331,6 +369,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>"+
@@ -377,7 +416,15 @@ FILENAME_HEAD: "文件名",
ZOOM_TO_FIT_MAX_LEVEL_NAME: "自动缩放的最高级别",
ZOOM_TO_FIT_MAX_LEVEL_DESC:
"自动缩放画布时,允许放大的最高级别。该值不能低于 0.550%)且不能超过 101000%)。",
LASER_HEAD: "激光笔工具More Tools > Laser pointer",
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 秒)。",
@@ -402,6 +449,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 将会聚焦到现有的标签页上 " +
@@ -635,7 +683,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。这将在控制台中启用调试消息。这对于排查问题很有帮助。" +
@@ -676,7 +724,7 @@ FILENAME_HEAD: "文件名",
"我建议尝试多个值来设置这个参数。当您放大一个较大的 PNG 图像时,如果图像突然从视图中消失,那就说明您已经达到了极限。默认值为 1。此设置对 iOS 无效。",
CUSTOM_PEN_HEAD: "自定义画笔",
CUSTOM_PEN_NAME: "自定义画笔工具的数量",
CUSTOM_PEN_DESC: "在画布上的 Obsidian 菜单按钮旁边切换自定义画笔。长按画笔按钮可以修改其样式。",
CUSTOM_PEN_DESC: "在画布上的 Obsidian 菜单按钮旁边切换自定义画笔。长按(双击)画笔按钮可以修改其样式。",
EXPERIMENTAL_HEAD: "杂项",
EXPERIMENTAL_DESC: `包括:默认的 LaTeX 公式字段建议绘图文件的类型标识符OCR 等设置。`,
EA_HEAD: "Excalidraw 自动化",
@@ -718,17 +766,35 @@ FILENAME_HEAD: "文件名",
"启用此功能简化了 Excalidraw 前置属性的使用,使您能够利用许多强大的设置。如果您不希望自动加载这些属性," +
"您可以禁用此功能,但您将需要手动从自动提示中移除任何不需要的属性。" +
"请注意,启用此设置需要重启插件,因为属性是在启动时加载的。",
FONTS_HEAD: "字体",
FONTS_DESC: "配置本地字体并下载的 CJK 字体以供 Excalidraw 使用。",
CUSTOM_FONT_HEAD: "本地字体",
ENABLE_FOURTH_FONT_NAME: "为文本元素启用本地字体",
ENABLE_FOURTH_FONT_DESC:
"开启此项后,文本元素的属性面板里会多出一个本地字体按钮。<br>" +
"使用了本地字体的绘图文件,将会失去一部分跨平台能力 —— " +
"若将绘图文件移动到其他库中打开,显示效果可能会截然不同;" +
"若在 excalidraw.com 或其他版本的 Excalidraw 中打开,使用本地字体的文本会变回系统默认字体。",
"启用此选项将在文本元素的属性面板的字体列表中添加一个本地字体。" +
"请注意,使用这个本地字体可能会破坏平台的独立性。" +
"使用自定义字体的文件在不同的库中打开或在以后打开时,根据字体设置,可能会以不同的方式呈现。" +
"此外,在excalidraw.com 或其他 Excalidraw 版本中,默认的本地字体字体将使用系统字体。",
FOURTH_FONT_NAME: "本地字体文件",
FOURTH_FONT_DESC:
"选择库文件夹中的一个 .ttf.woff 或 .woff2 字体文件作为本地字体文件。" +
"若未选择文件,则使用默认的 Virgil 字体。",
"从您的库中选择一个 .otf、.ttf.woff 或 .woff2 字体文件作为本地字体使用。"+
"如果没有选择文件,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光学符号识别",
@@ -785,6 +851,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: "进入全屏模式",
@@ -874,4 +969,7 @@ 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 文件过大,请尝试较小的分辨率",
};

File diff suppressed because it is too large Load Diff

View File

@@ -777,7 +777,11 @@ export const penIcon = (pen: PenStyle) => {
strokeLinecap="round"
strokeLinejoin="round"
>
<path fill={pen.strokeColor??"var(--icon-fill-color)"} strokeWidth="2" d="m9 11-6 6v3h9l3-3"></path>
<path fill={
pen.strokeColor??"var(--icon-fill-color)"}
strokeWidth="2" d="m9 11-6 6v3h9l3-3"
style={pen.strokeColor ? { filter: "var(--theme-filter)" } : {}}
></path>
<path fill="none" strokeWidth="2" d="m22 12-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4"></path>
</svg>
)
@@ -791,6 +795,7 @@ export const penIcon = (pen: PenStyle) => {
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
style={pen.strokeColor ? { filter: "var(--theme-filter)" } : {}}
>
<path strokeWidth="2" d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path>
</svg>
@@ -822,6 +827,7 @@ export const penIcon = (pen: PenStyle) => {
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
style={pen.strokeColor ? { filter: "var(--theme-filter)" } : {}}
>
<path d="M331 315l158.4-215L460.1 70.6 245 229 331 315zm-187 5l0 0V248.3c0-15.3 7.2-29.6 19.5-38.6L436.6 8.4C444 2.9 453 0 462.2 0c11.4 0 22.4 4.5 30.5 12.6l54.8 54.8c8.1 8.1 12.6 19 12.6 30.5c0 9.2-2.9 18.2-8.4 25.6L350.4 396.5c-9 12.3-23.4 19.5-38.6 19.5H240l-25.4 25.4c-12.5 12.5-32.8 12.5-45.3 0l-50.7-50.7c-12.5-12.5-12.5-32.8 0-45.3L144 320zM23 466.3l63-63 70.6 70.6-31 31c-4.5 4.5-10.6 7-17 7H40c-13.3 0-24-10.7-24-24v-4.7c0-6.4 2.5-12.5 7-17z"/>
</svg>
@@ -834,6 +840,7 @@ export const penIcon = (pen: PenStyle) => {
strokeWidth="2"
fill={pen.strokeColor??"var(--icon-fill-color)"}
stroke={pen.strokeColor??"var(--icon-fill-color)"}
style={pen.strokeColor ? { filter: "var(--theme-filter)" } : {}}
>
<path d="m-.58 95.628.22-.89q.22-.89.49-2.44.26-1.54.77-3.35t1.31-3.43q.79-1.61.2-.26-.6 1.34-.03-.14.58-1.49 1.54-2.97.96-1.49 2.54-3.18 1.59-1.68 3.46-2.96 1.86-1.27.81-.54-1.04.73.6-.46 1.64-1.19 2.8-1.81 1.16-.63.16-.08-.99.54 1.21-.5 2.2-1.03 1.11-.58-1.1.45-.03-.07 1.06-.53 2.32-.82 1.26-.3 2.91-.52 1.64-.23 3.05-.18 1.4.05 2.5.12 1.09.07 2.25.24 1.16.17 2.3.49 1.15.32 2.11.78.96.47 2.21 1.01 1.25.55 2.37 1.04t2.34.89q1.22.4 2.5.65 1.29.25 2.44.33 1.16.08 2.35.17 1.18.08 2.26-.1 1.08-.19 2-1.1.92-.91 1.25-1.93.32-1.02.38-2.15t.57.21q.51 1.34-.03-.02-.55-1.37-.96-2.83-.41-1.45.5-.67.92.79-.03-.06-.95-.85-1.52-1.8-.57-.94-1.5-1.52-.93-.57-1.94-1.22-1.01-.65-1.97-1.88-.96-1.22-1.44-2.54-.49-1.32-.65-2.57-.17-1.24-.11-2.35.06-1.11.31-2.91.24-1.79.76-2.77.51-.97 1.29-1.8.77-.84 1.64-1.55.88-.72 1.9-1.33 1.03-.61 2.43-1.15 1.41-.55 2.69-.92 1.29-.37 2.81-.68 1.53-.31 2.83-.58 1.31-.27 2.78-.47 1.47-.2 2.58-.49 1.12-.28 2.19-.58 1.08-.31 2.13-.73 1.05-.42 2.44-1.32 1.39-.9 2.68-1.85 1.3-.95 2.25-1.87.95-.91 2.06-2 1.11-1.09 1.92-1.93.82-.84 1.54-1.82.71-.98 1.4-1.88.69-.9 1.38-1.96.69-1.07 1.25-2.04.55-.97 1.21-1.94.65-.96 1.35-1.79.69-.83 1.46-1.74.77-.9 1.66-1.73.89-.84 2.72-2.31 1.84-1.48 1.84-1.47v.01l-1.1 1.03q-1.1 1.02-1.77 1.92-.68.9-1.39 1.85-.71.96-1.41 1.91-.7.96-1.19 1.92-.48.95-.95 1.92-.46.96-.9 1.95-.43.99-1.02 2.16-.59 1.17-1.17 2.15-.58.97-1.23 2.13t-1.29 2.02q-.64.86-1.3 1.73-.66.88-1.42 1.78-.75.9-1.72 2.03-.97 1.14-1.81 1.89-.85.75-1.98 1.71-1.14.96-2.05 1.61-.91.64-2.42 1.38-1.51.73-2.71 1.21t-2.49.92q-1.3.44-2.35.69-1.06.24-2.1.59-1.03.34-2.06.74-1.03.4-2.29.94-1.26.54-2.27 1.11-1.01.58-1.57 1.57-.56.99-.81 2.06-.25 1.08.56 2.02.8.94-.21-.02-1.02-.96-.01-.48 1 .49 1.87 1.25.87.77 0 0-.88-.77.46-.01 1.34.75 2.6 1.68 1.26.94 2.08 2.03.81 1.09.01.27-.8-.82.3.26 1.11 1.08 1.71 2.1.61 1.02 1.21 2.25.6 1.24.92 2.36.32 1.12-.16.13-.49-.98.02.36.51 1.35.71 2.69.2 1.34.24 2.46.03 1.12-.09 2.42-.13 1.29-.72 3.21-.6 1.92-1.4 3.49-.81 1.58-1.77 2.83-.96 1.24-2.88 2.72-1.92 1.48-2.95 1.85-1.04.36-2.47.76-1.44.41-3.33.72-1.89.32-3.37.41-1.48.09-2.63.15-1.15.05-2.74-.06-1.59-.1-2.8-.29-1.2-.19-3.2-.63-1.99-.45-3.63-.92-1.63-.48-3.28-.79-1.65-.31-2.76-.2-1.11.1-2.21.42-1.11.32.39-.29 1.49-.6-.12.21-1.61.8-.39.19 1.21-.61.29.13-.92.74-1.83 1.34-.92.61.15-.19t.3-.05q-.77.75-1.58 1.57-.81.82.01-.18.82-1 .24.23t-.72 2.72q-.15 1.48-.08 2.4.07.91-.19 2.16-.26 1.26-.81 2.41-.55 1.16-1.36 2.15t-1.84 1.75q-1.03.77-2.21 1.27t-2.44.7q-1.27.2-2.53.1-1.28-.11-2.49-.52-1.22-.41-2.3-1.1-1.08-.68-1.96-1.61-.89-.92-1.52-2.04-.64-1.11-.99-2.34-.36-1.23-.41-2.51l-.04-1.27Z"/>
</svg>
@@ -846,6 +853,7 @@ export const penIcon = (pen: PenStyle) => {
strokeWidth="2"
fill={pen.strokeColor??"var(--icon-fill-color)"}
stroke={pen.strokeColor??"var(--icon-fill-color)"}
style={pen.strokeColor ? { filter: "var(--theme-filter)" } : {}}
>
<path d="m10 103.405.13-1.22q.14-1.22 1.3-3.16 1.15-1.94 2.74-3.46 1.59-1.53 3.35-2.72 1.77-1.2 4-1.95 2.23-.76 4.45-1t4.86-.4q2.64-.15 5.14-.34 2.51-.19 4.85-.94 2.35-.75 4.55-1.71 2.21-.97 4.16-2.26 1.95-1.3 4.03-2.97 2.07-1.67 3.85-3.05 1.78-1.37 3.72-2.48 1.94-1.11 3.3-2.99 1.36-1.89 2.58-3.74 1.22-1.85-.63-3.42-1.85-1.57-3.82-2.86-1.97-1.3-4.11-2.08-2.15-.78-4.21-1.6-2.06-.81-4.02-1.96-1.96-1.14-3.71-2.48-1.74-1.33-3.37-2.77-1.63-1.43-3.23-3.62-1.6-2.18-2.23-4.64-.62-2.46-.36-4.96.27-2.49 1.19-4.46.91-1.97 2.42-3.7 1.5-1.73 3.5-3.15t4.11-2.28q2.1-.86 4.33-1.44 2.24-.58 4.92-.84 2.68-.26 4.83-.19t4.69.35q2.53.28 4.75.66 2.23.38 4.48.2 2.26-.19 4.43-1.3 2.17-1.12 4.2-2.36 2.04-1.24 3.93-2.43 1.9-1.19 3.84-2.14 1.95-.95 4.04-1.78 2.09-.83 4.56-2.28 2.46-1.46 2.46-1.45h.01q.01 0-1.38 1.3-1.38 1.29-3.08 2.59-1.7 1.3-3.5 2.5t-3.42 2.65q-1.62 1.45-3.18 3-1.57 1.56-3.37 3.13-1.8 1.57-3.6 2.91-1.81 1.33-3.92 2.12t-4.24.92q-2.13.14-4.31.26-2.18.12-4.5.39t-4.56.88q-2.25.61-4.24 1.6-1.99 1-3.83 2.29-1.83 1.29.18 2.44 2.01 1.15 4.2 1.92 2.2.78 4.34 1 2.15.22 4.4.69 2.25.46 4.34 1.16 2.08.71 4.33 1.91 2.25 1.21 4.11 2.73 1.87 1.52 3.68 4.03 1.82 2.5 2.74 5 .93 2.5 1.18 5.03.26 2.53-.04 4.81t-1.4 4.85q-1.09 2.58-2.4 4.26-1.3 1.68-3.1 3.44t-4.02 3.62q-2.23 1.85-4.32 3.07-2.08 1.23-4.34 1.99-2.25.76-4.46 1.96t-4.37 2.14q-2.15.93-4.22 1.81t-4.36 1.35q-2.3.46-4.52.82-2.22.35-4.76.38-2.54.04-4.87-.28t-4.67-.67q-2.34-.35-4.72-.54-2.39-.19-4.64.37-2.25.56-4.16 1.66-1.91 1.11-3.52 2.71-1.61 1.6-2.55 2.39l-.94.78Z"/>
</svg>
@@ -860,6 +868,7 @@ export const penIcon = (pen: PenStyle) => {
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
style={pen.strokeColor ? { filter: "var(--theme-filter)" } : {}}
>
<path d="M453.3 19.3l39.4 39.4c25 25 25 65.5 0 90.5l-52.1 52.1 0 0-1-1 0 0-16-16-96-96-17-17 52.1-52.1c25-25 65.5-25 90.5 0zM241 114.9c-9.4-9.4-24.6-9.4-33.9 0L105 217c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9L173.1 81c28.1-28.1 73.7-28.1 101.8 0L288 94.1l17 17 96 96 16 16 1 1-17 17L229.5 412.5c-48 48-109.2 80.8-175.8 94.1l-25 5c-7.9 1.6-16-.9-21.7-6.6s-8.1-13.8-6.6-21.7l5-25c13.3-66.6 46.1-127.8 94.1-175.8L254.1 128 241 114.9z"/>
</svg>
@@ -868,6 +877,9 @@ export const penIcon = (pen: PenStyle) => {
}
export const stringToSVG = (svg: string) => {
if(svg.match('class="skip"')) {
return <div dangerouslySetInnerHTML={{__html: svg}}></div>
}
svg = svg
.replace(/stroke\s*=\s*['"][^"']*['"]/g,"")
.replace(/[^-]width\s*=\s*['"][^"']*['"]/g,"")

View File

@@ -2,7 +2,7 @@ import { AppState, ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/e
import clsx from "clsx";
import { TFile } from "obsidian";
import * as React from "react";
import { DEVICE, VIEW_TYPE_EXCALIDRAW } from "src/constants/constants";
import { DEVICE } from "src/constants/constants";
import { PenSettingsModal } from "src/dialogs/PenSettingsModal";
import ExcalidrawView from "src/ExcalidrawView";
import { PenStyle } from "src/PenTypes";
@@ -11,8 +11,7 @@ import ExcalidrawPlugin from "../main";
import { ICONS, penIcon, stringToSVG } from "./ActionIcons";
import { UniversalInsertFileModal } from "src/dialogs/UniversalInsertFileModal";
import { t } from "src/lang/helpers";
declare const PLUGIN_VERSION:string;
import { getExcalidrawViews } from "src/utils/ObsidianUtils";
export function setPen (pen: PenStyle, api: any) {
const st = api.getAppState();
@@ -134,10 +133,8 @@ export class ObsidianMenu {
this.view.excalidrawAPI?.setToast({message:`Pin removed: ${name}`, duration: 3000, closable: true});
}
await this.plugin.saveSettings();
this.plugin.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> {
if (v.view instanceof ExcalidrawView) v.view.updatePinnedScripts()
})
})()
getExcalidrawViews(this.plugin.app).forEach(excalidrawView=>excalidrawView.updatePinnedScripts());
})()
},
1500
)

View File

@@ -3,7 +3,7 @@ import { Notice, TFile } from "obsidian";
import * as React from "react";
import { ActionButton } from "./ActionButton";
import { ICONS, saveIcon, stringToSVG } from "./ActionIcons";
import { DEVICE, SCRIPT_INSTALL_FOLDER, VIEW_TYPE_EXCALIDRAW } from "../constants/constants";
import { DEVICE, SCRIPT_INSTALL_FOLDER } from "../constants/constants";
import { insertLaTeXToView, search } from "../ExcalidrawAutomate";
import ExcalidrawView, { TextMode } from "../ExcalidrawView";
import { t } from "../lang/helpers";
@@ -17,10 +17,10 @@ import { ExportDialog } from "src/dialogs/ExportDialog";
import { openExternalLink } from "src/utils/ExcalidrawViewUtils";
import { UniversalInsertFileModal } from "src/dialogs/UniversalInsertFileModal";
import { DEBUGGING, debug } from "src/utils/DebugHelper";
import { REM_VALUE } from "src/utils/StylesManager";
import { getExcalidrawViews } from "src/utils/ObsidianUtils";
declare const PLUGIN_VERSION:string;
const dark = '<svg style="stroke:#ced4da;#212529;color:#ced4da;fill:#ced4da" ';
const light = '<svg style="stroke:#212529;color:#212529;fill:#212529" ';
type PanelProps = {
visible: boolean;
@@ -42,7 +42,7 @@ export type PanelState = {
scriptIconMap: ScriptIconMap | null;
};
const TOOLS_PANEL_WIDTH = 228;
const TOOLS_PANEL_WIDTH = () => REM_VALUE * 14.4;
export class ToolsPanel extends React.Component<PanelProps, PanelState> {
pos1: number = 0;
@@ -155,11 +155,11 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
return {
left:
(this.containerRef.current.clientWidth -
TOOLS_PANEL_WIDTH -
(isMobileOrZen ? 0 : TOOLS_PANEL_WIDTH + 4)) /
TOOLS_PANEL_WIDTH() -
(isMobileOrZen ? 0 : TOOLS_PANEL_WIDTH() + 4)) /
2 +
this.containerRef.current.parentElement.offsetLeft +
(isMobileOrZen ? 0 : TOOLS_PANEL_WIDTH + 4),
(isMobileOrZen ? 0 : TOOLS_PANEL_WIDTH() + 4),
top: 64 + this.containerRef.current.parentElement.offsetTop,
};
});
@@ -262,7 +262,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
shiftKey: e.shiftKey,
altKey: e.altKey,
});
this.view.handleLinkClick(event);
this.view.handleLinkClick(event, true);
}
actionOpenLinkProperties() {
@@ -366,11 +366,11 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
async actionRunScript(key: string) {
const view = this.view;
const plugin = view.plugin;
const f = app.vault.getAbstractFileByPath(key);
const f = plugin.app.vault.getAbstractFileByPath(key);
if (f && f instanceof TFile) {
plugin.scriptEngine.executeScript(
view,
await app.vault.read(f),
await plugin.app.vault.read(f),
plugin.scriptEngine.getScriptName(f),
f
);
@@ -391,9 +391,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
api?.setToast({message:`Pinned: ${scriptName}`, duration: 3000, closable: true})
}
await plugin.saveSettings();
plugin.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> {
if (v.view instanceof ExcalidrawView) v.view.updatePinnedScripts()
})
getExcalidrawViews(plugin.app).forEach(excalidrawView=>excalidrawView.updatePinnedScripts());
}
private islandOnClick(event: React.MouseEvent<HTMLDivElement, MouseEvent>) {
@@ -453,7 +451,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
style={{
top: `${this.state.top}px`,
left: `${this.state.left}px`,
width: `${TOOLS_PANEL_WIDTH}px`,
width: `14.4rem`,
display:
this.state.visible && !this.state.excalidrawViewMode
? "block"
@@ -492,7 +490,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
maxHeight: "350px",
width: "initial",
//@ts-ignore
"--padding": 2,
"--padding": "0.125rem",
display: this.state.minimized ? "none" : "block",
}}
>

View File

@@ -10,12 +10,11 @@ import {
TextComponent,
TFile,
} from "obsidian";
import { GITHUB_RELEASES, VIEW_TYPE_EXCALIDRAW } from "./constants/constants";
import ExcalidrawView from "./ExcalidrawView";
import { GITHUB_RELEASES } from "./constants/constants";
import { t } from "./lang/helpers";
import type ExcalidrawPlugin from "./main";
import { PenStyle } from "./PenTypes";
import { DynamicStyle } from "./types/types";
import { DynamicStyle, GridSettings } from "./types/types";
import { PreviewImageType } from "./utils/UtilTypes";
import { setDynamicStyle } from "./utils/DynamicStyling";
import {
@@ -41,6 +40,8 @@ import { setDebugging } from "./utils/DebugHelper";
import { Rank } from "./menu/ActionIcons";
import { TAG_AUTOEXPORT, TAG_MDREADINGMODE, TAG_PDFEXPORT } from "src/constants/constSettingsTags";
import { HotkeyEditor } from "./dialogs/HotkeyEditor";
import { getExcalidrawViews } from "./utils/ObsidianUtils";
import de from "./lang/locale/de";
export interface ExcalidrawSettings {
folder: string;
@@ -49,6 +50,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
@@ -72,6 +77,7 @@ export interface ExcalidrawSettings {
previewMatchObsidianTheme: boolean;
width: string;
height: string;
overrideObsidianFontSize: boolean;
dynamicStyling: DynamicStyle;
isLeftHanded: boolean;
iframeMatchExcalidrawTheme: boolean;
@@ -81,6 +87,7 @@ export interface ExcalidrawSettings {
defaultMode: string;
defaultPenMode: "never" | "mobile" | "always";
penModeDoubleTapEraser: boolean;
penModeSingleFingerPanning: boolean;
penModeCrosshairVisible: boolean;
renderImageInMarkdownReadingMode: boolean,
renderImageInHoverPreviewForMDNotes: boolean,
@@ -179,6 +186,7 @@ export interface ExcalidrawSettings {
pdfNumRows: number;
pdfDirection: "down" | "right";
pdfImportScale: number;
gridSettings: GridSettings;
laserSettings: {
DECAY_TIME: number,
DECAY_LENGTH: number,
@@ -204,6 +212,7 @@ export interface ExcalidrawSettings {
areaZoomLimit: number;
longPressDesktop: number;
longPressMobile: number;
doubleClickLinkOpenViewMode: boolean;
isDebugMode: boolean;
rank: Rank;
modifierKeyOverrides: {modifiers: Modifier[], key: string}[];
@@ -219,13 +228,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: " ",
@@ -242,6 +255,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
previewMatchObsidianTheme: false,
width: "400",
height: "",
overrideObsidianFontSize: false,
dynamicStyling: "colorful",
isLeftHanded: false,
iframeMatchExcalidrawTheme: true,
@@ -251,6 +265,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
defaultMode: "normal",
defaultPenMode: "never",
penModeDoubleTapEraser: true,
penModeSingleFingerPanning: true,
penModeCrosshairVisible: true,
renderImageInMarkdownReadingMode: false,
renderImageInHoverPreviewForMDNotes: false,
@@ -267,9 +282,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,
@@ -355,6 +370,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 +486,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
areaZoomLimit: 1,
longPressDesktop: 500,
longPressMobile: 500,
doubleClickLinkOpenViewMode: true,
isDebugMode: false,
rank: "Bronze",
modifierKeyOverrides: [
@@ -498,6 +519,12 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
}
async hide() {
if(this.plugin.settings.overrideObsidianFontSize) {
document.documentElement.style.fontSize = "";
} else if(!document.documentElement.style.fontSize) {
document.documentElement.style.fontSize = getComputedStyle(document.body).getPropertyValue("--font-text-size");
}
this.plugin.settings.scriptFolderPath = normalizePath(
this.plugin.settings.scriptFolderPath,
);
@@ -509,31 +536,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 +741,6 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
}),
);
// ------------------------------------------------
// Saving
// ------------------------------------------------
@@ -1057,6 +1077,17 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
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"))
@@ -1141,6 +1172,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 +1284,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 +1298,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();
}),
);
@@ -1310,7 +1349,79 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
el.innerText = ` ${this.plugin.settings.zoomToFitMaxLevel.toString()}`;
});
// ------------------------------------------------
// 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)
let opacityValue: HTMLDivElement;
new Setting(detailsEl)
.setName(t("GRID_OPACITY_NAME"))
.setDesc(fragWithHTML(t("GRID_OPACITY_DESC")))
.addSlider((slider) =>
slider
.setLimits(0, 100, 1) // 0 to 100 in decimal
.setValue(this.plugin.settings.gridSettings.OPACITY)
.onChange(async (value) => {
opacityValue.innerText = ` ${value.toString()}`;
this.plugin.settings.gridSettings.OPACITY = value;
this.applySettingsUpdate();
updateGridColor();
}),
)
.settingEl.createDiv("", (el) => {
opacityValue = el;
el.style.minWidth = "3em";
el.style.textAlign = "right";
el.innerText = ` ${this.plugin.settings.gridSettings.OPACITY}`;
});
// ------------------------------------------------
// Laser Pointer
// ------------------------------------------------
detailsEl = displayDetailsEl.createEl("details");
detailsEl.createEl("summary", {
text: t("LASER_HEAD"),
@@ -1418,6 +1529,18 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
el.innerText = ` ${this.plugin.settings.longPressMobile.toString()}`;
});
new Setting(detailsEl)
.setName(t("DOUBLE_CLICK_LINK_OPEN_VIEW_MODE"))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.doubleClickLinkOpenViewMode)
.onChange(async (value) => {
this.plugin.settings.doubleClickLinkOpenViewMode = value;
this.applySettingsUpdate();
}),
);
new ModifierKeySettingsComponent(
detailsEl,
this.plugin.settings.modifierKeyConfig,
@@ -2263,7 +2386,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);
});
@@ -2388,8 +2511,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",
@@ -2416,7 +2551,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 +2565,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
// ------------------------------------------------

15
src/types/types.d.ts vendored
View File

@@ -1,3 +1,4 @@
import { TFile } from "obsidian";
import { ExcalidrawAutomate } from "../ExcalidrawAutomate";
import { ExcalidrawLib } from "../ExcalidrawLib";
@@ -13,6 +14,12 @@ export type ValueOf<T> = T[keyof T];
export type DynamicStyle = "none" | "gray" | "colorful";
export type GridSettings = {
DYNAMIC_COLOR: boolean; // Whether the grid color is dynamic
COLOR: string; // The grid color (in hex format)
OPACITY: number; // The grid opacity (hex value between "00" and "FF")
};
export type DeviceType = {
isDesktop: boolean,
isPhone: boolean,
@@ -25,6 +32,14 @@ export type DeviceType = {
isAndroid: boolean,
};
export type Point = [number, number];
export type LinkSuggestion = {
file: TFile;
path: string;
alias?: string;
}
declare global {
interface Window {
ExcalidrawAutomate: ExcalidrawAutomate;

1403
src/utils/CJKLoader.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@ import { getEA } from "src";
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
import { getCropFileNameAndFolder, getListOfTemplateFiles, splitFolderAndFilename } from "./FileUtils";
import { Notice, TFile } from "obsidian";
import { Radians } from "@zsviczian/excalidraw/types/math";
export const CROPPED_PREFIX = "cropped_";
export const ANNOTATED_PREFIX = "annotated_";
@@ -30,7 +31,7 @@ export const carveOutImage = async (sourceEA: ExcalidrawAutomate, viewImageEl: E
const scale = newImage.scale;
const angle = newImage.angle;
newImage.scale = [1,1];
newImage.angle = 0;
newImage.angle = 0 as Radians;
const ef = sourceEA.targetView.excalidrawData.getFile(viewImageEl.fileId);
let imageLink = "";

View File

@@ -139,7 +139,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');

View File

@@ -13,6 +13,7 @@ export const setDynamicStyle = (
view: ExcalidrawView, //the excalidraw view
color: string,
dynamicStyle: DynamicStyle,
textBackgroundColor?: string,
) => {
if(dynamicStyle === "none") {
view.excalidrawContainer?.removeAttribute("style");
@@ -82,8 +83,10 @@ export const setDynamicStyle = (
[`--color-surface-high`]: str(gray1().lighterBy(step)),
[`--color-on-primary-container`]: str(!isDark?accent().darkerBy(15):accent().lighterBy(15)),
[`--color-surface-primary-container`]: str(isDark?accent().darkerBy(step):accent().lighterBy(step)),
//[`--color-primary-darker`]: str(accent().darkerBy(step)),
//[`--color-primary-darkest`]: str(accent().darkerBy(step)),
[`--bold-color`]: str(!isDark?accent().darkerBy(15):accent().lighterBy(15)),
[`--color-primary-darker`]: str(accent().darkerBy(step)),
[`--color-primary-darkest`]: str(accent().darkerBy(2*step)),
['--button-bg-color']: str(gray1()),
[`--button-gray-1`]: str(gray1()),
[`--button-gray-2`]: str(gray2()),
[`--input-border-color`]: str(gray1()),
@@ -96,11 +99,11 @@ export const setDynamicStyle = (
[`--overlay-bg-color`]: gray2().alphaTo(0.6).stringHEX(),
[`--popup-bg-color`]: str(gray1()),
[`--color-on-surface`]: str(text),
[`--default-border-color`]: str(gray1()),
//[`--color-gray-100`]: str(text),
[`--color-gray-40`]: str(text), //frame
[`--color-gray-50`]: str(text), //frame
[`--color-surface-highlight`]: str(gray1()),
//[`--color-gray-30`]: str(gray1),
[`--color-gray-20`]: str(gray1()),
[`--sidebar-border-color`]: str(gray1()),
[`--color-primary-light`]: str(accent().lighterBy(step)),
[`--button-hover-bg`]: str(gray1()),
@@ -114,9 +117,15 @@ export const setDynamicStyle = (
[`--h3-color`]: str(text),
[`--h4-color`]: str(text),
[`color`]: str(text),
['--excalidraw-caret-color']: str(isLightTheme ? text : cmBG()),
['--excalidraw-caret-color']: textBackgroundColor
? str(isLightTheme ? invertColor(textBackgroundColor) : ea.getCM(textBackgroundColor))
: str(isLightTheme ? text : cmBG()),
[`--select-highlight-color`]: str(gray1()),
[`--color-gray-80`]: str(isDark?text.darkerBy(40):text.lighterBy(40)), //frame
[`--color-gray-90`]: str(isDark?text.darkerBy(5):text.lighterBy(5)), //search background
[`--color-gray-80`]: str(isDark?text.darkerBy(10):text.lighterBy(10)), //frame
[`--color-gray-70`]: str(isDark?text.darkerBy(10):text.lighterBy(10)), //frame
[`--default-bg-color`]: str(isDark?text.darkerBy(20):text.lighterBy(20)), //search background,
[`--color-gray-50`]: str(text), //frame
};
const styleString = Object.keys(styleObject)

View File

@@ -23,6 +23,9 @@ export function updateElementIdsInScene(
boundEl.boundElements?.filter(x=>x.id === elementToChange.id).forEach( x => {
(x.id as Mutable<string>) = newID;
});
if(boundEl.type === "text") {
boundEl.containerId = newID;
}
if(boundEl.type === "arrow") {
const arrow = boundEl as Mutable<ExcalidrawArrowElement>;
if(arrow.startBinding?.elementId === elementToChange.id) {

View File

@@ -1,6 +1,6 @@
import { MAX_IMAGE_SIZE, IMAGE_TYPES, ANIMATED_IMAGE_TYPES, MD_EX_SECTIONS } from "src/constants/constants";
import { App, Notice, TFile, WorkspaceLeaf } from "obsidian";
import { App, Modal, Notice, TFile, WorkspaceLeaf } from "obsidian";
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
import { REGEX_LINK, REG_LINKINDEX_HYPERLINK, getExcalidrawMarkdownHeaderSection, REGEX_TAGS } from "src/ExcalidrawData";
import ExcalidrawView from "src/ExcalidrawView";
@@ -19,6 +19,7 @@ export async function insertImageToView(
file: TFile | string,
scale?: boolean,
shouldInsertToView: boolean = true,
repositionToCursor: boolean = false,
):Promise<string> {
if(shouldInsertToView) {ea.clear();}
ea.style.strokeColor = "transparent";
@@ -31,7 +32,7 @@ export async function insertImageToView(
file,
scale,
);
if(shouldInsertToView) {await ea.addElementsToView(false, true, true);}
if(shouldInsertToView) {await ea.addElementsToView(repositionToCursor, true, true);}
return id;
}
@@ -81,20 +82,18 @@ export function openTagSearch(link: string, app: App, view?: ExcalidrawView) {
return;
}
const search = app.workspace.getLeavesOfType("search");
if (search.length === 0) {
return;
const query = `tag:${tags[0].value[1]}`;
const searchPlugin = app.internalPlugins.getPluginById("global-search");
if (searchPlugin) {
const searchInstance = searchPlugin.instance;
if (searchInstance) {
searchInstance.openGlobalSearch(query);
}
}
//@ts-ignore
search[0].view.setQuery(`tag:${tags[0].value[1]}`);
app.workspace.revealLeaf(search[0]);
if (view && view.isFullscreen()) {
view.exitFullscreen();
}
return;
}
function getLinkFromMarkdownLink(link: string): string {
@@ -123,14 +122,21 @@ export function openExternalLink (link:string, app: App, element?: ExcalidrawEle
* @param link
* @param app
* @param returnWikiLink
* @param openLink: if set to false, the link will not be opened just true will be returned for an obsidian link.
* @returns
* false if the link is not an obsidian link,
* true if the link is an obsidian link and it was opened (i.e. it is a link to another Vault or not a file link e.g. plugin link), or
* the link to the file path. By default as a wiki link, or as a file path if returnWikiLink is false.
*/
export function parseObsidianLink(link: string, app: App, returnWikiLink: boolean = true): boolean | string {
export function parseObsidianLink(
link: string,
app: App,
returnWikiLink: boolean = true,
openLink: boolean = true,
): boolean | string {
if(!link) return false;
link = getLinkFromMarkdownLink(link);
if (!link.startsWith("obsidian://")) {
if (!link?.startsWith("obsidian://")) {
return false;
}
const url = new URL(link);
@@ -153,7 +159,9 @@ export function parseObsidianLink(link: string, app: App, returnWikiLink: boolea
}
}
window.open(link, "_blank");
if(openLink) {
window.open(link, "_blank");
}
return true;
}
@@ -378,6 +386,13 @@ export function isTextImageTransclusion (
return false;
}
if(file && file instanceof TFile) {
if(
view.plugin.isExcalidrawFile(file) &&
link?.split("#")[1] &&
!isImagePartRef(getEmbeddedFilenameParts(link)))
{
return false;
}
if (file.extension !== "md" || view.plugin.isExcalidrawFile(file)) {
callback(link, file);
return true;
@@ -387,4 +402,20 @@ export function isTextImageTransclusion (
}
}
return false;
}
export function displayFontMessage(app: App) {
const modal = new Modal(app);
modal.onOpen = () => {
const contentEl = modal.contentEl;
contentEl.createEl("h2", { text: t("FONT_INFO_TITLE") });
const releaseNotesHTML = t("FONT_INFO_DETAILED");
const div = contentEl.createDiv({ cls: "release-notes" });
div.innerHTML = releaseNotesHTML;
}
modal.open();
}

View File

@@ -2,6 +2,7 @@ import { ExcalidrawElement, ExcalidrawImageElement, ExcalidrawTextElement } from
import { REGEX_LINK, REG_LINKINDEX_HYPERLINK } from "src/ExcalidrawData";
import ExcalidrawView, { TextMode } from "src/ExcalidrawView";
import { rotatedDimensions } from "./Utils";
import { getBoundTextElementId } from "src/ExcalidrawAutomate";
export const getElementsAtPointer = (
pointer: any,
@@ -93,13 +94,24 @@ const api = view.excalidrawAPI;
if (!api) {
return;
}
const elements = (
let elements = (
getElementsAtPointer(
pointer,
api.getSceneElements(),
) as ExcalidrawImageElement[]
) as ExcalidrawElement[]
).filter((el) => el.link);
//as a fallback let's check if any of the elements at pointer are containers with a text element that has a link.
if (elements.length === 0) {
const textElIDs = (
getElementsAtPointer(
pointer,
api.getSceneElements(),
) as ExcalidrawImageElement[]
).map((el) => getBoundTextElementId(el));
elements = view.getViewElements().filter((el) => el.type==="text" && el.link && textElIDs.includes(el.id));
}
if (elements.length === 0) {
return { id: null, text: null };
}

View File

@@ -9,8 +9,9 @@ import ExcalidrawPlugin from "../main";
import { checkAndCreateFolder, splitFolderAndFilename } from "./FileUtils";
import { linkClickModifierType, ModifierKeys } from "./ModifierkeyHelper";
import { REG_BLOCK_REF_CLEAN, REG_SECTION_REF_CLEAN, VIEW_TYPE_EXCALIDRAW } from "src/constants/constants";
import yaml, { Mark } from "js-yaml";
import yaml from "js-yaml";
import { debug, DEBUGGING } from "./DebugHelper";
import ExcalidrawView from "src/ExcalidrawView";
export const getParentOfClass = (element: Element, cssClass: string):HTMLElement | null => {
let parent = element.parentElement;
@@ -24,6 +25,11 @@ export const getParentOfClass = (element: Element, cssClass: string):HTMLElement
return parent?.classList?.contains(cssClass) ? parent : null;
};
export function getExcalidrawViews(app: App): ExcalidrawView[] {
const leaves = app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).filter(l=>l.view instanceof ExcalidrawView);
return leaves.map(l=>l.view as ExcalidrawView);
}
export const getLeaf = (
plugin: ExcalidrawPlugin,
origo: WorkspaceLeaf,

37
src/utils/PDFUtils.ts Normal file
View File

@@ -0,0 +1,37 @@
//for future use, not used currently
import { ImageCrop } from "@zsviczian/excalidraw/types/excalidraw/element/types";
export function getPDFCropRect (props: {
scale: number,
link: string,
naturalHeight: number,
naturalWidth: number,
}) : ImageCrop | null {
const rectVal = props.link.match(/&rect=(\d*),(\d*),(\d*),(\d*)/);
if (!rectVal || rectVal.length !== 5) {
return null;
}
const R0 = parseInt(rectVal[1]);
const R1 = parseInt(rectVal[2]);
const R2 = parseInt(rectVal[3]);
const R3 = parseInt(rectVal[4]);
return {
x: R0 * props.scale,
y: (props.naturalHeight/props.scale - R3) * props.scale,
width: (R2 - R0) * props.scale,
height: (R3 - R1) * props.scale,
naturalWidth: props.naturalWidth,
naturalHeight: props.naturalHeight,
}
}
export function getPDFRect(elCrop: ImageCrop, scale: number): string {
const R0 = elCrop.x / scale;
const R2 = elCrop.width / scale + R0;
const R3 = (elCrop.naturalHeight - elCrop.y) / scale;
const R1 = R3 - elCrop.height / scale;
return `&rect=${Math.round(R0)},${Math.round(R1)},${Math.round(R2)},${Math.round(R3)}`;
}

View File

@@ -3,6 +3,8 @@ import ExcalidrawPlugin from "src/main";
import { getAllWindowDocuments } from "./ObsidianUtils";
import { DEBUGGING, debug } from "./DebugHelper";
export let REM_VALUE = 16;
const STYLE_VARIABLES = [
"--background-modifier-cover",
"--background-primary-alt",
@@ -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

@@ -18,7 +18,7 @@ import {
getContainerElement,
} from "../constants/constants";
import ExcalidrawPlugin from "../main";
import { ExcalidrawElement, ExcalidrawTextElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
import { ExcalidrawElement, ExcalidrawTextElement, ImageCrop } from "@zsviczian/excalidraw/types/excalidraw/element/types";
import { ExportSettings } from "../ExcalidrawView";
import { getDataURLFromURL, getIMGFilename, getMimeType, getURLImageExtension } from "./FileUtils";
import { generateEmbeddableLink } from "./CustomEmbeddableUtils";
@@ -29,6 +29,9 @@ import { updateElementLinksToObsidianLinks } from "src/ExcalidrawAutomate";
import { CropImage } from "./CropImage";
import opentype from 'opentype.js';
import { runCompressionWorker } from "src/workers/compression-worker";
import Pool from "es6-promise-pool";
import { FileData } from "src/EmbeddedFileLoader";
import { t } from "src/lang/helpers";
declare const PLUGIN_VERSION:string;
declare var LZString: any;
@@ -75,7 +78,7 @@ export async function checkExcalidrawVersion() {
if (isVersionNewerThanOther(latestVersion,PLUGIN_VERSION)) {
new Notice(
`A newer version of Excalidraw is available in Community Plugins.\n\nYou are using ${PLUGIN_VERSION}.\nThe latest is ${latestVersion}`,
t("UPDATE_AVAILABLE") + ` ${latestVersion}`,
);
}
} catch (e) {
@@ -218,15 +221,6 @@ export async function getFontDataURL (
const split = dataURL.split(";base64,", 2);
dataURL = `${split[0]};charset=utf-8;base64,${split[1]}`;
fontDef = ` @font-face {font-family: "${fontName}";src: url("${dataURL}") format("${format}")}`;
/* const mimeType = f.extension.startsWith("woff")
? "application/font-woff"
: "font/truetype";
fontName = name ?? f.basename;
dataURL = await getDataURL(ab, mimeType);
fontDef = ` @font-face {font-family: "${fontName}";src: url("${dataURL}")}`;
//format("${f.extension === "ttf" ? "truetype" : f.extension}");}`;
const split = fontDef.split(";base64,", 2);
fontDef = `${split[0]};charset=utf-8;base64,${split[1]}`;*/
}
return { fontDef, fontName, dataURL };
};
@@ -373,7 +367,7 @@ export async function getPNG (
}),
});
} catch (error) {
new Notice("Error exporting PNG - PNG too large, try a smaller resolution");
new Notice(t("ERROR_PNG_TOO_LARGE"));
errorlog({ where: "Utils.getPNG", error });
return null;
}
@@ -427,14 +421,14 @@ export function addAppendUpdateCustomData (el: Mutable<ExcalidrawElement>, newDa
export function scaleLoadedImage (
scene: any,
files: any
files: FileData[],
): { dirty: boolean; scene: any } {
let dirty = false;
if (!files || !scene) {
return { dirty, scene };
}
for (const f of files.filter((f:any)=>{
for (const img of files.filter((f:any)=>{
if(!Boolean(EXCALIDRAW_PLUGIN)) return true; //this should never happen
const ef = EXCALIDRAW_PLUGIN.filesMaster.get(f.id);
if(!ef) return true; //mermaid SVG or equation
@@ -442,34 +436,81 @@ export function scaleLoadedImage (
if(!file || (file instanceof TFolder)) return false;
return (file as TFile).extension==="md" || EXCALIDRAW_PLUGIN.isExcalidrawFile(file as TFile)
})) {
const [w_image, h_image] = [f.size.width, f.size.height];
const imageAspectRatio = f.size.width / f.size.height;
const [imgWidth, imgHeight] = [img.size.width, img.size.height];
const imgAspectRatio = imgWidth / imgHeight;
scene.elements
.filter((e: any) => e.type === "image" && e.fileId === f.id)
.filter((e: any) => e.type === "image" && e.fileId === img.id)
.forEach((el: any) => {
const [w_old, h_old] = [el.width, el.height];
if(el.customData?.isAnchored && f.shouldScale || !el.customData?.isAnchored && !f.shouldScale) {
addAppendUpdateCustomData(el, f.shouldScale ? {isAnchored: false} : {isAnchored: true});
const [elWidth, elHeight] = [el.width, el.height];
const maintainArea = img.shouldScale; //true if image should maintain its area, false if image should display at 100% its size
const elCrop: ImageCrop = el.crop;
const isCropped = Boolean(elCrop);
if(el.customData?.isAnchored && img.shouldScale || !el.customData?.isAnchored && !img.shouldScale) {
//customData.isAnchored is used by the Excalidraw component to disable resizing of anchored images
//customData.isAnchored has no direct role in the calculation in the scaleLoadedImage function
addAppendUpdateCustomData(el, img.shouldScale ? {isAnchored: false} : {isAnchored: true});
dirty = true;
}
if(f.shouldScale) {
const elementAspectRatio = w_old / h_old;
if (imageAspectRatio !== elementAspectRatio) {
if(isCropped) {
if(elCrop.naturalWidth !== imgWidth || elCrop.naturalHeight !== imgHeight) {
dirty = true;
const h_new = Math.sqrt((w_old * h_old * h_image) / w_image);
const w_new = Math.sqrt((w_old * h_old * w_image) / h_image);
el.height = h_new;
el.width = w_new;
el.y += (h_old - h_new) / 2;
el.x += (w_old - w_new) / 2;
//the current crop area may be maintained, need to calculate the new crop.x, crop.y offsets
el.crop.y += (imgHeight - elCrop.naturalHeight)/2;
if(imgWidth < elCrop.width) {
const scaleX = el.width / elCrop.width;
el.crop.x = 0;
el.crop.width = imgWidth;
el.width = imgWidth * scaleX;
} else {
const ratioX = elCrop.x / (elCrop.naturalWidth - elCrop.x - elCrop.width);
const gapX = imgWidth - elCrop.width;
el.crop.x = ratioX * gapX / (1 + ratioX);
if(el.crop.x + elCrop.width > imgWidth) {
el.crop.x = (imgWidth - elCrop.width) / 2;
}
}
if(imgHeight < elCrop.height) {
const scaleY = el.height / elCrop.height;
el.crop.y = 0;
el.crop.height = imgHeight;
el.height = imgHeight * scaleY;
} else {
const ratioY = elCrop.y / (elCrop.naturalHeight - elCrop.y - elCrop.height);
const gapY = imgHeight - elCrop.height;
el.crop.y = ratioY * gapY / (1 + ratioY);
if(el.crop.y + elCrop.height > imgHeight) {
el.crop.y = (imgHeight - elCrop.height)/2;
}
}
el.crop.naturalWidth = imgWidth;
el.crop.naturalHeight = imgHeight;
const noCrop = el.crop.width === imgWidth && el.crop.height === imgHeight;
if(noCrop) {
el.crop = null;
}
}
} else {
if(w_old !== w_image || h_old !== h_image) {
} else if(maintainArea) {
const elAspectRatio = elWidth / elHeight;
if (imgAspectRatio !== elAspectRatio) {
dirty = true;
el.height = h_image;
el.width = w_image;
el.y += (h_old - h_image) / 2;
el.x += (w_old - w_image) / 2;
const elNewHeight = Math.sqrt((elWidth * elHeight * imgHeight) / imgWidth);
const elNewWidth = Math.sqrt((elWidth * elHeight * imgWidth) / imgHeight);
el.height = elNewHeight;
el.width = elNewWidth;
el.y += (elHeight - elNewHeight) / 2;
el.x += (elWidth - elNewWidth) / 2;
}
} else { //100% size
if(elWidth !== imgWidth || elHeight !== imgHeight) {
dirty = true;
el.height = imgHeight;
el.width = imgWidth;
el.y += (elHeight - imgHeight) / 2;
el.x += (elWidth - imgWidth) / 2;
}
}
});
@@ -724,6 +765,8 @@ export function getPNGScale (plugin: ExcalidrawPlugin, file: TFile): number {
};
export function isVersionNewerThanOther (version: string, otherVersion: string): boolean {
if(!version || !otherVersion) return true;
const v = version.match(/(\d*)\.(\d*)\.(\d*)/);
const o = otherVersion.match(/(\d*)\.(\d*)\.(\d*)/);
@@ -968,4 +1011,63 @@ export function cropCanvas(
0, 0, output.width, output.height
);
return dstCanvas;
}
// Promise.try, adapted from https://github.com/sindresorhus/p-try
export async function promiseTry <TValue, TArgs extends unknown[]>(
fn: (...args: TArgs) => PromiseLike<TValue> | TValue,
...args: TArgs
): Promise<TValue> {
return new Promise((resolve) => {
resolve(fn(...args));
});
};
// extending the missing types
// relying on the [Index, T] to keep a correct order
type TPromisePool<T, Index = number> = Pool<[Index, T][]> & {
addEventListener: (
type: "fulfilled",
listener: (event: { data: { result: [Index, T] } }) => void,
) => (event: { data: { result: [Index, T] } }) => void;
removeEventListener: (
type: "fulfilled",
listener: (event: { data: { result: [Index, T] } }) => void,
) => void;
};
export class PromisePool<T> {
private readonly pool: TPromisePool<T>;
private readonly entries: Record<number, T> = {};
constructor(
source: IterableIterator<Promise<void | readonly [number, T]>>,
concurrency: number,
) {
this.pool = new Pool(
source as unknown as () => void | PromiseLike<[number, T][]>,
concurrency,
) as TPromisePool<T>;
}
public all() {
const listener = (event: { data: { result: void | [number, T] } }) => {
if (event.data.result) {
// by default pool does not return the results, so we are gathering them manually
// with the correct call order (represented by the index in the tuple)
const [index, value] = event.data.result;
this.entries[index] = value;
}
};
this.pool.addEventListener("fulfilled", listener);
return this.pool.start().then(() => {
setTimeout(() => {
this.pool.removeEventListener("fulfilled", listener);
});
return Object.values(this.entries);
});
}
}

18
src/utils/typechecks.ts Normal file
View File

@@ -0,0 +1,18 @@
/**
* Checks if a given target is an HTMLElement.
*
* This function is necessary because `instanceof HTMLElement` can fail
* in environments with multiple execution contexts (e.g., popout windows),
* where `HTMLElement` comes from different global objects.
* Instead, we use feature detection by checking for key properties
* common to all HTML elements (nodeType and tagName).
*
* @param target - The target to check.
* @returns True if the target is an HTMLElement, false otherwise.
*/
export function isHTMLElement (target: any): target is HTMLElement {
return target &&
typeof target === 'object' &&
target.nodeType === 1 && // nodeType 1 means it's an element
typeof target.tagName === 'string'; // tagName exists on HTML elements
}

View File

@@ -226,7 +226,7 @@ li[data-testid] {
}
.excalidraw .ToolIcon__icon img{
height: 1em;
height: 1rem;
}
.excalidraw-scriptengine-install tbody>tr>td>div>img {
@@ -346,7 +346,7 @@ label.color-input-container > input {
padding: 0;
}
.excalidraw-settings input:not([type="color"]) {
.excalidraw-settings input[type="text"] {
min-width: 10em;
}
@@ -394,23 +394,28 @@ div.excalidraw-draginfo {
.modal-content.excalidraw-scriptengine-install .search-bar-wrapper {
position: sticky;
top: 1em;
margin-right: 1em;
top: 1rem;
margin-right: 1rem;
display: flex;
align-items: center;
gap: 5px;
flex-wrap: nowrap;
z-index: 10;
background: var(--background-secondary);
padding: 0.5em;
padding: 0.5rem;
border-bottom: 1px solid var(--background-modifier-border);
float: right;
max-width: 28em;
max-width: 28rem;
}
div.search-bar-wrapper input {
margin-right: -0.5rem;
}
.modal-content.excalidraw-scriptengine-install .hit-count {
margin-left: 0.5em;
white-space: nowrap;
font-size: smaller;
}
.modal-content.excalidraw-scriptengine-install .active-highlight {
@@ -629,4 +634,20 @@ textarea.excalidraw-wysiwyg, .excalidraw input {
.excalidraw .color-picker-content input[type="color"] {
filter: var(--theme-filter);
}
}
.ExcTextField__input input::placeholder {
color: var(--select-highlight-color);
}
.excalidraw textarea::placeholder {
color: var(--color-gray-50);
}
.excalidraw textarea.ttd-dialog-input {
caret-color: var(--excalidraw-caret-color);
}
.excalidraw .ToolIcon_type_button {
color: var(--text-primary-color);
}