Compare commits
220 Commits
2.0.19
...
2.5.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3cca2fedb0 | ||
|
|
a0682b8e3c | ||
|
|
da19ce1b84 | ||
|
|
2b1f504b5b | ||
|
|
d8f2776b40 | ||
|
|
2fd800e0a0 | ||
|
|
712402f7b1 | ||
|
|
b5c034ed92 | ||
|
|
d2805e7a8a | ||
|
|
4011b1c89a | ||
|
|
5bc348611c | ||
|
|
dd24affdbb | ||
|
|
d8542824a1 | ||
|
|
c6bf24ed60 | ||
|
|
a910550e34 | ||
|
|
01e53471d7 | ||
|
|
5569cff20e | ||
|
|
757c12c386 | ||
|
|
4cfe6cecda | ||
|
|
a34fbad038 | ||
|
|
6138ef03e0 | ||
|
|
9eb2821c9b | ||
|
|
628bd7bdb2 | ||
|
|
a3914da4ff | ||
|
|
6a1e7f08f4 | ||
|
|
15b400a3d9 | ||
|
|
e447cc3b67 | ||
|
|
8240c7e240 | ||
|
|
6f020e9574 | ||
|
|
c9d5f74ed4 | ||
|
|
78034f6dea | ||
|
|
b81cb52614 | ||
|
|
56280cd893 | ||
|
|
e515586e6b | ||
|
|
424af4c60d | ||
|
|
0a7048aca1 | ||
|
|
dd812e0684 | ||
|
|
51b7aebbc3 | ||
|
|
01b1698934 | ||
|
|
1025054bf4 | ||
|
|
06b4986997 | ||
|
|
31716ebcbc | ||
|
|
2e1f28f67e | ||
|
|
7f2e2b2d45 | ||
|
|
b961435e01 | ||
|
|
c54fab9603 | ||
|
|
cc6832afd6 | ||
|
|
29c41cb45a | ||
|
|
c18984a26b | ||
|
|
23d1ad0da6 | ||
|
|
49173dc766 | ||
|
|
03a563856d | ||
|
|
c3809c409d | ||
|
|
dfdca90ca5 | ||
|
|
6a8e1735db | ||
|
|
c0e9a0553e | ||
|
|
e1501165d9 | ||
|
|
3b0f706059 | ||
|
|
7d19662f68 | ||
|
|
5c949dc71c | ||
|
|
0439d67a0c | ||
|
|
d3446a20b1 | ||
|
|
5b37dc2e38 | ||
|
|
eee264918e | ||
|
|
89172a88f1 | ||
|
|
ffdb054291 | ||
|
|
200d39c408 | ||
|
|
4306574ace | ||
|
|
12e3b90458 | ||
|
|
03364b5d2e | ||
|
|
4e268991dc | ||
|
|
429c84f940 | ||
|
|
e890e4489b | ||
|
|
8466c42217 | ||
|
|
353732f597 | ||
|
|
5599d2507f | ||
|
|
70cf6ffe70 | ||
|
|
61c9277097 | ||
|
|
401052efd3 | ||
|
|
a57a0e797d | ||
|
|
8f48853e2c | ||
|
|
a62148dc07 | ||
|
|
3ae890bd86 | ||
|
|
65a4cd4ba5 | ||
|
|
f63b473bc1 | ||
|
|
859a5ba03a | ||
|
|
832b97b179 | ||
|
|
e98d688d36 | ||
|
|
39318337fe | ||
|
|
f21215be84 | ||
|
|
0690525af8 | ||
|
|
b3176425c5 | ||
|
|
9f2c18b6b6 | ||
|
|
d529a04f48 | ||
|
|
8786c5aa99 | ||
|
|
013279ab60 | ||
|
|
06193b6d49 | ||
|
|
ac6f4af5d6 | ||
|
|
bf148adc68 | ||
|
|
0f9dafb01d | ||
|
|
9fc0452b70 | ||
|
|
83eda9b3f5 | ||
|
|
9bfbf47963 | ||
|
|
252bf411b1 | ||
|
|
5622c019dd | ||
|
|
b32fab7865 | ||
|
|
cafdad1f7a | ||
|
|
9da40944ab | ||
|
|
f678203a64 | ||
|
|
a9572e08e9 | ||
|
|
ee9b042cdf | ||
|
|
bc138fa78a | ||
|
|
67dbe256f7 | ||
|
|
8b066d46e2 | ||
|
|
1769a65a82 | ||
|
|
941eb56769 | ||
|
|
a317613ef4 | ||
|
|
173571846f | ||
|
|
ab1078d393 | ||
|
|
681321a595 | ||
|
|
fcd50d4bc2 | ||
|
|
7e214e5aaa | ||
|
|
de39053857 | ||
|
|
f543e3218e | ||
|
|
e668aea214 | ||
|
|
d810daa735 | ||
|
|
b94b3118eb | ||
|
|
06cbd0c92d | ||
|
|
96ebbbf11d | ||
|
|
fc1467b05b | ||
|
|
53c27f2a59 | ||
|
|
db80f5c715 | ||
|
|
405c98ca50 | ||
|
|
4892aed9e6 | ||
|
|
1b04b94db2 | ||
|
|
4a430f5fe7 | ||
|
|
325bfd825f | ||
|
|
471553f913 | ||
|
|
a43d0689d8 | ||
|
|
afacb8a94b | ||
|
|
afe5300e00 | ||
|
|
ef85d0e323 | ||
|
|
d3aeedb9f6 | ||
|
|
7718f69269 | ||
|
|
af50b0c5c6 | ||
|
|
b576faad82 | ||
|
|
4583e603e9 | ||
|
|
7ee316a605 | ||
|
|
7dca225691 | ||
|
|
8a27d0240a | ||
|
|
939bd6fd91 | ||
|
|
dfbd385de7 | ||
|
|
0d791070dd | ||
|
|
94fbac38bf | ||
|
|
73cf8e75d3 | ||
|
|
395cbc104c | ||
|
|
9a24db8379 | ||
|
|
934a5f4838 | ||
|
|
9022500087 | ||
|
|
795807b6cf | ||
|
|
9eff79733c | ||
|
|
c4e95d9207 | ||
|
|
32cd3a62b6 | ||
|
|
d05ccc0055 | ||
|
|
ff1d7b44b4 | ||
|
|
2b86ba2128 | ||
|
|
44a3b30e3b | ||
|
|
bb9389c7dd | ||
|
|
ba4bfe9de7 | ||
|
|
3a73b14ebb | ||
|
|
31f54db433 | ||
|
|
26812dd297 | ||
|
|
ae4f4b4f08 | ||
|
|
4ac0a4c565 | ||
|
|
69c9f824a0 | ||
|
|
6a2220c960 | ||
|
|
aa501c2843 | ||
|
|
efce44f0a7 | ||
|
|
491eb83d35 | ||
|
|
35cf0802d1 | ||
|
|
f768548f60 | ||
|
|
37789f9907 | ||
|
|
131294464e | ||
|
|
b7652a41f8 | ||
|
|
91c5f85ec6 | ||
|
|
985983b31d | ||
|
|
9a27e38ce2 | ||
|
|
0017ed7c92 | ||
|
|
d5cf4ace21 | ||
|
|
1c899746fd | ||
|
|
9ea09c0fdd | ||
|
|
cda674c289 | ||
|
|
ef7d7ccc91 | ||
|
|
21aa5eb2d6 | ||
|
|
e725fb9b65 | ||
|
|
fbd634bfce | ||
|
|
9a686f3827 | ||
|
|
999219a0c9 | ||
|
|
65fd370cc5 | ||
|
|
232f0c38fa | ||
|
|
fa03968508 | ||
|
|
78dace32d4 | ||
|
|
a21e7aa0c5 | ||
|
|
9b7d828209 | ||
|
|
a1071426c0 | ||
|
|
23baf21ef5 | ||
|
|
d280b33073 | ||
|
|
d1ab6bb7a1 | ||
|
|
146d04ea64 | ||
|
|
fd46a3f8ac | ||
|
|
9974ca1e2e | ||
|
|
2c8a733359 | ||
|
|
4c9eeb9a61 | ||
|
|
de23be8c2d | ||
|
|
bb25a1fde9 | ||
|
|
9267c27bc5 | ||
|
|
591f073413 | ||
|
|
9c28c1ee00 | ||
|
|
120d41ea2f | ||
|
|
80b24a45ad |
50
.github/ISSUE_TEMPLATE/Contribution.yml.old
vendored
Normal 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 you’d like to contribute, please provide the following details:
|
||||
|
||||
1. **Contribution Area**: Describe what areas of the wiki you’re 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
@@ -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.
|
||||
30
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,30 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help me improve Excalidraw
|
||||
title: 'BUG: '
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Your environment**
|
||||
Please run `Command Palette/Show Debug info` in Obsidian and paste the result here.
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
40
.github/ISSUE_TEMPLATE/bug_report.md.x
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: When something is clearly broken. Everything else is a feature request.
|
||||
title: 'BUG: '
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Help me help you. I am a one man show doing this plugin as a part time hobby. There is no point in flooding me with issues, if there are too many, and they are poorly documented, I will just ignore them. Sorry...
|
||||
|
||||
Before creating a bug report, please
|
||||
1. review recent release notes - maybe there is already an answer,
|
||||
2. search issues (including closed ones) to see if there is anything similar.
|
||||
|
||||
⚠️ I will have to close all recorded bugs that do not provide this background information. Sorry, I need to control my workload/time. ⚠️
|
||||
|
||||
--------
|
||||
|
||||
**Your environment**
|
||||
Please run `Command Palette/Show Debug info` in Obsidian and paste the result here.
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
85
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
name: Bug report
|
||||
description: When something is clearly broken. Everything else is a feature request.
|
||||
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.
|
||||
|
||||
69
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal 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.
|
||||
@@ -195,7 +195,7 @@ This is relevant when setting a fix height using the `addText()` function.
|
||||
### startArrowHead, endArrowHead
|
||||
String. Valid values are "arrow", "bar", "dot", and "none". Specifies the beginning and ending of an arrow.
|
||||
|
||||
This is relavant when using the `addArrow()` and the `connectObjects()` functions.
|
||||
This is relevant when using the `addArrow()` and the `connectObjects()` functions.
|
||||
|
||||
## canvas
|
||||
Sets the properties of the canvas.
|
||||
|
||||
@@ -2,31 +2,3 @@ The project runs with `node 18`.
|
||||
|
||||
After running `npm -i` you'll need to make two manual changes:
|
||||
|
||||
## postprocess
|
||||
postprocess is used in rollup.config.js.
|
||||
However, the version available on npmjs does not work, after installing packages you need this update:
|
||||
`npm install brettz9/rollup-plugin-postprocess#update --save-dev``
|
||||
|
||||
More info here: https://github.com/developit/rollup-plugin-postprocess/issues/10
|
||||
|
||||
## colormaster
|
||||
1.2.1 misses 3 plugin references after installing the package you need to update
|
||||
`node_modules/colormaster/package.json` adding the following to the `exports:` section:
|
||||
```typescript
|
||||
,
|
||||
"./plugins/luv": {
|
||||
"import": "./plugins/luv.mjs",
|
||||
"require": "./plugins/luv.js",
|
||||
"default": "./plugins/luv.mjs"
|
||||
},
|
||||
"./plugins/uvw": {
|
||||
"import": "./plugins/uvw.mjs",
|
||||
"require": "./plugins/uvw.js",
|
||||
"default": "./plugins/uvw.mjs"
|
||||
},
|
||||
"./plugins/ryb": {
|
||||
"import": "./plugins/ryb.mjs",
|
||||
"require": "./plugins/ryb.js",
|
||||
"default": "./plugins/ryb.mjs"
|
||||
}
|
||||
```
|
||||
|
||||
17
README.md
@@ -1,9 +1,13 @@
|
||||
# Excalidraw
|
||||
|
||||
[简体中文](./docs/zh-cn/README.md)
|
||||
|
||||
👉👉👉 Check out and contribute to the new [Obsidian-Excalidraw Community Wiki](https://excalidraw-obsidian.online/Hobbies/Excalidraw+Blog/WIKI/Welcome+to+the+WIKI)
|
||||
|
||||
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.
|
||||
|
||||
## Video Walkthrough
|
||||
|
||||
<a href="https://youtu.be/P_Q6avJGoWI" target="_blank"><img src="https://github.com/zsviczian/obsidian-excalidraw-plugin/assets/14358394/da34bb33-7610-45e6-b36f-cb7a02a9141b" width="300"/></a>
|
||||
<a href="https://youtu.be/o0exK-xFP3k" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/156931370-aa4d88de-c4a8-46cc-aeb2-dc09aa0bea39.jpg" width="300"/></a>
|
||||
<a href="https://youtu.be/QKnQgSjJVuc" target="_blank"><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/thumbnail-getting-started.jpg" width="300"/></a>
|
||||
|
||||
@@ -63,6 +67,13 @@ The Obsidian-Excalidraw plugin integrates [Excalidraw](https://excalidraw.com/),
|
||||
<a href="https://youtu.be/4N6efq1DtH0" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/158008902-12c6a851-237e-4edd-a631-d48e81c904b2.jpg" width="100" style="vertical-align: middle;"/> Eraser, left-handed mode, improved filename configuration</a><br>
|
||||
</details>
|
||||
|
||||
### Beta testing
|
||||
The plugin follows a monthly release schedule. If you want to receive more frequent updates with new features (e.g. shiny new stuff available on excalidraw.com, but not yet in Obsidian) and minor bug fixes, then join the beta community.
|
||||
|
||||
[](https://youtu.be/2poSS-Z91lY)
|
||||
|
||||
[](https://github.com/user-attachments/assets/120a0790-7239-48ae-bfbd-eb249f8b518d)
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
@@ -243,11 +254,11 @@ Drag the desired file from the Obsidian file explorer and hold down <kbd>SHIFT</
|
||||
- In plugin settings, you can add a custom fourth font. For more details, see this [video](https://youtu.be/eKFmrSQhFA4)
|
||||
- The plugin includes OCR support using Taskbone OCR. For more details, see this [video](https://youtu.be/7gu4ETx7zro)
|
||||
- You can convert SVG files into Excalidraw drawings (with some limitation). For more details, see this [video](https://youtu.be/vlC1-iBvIfo)
|
||||
- You can define custom freedraw pens. See documentation [here].(https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Alternative%20Pens.md), [video](https://youtu.be/uZz5MgzWXiM)
|
||||
- You can define custom pens and higlighters and pin them to the sidebar. For more details, see this [video](https://youtu.be/OjNhjaH2KjI). Using ExcalidrawAutomate, you can add support for [auto-toggling](<ea-scripts/Auto Draw for Pen.md>) pen & support for [hardware eraser buttons](<ea-scripts/Hardware Eraser Support.md>).
|
||||
|
||||
### Script Engine
|
||||
|
||||
- Since 1.5.0, you can easily execute ExcalidrawAutomate macros and assign command palette shortcuts to them, using the ScriptEngine. You will find an intro video and a growing library of ready to install scripts [here](https://github.com/zsviczian/obsidian-excalidraw-plugin/tree/master/ea-scripts).
|
||||
- Since 1.5.0, you can easily execute ExcalidrawAutomate macros and assign command palette shortcuts to them, using the ScriptEngine. You will find an intro video and a growing library of ready to install scripts [here](ea-scripts/README.md).
|
||||
- You can organize scripts into groups on the Obsidian Tools Panel in Excalidraw by moving scripts and accompanying SVG icon files to folders. See the demo [video](https://youtu.be/wTtaXmRJ7wg?t=16).
|
||||
|
||||
### Other
|
||||
|
||||
@@ -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;
|
||||
17
docs/API/ExcalidrawAutomate.d.ts
vendored
@@ -6,7 +6,7 @@ import * as obsidian_module from "obsidian";
|
||||
import ExcalidrawView, { ExportSettings } from "src/ExcalidrawView";
|
||||
import { AppState, BinaryFileData, DataURL, ExcalidrawImperativeAPI, Point } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { EmbeddedFilesLoader } from "src/EmbeddedFileLoader";
|
||||
import { ConnectionPoint, DeviceType } from "src/types";
|
||||
import { ConnectionPoint, DeviceType } from "src/types/types";
|
||||
import { ColorMaster } from "colormaster";
|
||||
import { TInput } from "colormaster/types";
|
||||
import { ClipboardData } from "@zsviczian/excalidraw/types/excalidraw/clipboard";
|
||||
@@ -35,7 +35,7 @@ export declare class ExcalidrawAutomate {
|
||||
*/
|
||||
newFilePrompt(newFileNameOrPath: string, shouldOpenNewFile: boolean, targetPane?: PaneTarget, parentFile?: TFile): Promise<TFile | null>;
|
||||
/**
|
||||
* Generates a new Obsidian Leaf following Excalidraw plugin settings such as open in Main Workspace or not, open in adjacent pane if avaialble, etc.
|
||||
* Generates a new Obsidian Leaf following Excalidraw plugin settings such as open in Main Workspace or not, open in adjacent pane if available, etc.
|
||||
* @param origo // the currently active leaf, the origin of the new leaf
|
||||
* @param targetPane //type PaneTarget = "active-pane"|"new-pane"|"popout-window"|"new-tab"|"md-properties";
|
||||
* @returns
|
||||
@@ -123,7 +123,7 @@ export declare class ExcalidrawAutomate {
|
||||
* @param val //1: Virgil, 2:Helvetica, 3:Cascadia
|
||||
* @returns
|
||||
*/
|
||||
setFontFamily(val: number): "Virgil, Segoe UI Emoji" | "Helvetica, Segoe UI Emoji" | "Cascadia, Segoe UI Emoji" | "LocalFont";
|
||||
setFontFamily(val: number): "Virgil, Segoe UI Emoji" | "Helvetica, Segoe UI Emoji" | "Cascadia, Segoe UI Emoji" | "Local Font";
|
||||
/**
|
||||
* @param val //0:"light", 1:"dark"
|
||||
* @returns
|
||||
@@ -148,7 +148,7 @@ export declare class ExcalidrawAutomate {
|
||||
}>;
|
||||
/**
|
||||
* get all elements from ExcalidrawAutomate elementsDict
|
||||
* @returns elements from elemenetsDict
|
||||
* @returns elements from elementsDict
|
||||
*/
|
||||
getElements(): ExcalidrawElement[];
|
||||
/**
|
||||
@@ -422,6 +422,7 @@ export declare class ExcalidrawAutomate {
|
||||
appState?: AppState;
|
||||
files?: BinaryFileData;
|
||||
commitToHistory?: boolean;
|
||||
storeAction?: "capture" | "none" | "update";
|
||||
}, restore?: boolean): void;
|
||||
/**
|
||||
* connect an object to the selected element in the view
|
||||
@@ -698,25 +699,25 @@ export declare class ExcalidrawAutomate {
|
||||
*/
|
||||
moveViewElementToZIndex(elementId: number, newZIndex: number): void;
|
||||
/**
|
||||
* Depricated. Use getCM / ColorMaster instead
|
||||
* Deprecated. Use getCM / ColorMaster instead
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
hexStringToRgb(color: string): number[];
|
||||
/**
|
||||
* Depricated. Use getCM / ColorMaster instead
|
||||
* Deprecated. Use getCM / ColorMaster instead
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
rgbToHexString(color: number[]): string;
|
||||
/**
|
||||
* Depricated. Use getCM / ColorMaster instead
|
||||
* Deprecated. Use getCM / ColorMaster instead
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
hslToRgb(color: number[]): number[];
|
||||
/**
|
||||
* Depricated. Use getCM / ColorMaster instead
|
||||
* Deprecated. Use getCM / ColorMaster instead
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
|
||||
@@ -42,7 +42,7 @@ export declare class ExcalidrawAutomate {
|
||||
*/
|
||||
newFilePrompt(newFileNameOrPath: string, shouldOpenNewFile: boolean, targetPane?: PaneTarget, parentFile?: TFile): Promise<TFile | null>;
|
||||
/**
|
||||
* Generates a new Obsidian Leaf following Excalidraw plugin settings such as open in Main Workspace or not, open in adjacent pane if avaialble, etc.
|
||||
* Generates a new Obsidian Leaf following Excalidraw plugin settings such as open in Main Workspace or not, open in adjacent pane if available, etc.
|
||||
* @param origo // the currently active leaf, the origin of the new leaf
|
||||
* @param targetPane //type PaneTarget = "active-pane"|"new-pane"|"popout-window"|"new-tab"|"md-properties";
|
||||
* @returns
|
||||
@@ -155,7 +155,7 @@ export declare class ExcalidrawAutomate {
|
||||
}>;
|
||||
/**
|
||||
* get all elements from ExcalidrawAutomate elementsDict
|
||||
* @returns elements from elemenetsDict
|
||||
* @returns elements from elementsDict
|
||||
*/
|
||||
getElements(): ExcalidrawElement[];
|
||||
/**
|
||||
@@ -705,25 +705,25 @@ export declare class ExcalidrawAutomate {
|
||||
*/
|
||||
moveViewElementToZIndex(elementId: number, newZIndex: number): void;
|
||||
/**
|
||||
* Depricated. Use getCM / ColorMaster instead
|
||||
* Deprecated. Use getCM / ColorMaster instead
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
hexStringToRgb(color: string): number[];
|
||||
/**
|
||||
* Depricated. Use getCM / ColorMaster instead
|
||||
* Deprecated. Use getCM / ColorMaster instead
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
rgbToHexString(color: number[]): string;
|
||||
/**
|
||||
* Depricated. Use getCM / ColorMaster instead
|
||||
* Deprecated. Use getCM / ColorMaster instead
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
hslToRgb(color: number[]): number[];
|
||||
/**
|
||||
* Depricated. Use getCM / ColorMaster instead
|
||||
* Deprecated. Use getCM / ColorMaster instead
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
|
||||
@@ -88,4 +88,4 @@ This is relevant when setting a fix height using the `addText()` function.
|
||||
### startArrowHead, endArrowHead
|
||||
String. Valid values are "arrow", "bar", "dot", and "none". Specifies the beginning and ending of an arrow.
|
||||
|
||||
This is relavant when using the `addArrow()` and the `connectObjects()` functions.
|
||||
This is relevant when using the `addArrow()` and the `connectObjects()` functions.
|
||||
@@ -22,7 +22,7 @@ Places the generated drawing to the clipboard. Useful when you don't want to cre
|
||||
```typescript
|
||||
getElements():ExcalidrawElement[];
|
||||
```
|
||||
Returns the elements in ExcalidrawAutomate as an array of ExcalidrawElements. This format is usefull when working with ExcalidrawRef.
|
||||
Returns the elements in ExcalidrawAutomate as an array of ExcalidrawElements. This format is useful when working with ExcalidrawRef.
|
||||
|
||||
### getElement()
|
||||
```typescript
|
||||
@@ -156,7 +156,7 @@ You first need to set the view calling `setView()`.
|
||||
|
||||
Gets the array of selected elements in the scene. Returns [] if no elements are selected.
|
||||
|
||||
Note: you can call `getExcalidrawAPI().getSceneElements()` to retreive all the elements in the scene.
|
||||
Note: you can call `getExcalidrawAPI().getSceneElements()` to retrieve all the elements in the scene.
|
||||
|
||||
#### viewToggleFullScreen()
|
||||
```typescript
|
||||
@@ -178,7 +178,7 @@ Same as `connectObjects()`, but ObjectB is the currently selected element in the
|
||||
async addElementsToView(repositionToCursor:boolean=false, save:boolean=false):Promise<boolean>
|
||||
```
|
||||
Adds elements created with ExcalidrawAutomate to the target ExcalidrawView.
|
||||
`repositionToCursor` dafault is false
|
||||
`repositionToCursor` default is false
|
||||
- true: the elements will be moved such that the center point of the elements will be aligned with the current position of the pointer on ExcalidrawView. You can point and place elements to a desired location in your drawing using this switch.
|
||||
- false: elements will be positioned as defined by the x&y coordinates of each element.
|
||||
|
||||
@@ -204,7 +204,7 @@ onDropHook (data: {
|
||||
```
|
||||
|
||||
Callback function triggered when an draggable item is dropped on Excalidraw.
|
||||
The function should return a boolean value. True if the drop was handled by the hook and futher native processing should be stopped, and false if Excalidraw should continue with the processing of the drop.
|
||||
The function should return a boolean value. True if the drop was handled by the hook and further native processing should be stopped, and false if Excalidraw should continue with the processing of the drop.
|
||||
type of drop can be one of:
|
||||
- "file" if a file from Obsidian file explorer is dropped onto Excalidraw. In this case payload.files will contain the list of files dropped.
|
||||
- "text" if a link (e.g. url, or wiki link) or other text is dropped. In this case payload.text will contain the received string
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# [◀ Excalidraw Automate How To](../readme.md)
|
||||
## Generating a simple mindmap from a text outline
|
||||
This is a slightly more elaborate example. This will generate an a mindmap from a tabulated outline.
|
||||
This is a slightly more elaborate example. This will generate a mindmap from a tabulated outline.
|
||||
|
||||
### Output
|
||||

|
||||
|
||||
@@ -21,7 +21,7 @@ This will allow you to assign hotkeys to your favorite scripts just like to any
|
||||
|
||||
## Script development
|
||||
An Excalidraw script will automatically receive two objects:
|
||||
- `ea`: The Script Enginge will initialize the `ea` object including setting the active view to the View from which the script was called.
|
||||
- `ea`: The Script Engine will initialize the `ea` object including setting the active view to the View from which the script was called.
|
||||
- `utils`: I have borrowed functions exposed on utils from [QuickAdd](https://github.com/chhoumann/quickadd/blob/master/docs/QuickAddAPI.md), though currently not all QuickAdd utility functions are implemented in Excalidraw. As of now, these are the available functions. See the example below for details.
|
||||
- `inputPrompt: (header: string, placeholder?: string, value?: string, buttons?: [{caption:string, action:Function}])`
|
||||
- Opens a prompt that asks for an input. Returns a string with the input.
|
||||
|
||||
@@ -8,7 +8,7 @@ With a little work, using ExcalidrawAutomate you can generate simple mindmaps, b
|
||||
## API documentation
|
||||
- **start here** [Introduction to the API](API/introduction.md)
|
||||
- [Overview of Attributes and Functions](API/attributes_functions_overview.md)
|
||||
- [Element Sytle](API/element_style.md)
|
||||
- [Element Style](API/element_style.md)
|
||||
- [Canvas Style](API/canvas_style.md)
|
||||
- [Adding Objects](API/objects.md)
|
||||
- [Utility Functions](API/utility.md)
|
||||
|
||||
484
docs/zh-cn/AutomateHowTo.md
Normal 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();
|
||||
%>
|
||||
```
|
||||
该脚本将生成以下绘图:
|
||||
|
||||

|
||||
|
||||
## 属性和功能一览
|
||||
这是 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 值从上到下增加。
|
||||
|
||||

|
||||
|
||||
### 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});
|
||||
%>
|
||||
```
|
||||
|
||||
### 从文本大纲生成简单思维导图
|
||||
这是一个稍微复杂一些的示例。这个示例将从一个表格化的大纲生成思维导图。
|
||||
|
||||

|
||||
|
||||
输入示例:
|
||||
|
||||
```
|
||||
- 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});
|
||||
%>
|
||||
```
|
||||
289
docs/zh-cn/README.md
Normal file
@@ -0,0 +1,289 @@
|
||||
# Excalidraw
|
||||
|
||||
> 此说明当前更新至 `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) 或观看以下视频。
|
||||
|
||||
## 视频演示
|
||||
|
||||
<a href="https://youtu.be/P_Q6avJGoWI" target="_blank"><img src="https://github.com/zsviczian/obsidian-excalidraw-plugin/assets/14358394/da34bb33-7610-45e6-b36f-cb7a02a9141b" width="300"/></a>
|
||||
<a href="https://youtu.be/o0exK-xFP3k" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/156931370-aa4d88de-c4a8-46cc-aeb2-dc09aa0bea39.jpg" width="300"/></a>
|
||||
<a href="https://youtu.be/QKnQgSjJVuc" target="_blank"><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/thumbnail-getting-started.jpg" width="300"/></a>
|
||||
|
||||
### 这是我完整的视频目录:
|
||||
|
||||
<a href="https://excalidraw-obsidian.online/Hobbies/Excalidraw+Blog/Catalogue+of+Videos"><img width="380" alt="image" src="https://github.com/zsviczian/obsidian-excalidraw-plugin/assets/14358394/2577e5ad-7a21-4c62-acd5-4fe80c8a8a95"></a>
|
||||
<br>
|
||||
|
||||
<details><summary>10 部分(稍微过时)视频演示</summary>
|
||||
<a href="https://youtu.be/sY4FoflGaiM" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/125160304-7f211180-e17c-11eb-8363-c52723de1ffd.jpg" width="100" style="vertical-align: middle;"/> 1 入门</a><br>
|
||||
<a href="https://youtu.be/Iy_oVTq12Gw" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/125160312-8a743d00-e17c-11eb-9fa2-490ef4cbd59e.jpg" width="100" style="vertical-align: middle;"/> 2 基本形状和功能</a><br>
|
||||
<a href="https://youtu.be/QOL1KF7-kdc" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/125160323-96f89580-e17c-11eb-9bce-8eb1067a51bb.jpg" width="100" style="vertical-align: middle;"/> 3 元素分组</a><br>
|
||||
<a href="https://youtu.be/aSgcbfspvfo" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/125160332-9f50d080-e17c-11eb-98e9-fec60fe147d9.jpg" width="100" style="vertical-align: middle;"/> 4 模板库</a><br>
|
||||
<a href="https://youtu.be/MaJ5jJwBRWs" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/125160341-a546b180-e17c-11eb-9de8-d87fdc844c9c.jpg" width="100" style="vertical-align: middle;"/> 5 嵌入</a><br>
|
||||
<a href="https://youtu.be/MXzeCOEExNo" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/125160346-aa0b6580-e17c-11eb-930b-4024807040d1.jpg" width="100" style="vertical-align: middle;"/> 6 链接</a><br>
|
||||
<a href="https://youtu.be/R0IAg0s-wQE" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/125160354-b2fc3700-e17c-11eb-81af-9e71e461f6dd.jpg" width="100" style="vertical-align: middle;"/> 7 Markdown</a><br>
|
||||
<a href="https://youtu.be/ibdS7ykwpW4" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/125160360-b8f21800-e17c-11eb-8bd8-79d4e3f6e92d.jpg" width="100" style="vertical-align: middle;"/> 8 模板</a><br>
|
||||
<a href="https://youtu.be/VRZVujfVab0" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/125160367-bdb6cc00-e17c-11eb-92f1-6f59faea85fd.jpg" width="100" style="vertical-align: middle;"/> 9 Excalidraw 自动化</a><br>
|
||||
<a href="https://youtu.be/D1iBYo1_jjc" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/125160374-c3141680-e17c-11eb-8cc2-dfaffd903d15.jpg" width="100" style="vertical-align: middle;"/> 10 杂项</a><br>
|
||||
</details>
|
||||
|
||||
<details><summary>将内容嵌入 Excalidraw</summary>
|
||||
<a href="https://www.youtube.com/watch?v=_c_0zpBJ4Xc&" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/138607067-ccb62f92-48a4-4880-ac6e-68c1bf86ac2c.png" width="100" style="vertical-align: middle;"/> 图像元素</a><br>
|
||||
<a href="https://youtu.be/r08wk-58DPk" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/143732412-1c65227e-4381-406d-847a-b001ab3506ca.jpg" width="100" style="vertical-align: middle;"/> LaTeX 演示</a><br>
|
||||
<a href="https://youtu.be/tsecSfnTMow" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/143732440-90bfa029-8615-462e-ada3-c903d71a82c9.jpg" width="100" style="vertical-align: middle;"/> Markdown 嵌入</a><br>
|
||||
<a href="https://youtu.be/K6qZkTz8GHs" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/143783906-15cee494-c6d5-4495-a2ca-74634e4e7355.jpg" width="100" style="vertical-align: middle;"/> Markdown 嵌入高级功能</a><br>
|
||||
<a href="https://youtu.be/Etskjw7a5zo" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/156931461-0979b821-315a-41dd-86f1-31d169b7c127.jpg" width="100" style="vertical-align: middle;"/> 链接到元素、垂直文本对齐、Markdown 样式</a><br>
|
||||
</details>
|
||||
<details><summary>脚本引擎商店 - Excalidraw 自动化</summary>
|
||||
<a href="https://youtu.be/hePJcObHIso" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/145684531-8d9c2992-59ac-4ebc-804a-4cce1777ded2.jpg" width="100" style="vertical-align: middle;"/> 介绍脚本引擎</a><br>
|
||||
<a href="https://youtu.be/lzYdOQ6z8F0" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/147889174-6c306d0d-2d29-46cc-a53f-3f0013cf14de.jpg" width="100" style="vertical-align: middle;"/> 脚本引擎商店</a><br>
|
||||
</details>
|
||||
<details><summary>使用颜色</summary>
|
||||
<a href="https://youtu.be/6PLGHBH9VZ4" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/194773147-5418a0ab-6be5-4eb0-a8e4-d6af21b1b483.png" width="100" style="vertical-align: middle;"/> 颜色 - Excalidraw 基础(自定义)</a><br>
|
||||
<a href="https://youtu.be/epYNx2FSf2w" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/194773211-9e871be7-0795-4dc7-947e-c6c275e690d0.png" width="100" style="vertical-align: middle;"/> Excalidraw 调色板(自定义)</a><br>
|
||||
<a href="https://youtu.be/Amhlv6r9WvM" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/194773268-400cfb1b-6bde-45e0-9e4b-91bbaa461cf0.png" width="100" style="vertical-align: middle;"/> “艺术”颜色渐变</a><br>
|
||||
<a href="https://youtu.be/r9oB1SlK1GU" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/194773527-ef35c8b9-1a6d-4415-9c7e-b667fb17535d.png" width="100" style="vertical-align: middle;"/> 美丽草图的简单规则</a><br>
|
||||
<a href="https://youtu.be/7gJDwNgQ6NU" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/195988535-a133a9b9-d094-45ba-ba64-c994b9a1e0ef.png" width="100" style="vertical-align: middle;"/> ColorMaster 脚本编写</a><br>
|
||||
</details>
|
||||
<details><summary>链接和块引用</summary>
|
||||
<a href="https://youtu.be/qiKuqMcNWgU" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/171635214-30533c45-94fa-436e-83a9-b2ec99f190e2.jpg" width="100" style="vertical-align: middle;"/> 链接视觉思维的 6 种策略 v4</a><br>
|
||||
<a href="https://youtu.be/yZQoJg2RCKI" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/185791706-3d9983ab-7cb1-4b27-a016-30c039d84e34.jpg" width="100" style="vertical-align: middle;"/> 图像的块引用部分</a><br>
|
||||
<a href="https://youtu.be/Etskjw7a5zo" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/156931461-0979b821-315a-41dd-86f1-31d169b7c127.jpg" width="100" style="vertical-align: middle;"/> 链接到元素、垂直文本对齐、Markdown 样式</a><br>
|
||||
<a href="https://youtu.be/2Y8OhkGiTHg" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/152585752-7eb0371f-0bab-40f6-a194-3b48e5811735.jpg" width="100" style="vertical-align: middle;"/> Excalidraw 原生超链接使用指南</a><br>
|
||||
</details>
|
||||
<details><summary>强大工具</summary>
|
||||
<a href="https://youtu.be/NOuddK6xrr8" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/147283367-e5689385-ea51-4983-81a3-04d810d39f62.jpg" width="100" style="vertical-align: middle;"/> 便签(自动换行)</a><br>
|
||||
<a href="https://youtu.be/eKFmrSQhFA4" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/149659524-2a4e0a24-40c9-4e66-a6b1-c92f3b88ecd5.jpg" width="100" style="vertical-align: middle;"/> 本地字体</a><br>
|
||||
<a href="https://youtu.be/vlC1-iBvIfo" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/199207784-8bbe14e0-7d10-47d7-971d-20dce8dbd659.png" width="100" style="vertical-align: middle;"/> SVG 导入</a><br>
|
||||
<a href="https://youtu.be/7gu4ETx7zro" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/202916770-28f2fa64-1ba2-4b40-a7fe-d721b42634f7.png" width="100" style="vertical-align: middle;"/> OCR</a><br>
|
||||
<a href="https://youtu.be/U2LkBRBk4LY" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/159369910-6371f08d-b5fa-454d-9c6c-948f7e7a7d26.jpg" width="100" style="vertical-align: middle;"/> 绑定/解绑文本与容器,前置标签自定义导出</a><br>
|
||||
<a href="https://youtu.be/uZz5MgzWXiM" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/211054371-8872e01a-77d6-4afc-a0c2-86a55410a8d3.png" width="100" style="vertical-align: middle;"/> 自定义笔支持</a><br>
|
||||
</details>
|
||||
<details><summary>生活质量改善</summary>
|
||||
<a href="https://youtu.be/qbPIAZguJeo" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/151705333-54e9ffd2-0bd7-4d02-b99e-0bd4e4708d4d.jpg" width="100" style="vertical-align: middle;"/> 移动支持</a><br>
|
||||
<a href="https://youtu.be/2v9TZmQNO8c" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/153676009-6f86b2d7-c248-49a2-b802-be21c6999e4f.jpg" width="100" style="vertical-align: middle;"/> 托盘模式和可自定义调色板</a><br>
|
||||
<a href="https://youtu.be/xHPGWR3m0c8" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/154821232-a404b6cf-72fb-4ce4-9d53-619132dce491.jpg" width="100" style="vertical-align: middle;"/> 压缩 JSON 和改进的保存/同步支持</a><br>
|
||||
<a href="https://youtu.be/gMIKXyhS-dM" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/156931428-b2269fd9-87bd-43ab-8558-5572f40dff93.jpg" width="100" style="vertical-align: middle;"/> Obsidian 工具面板</a><br>
|
||||
<a href="https://youtu.be/4N6efq1DtH0" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/158008902-12c6a851-237e-4edd-a631-d48e81c904b2.jpg" width="100" style="vertical-align: middle;"/> 橡皮擦、左利手模式、改进的文件名配置</a><br>
|
||||
</details>
|
||||
|
||||
### Beta 测试
|
||||
|
||||
该插件遵循每月发布的计划。如果您希望获得更频繁的更新,包括新功能(例如,excalidraw.com 上的新内容,但尚未在 Obsidian 中提供)和小的 bug 修复,请加入 beta 社区。
|
||||
|
||||
[](https://youtu.be/2poSS-Z91lY)
|
||||
|
||||
[](https://github.com/user-attachments/assets/120a0790-7239-48ae-bfbd-eb249f8b518d)
|
||||
|
||||
---
|
||||
|
||||
## 功能
|
||||
|
||||
- 该插件将 Excalidraw 无缝集成到 Obsidian 中,包括命令面板操作、文件资源管理器功能、选项菜单命令和工具栏按钮。
|
||||
- 在工具栏按钮或文件资源管理器中 <kbd>CTRL/CMD+鼠标左键</kbd> 以在新面板中创建/打开绘图。
|
||||
|
||||
### 设置
|
||||
|
||||
设置将允许您根据需要自定义 Excalidraw。该插件提供了大量设置。我尝试为这些设置添加有意义的解释,所以请耐心查找,对于大多数请求,已经存在相关设置。
|
||||
|
||||
插件设置分为以下几个部分:
|
||||
|
||||
- **基本设置**:例如使用的默认文件夹。
|
||||
- **保存**:压缩和自动保存间隔。
|
||||
- **文件名**:配置自动生成的 Excalidraw 文件名。
|
||||
- **显示**:影响 Excalidraw 处理的设置(例如:左利手模式、主题设置、鼠标滚轮和捏合缩放设置、适应缩放设置)。
|
||||
- **链接和嵌入**:影响链接和嵌入项在 Excalidraw 画布上行为的设置。
|
||||
- **Markdown 嵌入设置**:这些设置控制从您的 Vault 嵌入到 Excalidraw 绘图中的 Markdown 文档的行为。
|
||||
- **嵌入与导出**:控制 Excalidraw 图像在嵌入到 Markdown 文档时的显示方式的设置。
|
||||
- **自动导出设置**:您可以配置 Excalidraw 在每次保存时创建绘图的 PNG 或 SVG 副本。
|
||||
- **兼容性功能**:如果您在 Obsidian 之外编辑 Excalidraw 绘图(例如在 LogSeq、Visual Studio、网页等),请检查这些设置。
|
||||
- **实验性功能**:有一些高级功能作为“巧妙”的 hacks 实现,包括定义本地字体、添加自定义图标以区分 Obsidian 文件资源管理器中的 Excalidraw 文件、OCR 设置等。
|
||||
- **已安装脚本的设置**:从脚本库安装的一些脚本附带设置。脚本设置在您第一次运行脚本时安装。因此,要访问脚本的设置,请安装脚本,首次运行后在插件设置中查找设置。
|
||||
|
||||
#### 模板
|
||||
|
||||
- 新绘图的模板。该模板将恢复笔画属性。这意味着您可以在模板中设置笔画颜色、笔画宽度、不透明度、字体系列、字体大小、填充样式、笔画样式等的默认值。这同样适用于 ExcalidrawAutomate。
|
||||
- 通过模板,您可以自定义 Excalidraw 使用的调色板。
|
||||
- 切换到 Markdown 视图。
|
||||
- 滚动到文件底部,找到 `"AppState": {`。
|
||||
- 在 AppState 部分末尾找到 `"customColorPalette": {`。
|
||||
- 您可以通过添加以下三个变量中的任何一个或全部来指定 Excalidraw 使用的 3 个调色板:
|
||||
- `"canvasBackground":[], "elementBackground":[], "elementStroke": []`。
|
||||
- 在每个变量的数组中添加有效 HTML 颜色的逗号分隔列表(例如,`#FF0000` 表示红色)。
|
||||
- 有关更多帮助,请查看我上面的录像。
|
||||
|
||||
#### 导出
|
||||
|
||||
- 如果便携性对您很重要:
|
||||
- 自动导出 SVG 和/或 PNG 文件,包括同步保持功能,这样您可以将 SVG/PNG 嵌入到文档中,而不是嵌入 Excalidraw 文件。
|
||||
- 您可以通过添加 `excalidraw-autoexport` 前置字段键来覆盖单个文件的导出设置。该键的有效值为 `none`、`both`、`png` 和 `svg`。
|
||||
|
||||
- 指定嵌入绘图的默认宽度。
|
||||
- 兼容性功能以自动导出和保持同步 Markdown Excalidraw 文件及旧版 `.excalidraw` 文件。
|
||||
- 实验性功能可在文件资源管理器中添加自定义标签以标记绘图文件。
|
||||
- 启用/禁用自动保存。
|
||||
|
||||
### 将您的绘图嵌入到 Markdown 文档中
|
||||
|
||||
- 您可以使用以下格式自定义嵌入图像的大小和位置:
|
||||
- `![[image.excalidraw|100]]`,
|
||||
- `![[image.excalidraw|100x100]]`,
|
||||
- `![[image.excalidraw|100|left]]`,
|
||||
- `![[image.excalidraw|right-wrap]]`,
|
||||
- `![[<filename.excalidraw>|<width>x<height>|<alignment>]]`。
|
||||
- 您可以通过 CSS 添加自定义 [对齐方式](https://www.scaler.com/topics/align-image-in-html/)。
|
||||
- 出现在 `<alignment>` 中的任何文本将被添加到渲染的 SVG 元素样式和包装 DIV 元素中。
|
||||
- 有关更多信息,请参见 [styles.css](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/styles.css)。
|
||||
- Excalidraw 绘图在 Obsidian Publish 中不显示。如果您希望在发布的文档中使用 Excalidraw,可以在插件设置中的 `Embed & Export` 下进行配置,以便在创建新文件时自动将绘图的 PNG 或 SVG 版本插入到文档中。请参见 `type of file to insert into document`。
|
||||
- 在 `Export settings` 下,您还可以配置自动导出图像的深色和浅色版本,以便您的发布网站支持深色和浅色模式。
|
||||
|
||||
### 超链接和拖放支持
|
||||
|
||||

|
||||
|
||||
#### 超链接
|
||||
|
||||
- 支持超链接,例如:
|
||||
- `https://zsolt.blog`,
|
||||
- `[Obsidian](https://obsidian.md)`,以及
|
||||
- 内部链接,例如在绘图文本中使用 `[[My file in vault|Alias]]`。
|
||||
- 如果您启用了 Obsidian 设置中的“文件与链接/自动更新内部链接”,则文件移动或重命名时链接会自动更新。
|
||||
- 绘图中的链接会出现在文档的反向链接中。
|
||||
- 支持嵌入:
|
||||
- `![[myfile#^blockref]]` 将绘图转换为该块的嵌入文本。
|
||||
- `![[myfile#section]]` 也有效,这将嵌入该部分。
|
||||
- 您还可以通过在嵌入后加上最大字符数的花括号来指定嵌入文本的换行,例如 `![[myfile#^blockref]]{40}` 将在 40 个字符处换行。
|
||||
- 为了方便,您还可以使用命令面板将链接插入到绘图中。
|
||||
- <kbd>CTRL/CMD + 鼠标悬停</kbd> 可以调出链接的 Obsidian 快速预览。(在 Mac 上为 <kbd>CTRL+CMD+鼠标悬停</kbd>)。
|
||||
- 使用块引用,您还可以在其他文档中引用和嵌入绘图中出现的文本。
|
||||
|
||||
#### 拖放支持
|
||||
|
||||
- 您可以从 Obsidian 文件资源管理器中拖动文件,它们将成为 Excalidraw 中指向这些文件的链接。有关各种修饰键组合,请参见上面的表格。
|
||||
- 注意:将图像锚定到其 100% 尺寸是一个非常小众的功能,具有非常特定的行为,我主要是为自己开发的。
|
||||
- (甚至 Excalidraw Obsidian 中的其他功能更是如此 - 也是主要为自己开发的 😉)。
|
||||
- 每次打开 Excalidraw 绘图时,这将重置您嵌入的图像为 100% 尺寸,或者如果您在画布上嵌入了使用此功能插入的 Excalidraw 绘图,每次更新嵌入的绘图时,它将缩放回 100% 尺寸。
|
||||
- 这意味着即使您在绘图中调整了图像的大小,下次打开文件或修改原始嵌入对象时,它也会重置为 100%。此功能在将绘图分解为单独的 Excalidraw 文件时非常有用,但当它们组合到单个画布上时,您希望各个部分保持其实际大小。我使用此功能从原子绘图构建“一页书”摘要。
|
||||
- 您可以将文本从 Markdown 视图拖放到 Excalidraw 中。
|
||||
- 您可以从浏览器中拖放网页地址,它们将成为链接。
|
||||
- 您可以拖放 YouTube 链接和缩略图,它们将在 Excalidraw 中成为带缩略图的 YouTube 链接。
|
||||
|
||||
### LaTeX
|
||||
|
||||
使用命令面板操作“插入 LaTeX 公式”插入 LaTeX 公式。您可以在 Markdown 视图中编辑公式,或者通过 <kbd>CTRL/CMD + 鼠标左键</kbd> 点击公式进行编辑。
|
||||
|
||||
### 图像支持
|
||||
|
||||
- 在 iOS 和 Android 上,您可以通过按下 Excalidraw 中的添加图像按钮从相机添加图像。
|
||||
- 您可以将图像复制/粘贴到绘图中。图像将保存在您的 Vault 中。
|
||||
- 您可以按照上面的说明拖放图像。
|
||||
- URL 链接到网络上的图像:您可以从网页将图像拖放到 Excalidraw。如果在将图像拖放到 Excalidraw 时按住 CTRL 键,图像将不会保存到您的 Vault 中。Excalidraw 将从 URL 加载图像。请注意,如果您没有互联网连接,或者这些图像从互联网上被删除,它们也会从您的绘图中消失。
|
||||
- 如果您将图像 URL 粘贴到 Excalidraw(只需点击 URL 复制,然后在 Excalidraw 画布上点击粘贴),图像将以链接形式插入到网络图像上。同样,图像不会保存到您的 Vault 中,只有链接会被保存。
|
||||
- 如果您拖放 YouTube 视频链接,它将转换为一个缩略图,并带有指向视频的元素链接。
|
||||
|
||||
### 引用图像部分的块
|
||||
|
||||
有关更多详细信息,请参见此 [视频](https://youtu.be/yZQoJg2RCKI)。
|
||||
- 当通过链接引用 Excalidraw 文件中的画布元素时,可以使用:
|
||||
- 元素 ID 或章节标题(即包含 `# <章节标题>` 的文本元素)
|
||||
- 例如 `[[file#^elementID]]`,
|
||||
- 您可以添加 `group=` 前缀,
|
||||
- 例如 `[[file#^group=elementID]]`,或
|
||||
- `area=` 前缀,
|
||||
- 例如 `[[file#area=Section heading]]`。
|
||||
- 如果找到 `group=` 前缀,Excalidraw 将选择与通过元素 ID(块引用)或章节标题引用的元素在同一组中的元素。
|
||||
- 如果找到 `area=` 前缀,Excalidraw 将在引用的元素周围插入图像的剪切部分。
|
||||
- 请注意,当将 Excalidraw 嵌入为 PNG 到您的 Markdown 文档时,不支持 `area=` 选择器。
|
||||
- 引用文本元素的元素 ID 而不带 `group=` 或 `area=` 前缀将以普通文本嵌入该元素。引用非文本元素(例如矩形、椭圆等)而不带 `group=` 或 `area=` 前缀将导致 Obsidian 错误,因为这些元素 ID 在 Excalidraw Markdown 文件中不能够作为块引用。
|
||||
|
||||
### Markdown
|
||||
|
||||
- 从 1.2.0 版本开始,绘图文件存储在 Markdown 文件中。
|
||||
- 您可以为绘图添加标签。
|
||||
- 您可以在绘图的 YAML 前置字段中添加元数据。
|
||||
- 您在前置字段和 `# Text Elements` 标题之间添加的任何内容将被 Excalidraw 忽略,即您可以在这里添加任何内容,它将作为文档的一部分被保留。
|
||||
- Excalidraw 文档现在会在图形视图中显示。
|
||||
- 以下前置字段键将自定义绘图的显示方式 - 覆盖一般设置:
|
||||
- `excalidraw-link-prefix: "📍"` 内部链接的预览前缀
|
||||
- `excalidraw-url-prefix: "🌐"` 外部链接的预览前缀
|
||||
- `excalidraw-link-brackets: true|false` 是否在预览中显示链接周围的括号
|
||||
- `excalidraw-default-mode: view|zen` 默认以查看模式或禅模式打开此文档。默认查看模式非常适合演示幻灯片。
|
||||
- 前置字段标签用于在文件级别自定义图像导出 [519](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/519)。如果这些键存在,它们将覆盖默认的 Excalidraw 嵌入和导出设置。
|
||||
- `excalidraw-export-transparent: true`: true == 透明 / false == 有背景。
|
||||
- `excalidraw-export-dark`: true == 深色模式 / false == 浅色模式。
|
||||
- `excalidraw-export-padding`:指定图像的导出边距。
|
||||
- `excalidraw-export-pngscale`:这仅影响导出为 PNG。指定图像的导出比例。典型范围在 0.5 到 5 之间,但您也可以尝试其他值。
|
||||
|
||||
### 将完整的 Markdown 文件嵌入到您的绘图中
|
||||
|
||||
从 Obsidian 文件资源管理器中拖动所需文件,同时按住 <kbd>SHIFT</kbd> 将文件放到画布上。
|
||||
- 使用命令面板操作:`从 Vault 插入 Markdown 文件`
|
||||
- 使用自定义的 woff、woff2 或 TTF 字体来显示文档,您可以在 Excalidraw 设置中设置默认字体。
|
||||
- 您可以为渲染 Markdown 文档的快照图像设置自定义 CSS。仅支持操作系统标准字体作为字体系列([Win10](https://docs.microsoft.com/en-us/typography/fonts/windows_10_font_list)、[Mac & iOS](https://developer.apple.com/fonts/system-fonts/)),此外,您可以使用上述设置添加一个额外的自定义字体。
|
||||
- (要查看演示,请观看此 [视频](https://youtu.be/K6qZkTz8GHs) 并查看此
|
||||
- [示例 CSS](https://github.com/zsviczian/obsidian-excalidraw-plugin/discussions/281))。
|
||||
- 为了帮助样式设置,您可以查看 Excalidraw 创建的 Markdown 文档的 SVG 快照。
|
||||
- 打开 Obsidian 开发者控制台 (<kbd>CTRL+Shift+i</kbd>/<kbd>CMD+OPT+i</kbd>),并
|
||||
- 执行以下命令:`ExcalidrawAutomate.mostRecentMarkdownSVG`
|
||||
- 您可以通过将以下前置字段键添加到您的 Markdown 文档,按文件控制嵌入 Markdown 文件的外观:
|
||||
- `excalidraw-font: Virgil|Cascadia|font_file_name.extension`
|
||||
- `excalidraw-font-color: css-color-name|#HEXcolor|any-other-html-standard-format`,
|
||||
- 您可以在 [这里](https://www.w3schools.com/colors/colors_names.asp) 找到 CSS 颜色名称。
|
||||
- `excalidraw-border-color: css-color-name|#HEXcolor|any-other-html-standard-format`
|
||||
- `excalidraw-css: "css-filename|css snippet"`
|
||||
- 切换到 Markdown 视图或使用 <kbd>WIN+CTRL</kbd>/<kbd>CMD+CTRL</kbd> 点击图像以编辑嵌入的属性:
|
||||
- `[[filename#^blockref|WIDTHxMAXHEIGHT]]`
|
||||
|
||||
### 自定义字体、自定义笔、OCR 支持、SVG 导入
|
||||
|
||||
- 在插件设置中,您可以添加自定义的本地字体。有关更多详细信息,请参见此 [视频](https://youtu.be/eKFmrSQhFA4)。
|
||||
- 该插件包括使用 Taskbone OCR 的 OCR 支持。有关更多详细信息,请参见此 [视频](https://youtu.be/7gu4ETx7zro)。
|
||||
- 您可以将 SVG 文件转换为 Excalidraw 绘图(有一些限制)。有关更多详细信息,请参见此 [视频](https://youtu.be/vlC1-iBvIfo)。
|
||||
- 您可以定义自定义笔和荧光笔,并将其固定到侧边栏。有关更多详细信息,请参见此 [视频](https://youtu.be/OjNhjaH2KjI)。使用 ExcalidrawAutomate,您可以添加对 [自动切换](<ea-scripts/Auto Draw for Pen.md>) 笔的支持,以及对 [硬件橡皮擦按钮](<ea-scripts/Hardware Eraser Support.md>) 的支持。
|
||||
|
||||
### 脚本引擎
|
||||
|
||||
- 从 1.5.0 版本开始,您可以轻松执行 ExcalidrawAutomate 宏,并为它们分配命令面板快捷键,使用脚本引擎。您可以在 [这里](ea-scripts/README.md) 找到介绍视频和不断增加的可安装脚本库。
|
||||
- 您可以通过将脚本和随附的 SVG 图标文件移动到文件夹中,将脚本组织成组,放在 Excalidraw 的 Obsidian 工具面板中。请参见演示 [视频](https://youtu.be/wTtaXmRJ7wg?t=16)。
|
||||
|
||||
### 其他
|
||||
|
||||
- 左利手模式
|
||||
- 包含完整的
|
||||
- [QuickAdd](https://github.com/chhoumann/quickadd),
|
||||
- [Templater](https://silentvoid13.github.io/Templater/) 和
|
||||
- [Dataview](https://blacksmithgu.github.io/obsidian-dataview/docs/api/intro/) 支持,通过 ExcalidrawAutomate 实现。
|
||||
- 查看 [详细帮助 + 示例](https://zsviczian.github.io/obsidian-excalidraw-plugin/)。
|
||||
- 我还有一个 [YouTube ExcalidrawAutomate 播放列表](https://www.youtube.com/playlist?list=PL6mqgtMZ4NP1IR4nXxSlMA4PA5E-qpyHZ),里面有很多示例。
|
||||
- 需要 Obsidian Sync 订阅:完整的绘图文件历史记录和设备之间的同步。
|
||||
- 多语言支持:如果您想通过翻译插件来帮助,请与我联系。
|
||||
|
||||
## 反馈、问题、想法、问题
|
||||
|
||||
请在 [forum.obsidian.md](https://forum.obsidian.md/t/excalidraw-full-featured-sketching-plugin-in-obsidian) 上参与关于 Excalidraw 插件的讨论。
|
||||
|
||||
请前往 [GitHub](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues) 报告错误或请求增强功能。
|
||||
|
||||
---
|
||||
|
||||
## 感谢支持
|
||||
|
||||
如果您喜欢 Excalidraw,请通过在 [https://ko-fi/zsolt](https://ko-fi.com/zsolt) 上请我喝杯咖啡来支持我的工作和热情。
|
||||
|
||||
请通过在 Twitter、Reddit 或其他您常用的社交媒体平台上分享 Obsidian Excalidraw 插件来帮助传播消息。
|
||||
|
||||
您可以在 Twitter 上找到我 [@zsviczian](https://twitter.com/zsviczian),以及我的博客 [zsolt.blog](https://zsolt.blog)。
|
||||
|
||||
[<img style="float:left" src="https://user-images.githubusercontent.com/14358394/115450238-f39e8100-a21b-11eb-89d0-fa4b82cdbce8.png" width="200">](https://ko-fi.com/zsolt)
|
||||
|
||||
---
|
||||
|
||||
## Excalidraw 的朋友们
|
||||
如果您喜欢 Excalidraw,可以考虑尝试 [ExcaliBrain](https://github.com/zsviczian/excalibrain)(也可通过 Obsidian 社区插件获得)。
|
||||
|
||||
<a href="https://youtu.be/gOkniMkDPyM" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/169708346-9e41289d-9536-43ec-8f70-2d2ad2d369d6.png" width="300"/></a>
|
||||
@@ -4,7 +4,7 @@
|
||||
THIS SCRIPT REQUIRES EXCALIDRAW 1.5.15
|
||||
|
||||
The script will
|
||||
1) send the selected image file to [taskbone.com](https://taskbone.com) to exctract the text from the image, and
|
||||
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
|
||||
|
||||
I recommend also installing the [Transfer TextElements to Excalidraw markdown metadata](Transfer%20TextElements%20to%20Excalidraw%20markdown%20metadata.md) script as well.
|
||||
|
||||
@@ -15,7 +15,7 @@ In the `Command Palette` installed scripts are prefixed with `Downloaded/`, thus
|
||||
|
||||
## Attention developers and hobby hackers
|
||||
<img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/hobby-programmer.svg' align='left' style='background-color:whitesmoke; width:80px; margin-right:15px; margin-bottom:10px;'/>
|
||||
If you want to modify scripts, I recommend moving them to the `Excalidraw Automate script folder` or a different subfolder under the script folder. Scripts in the `Downloaded` folder will be overwritten when you click the `Update this script` button. Note also, that at this time, I do not check if the script file has been updated on GitHub, thus the `Update this script` button is always visible once you have installed a script, not only when an update is availble (hope to build this feature in the future).
|
||||
If you want to modify scripts, I recommend moving them to the `Excalidraw Automate script folder` or a different subfolder under the script folder. Scripts in the `Downloaded` folder will be overwritten when you click the `Update this script` button. Note also, that at this time, I do not check if the script file has been updated on GitHub, thus the `Update this script` button is always visible once you have installed a script, not only when an update is available (hope to build this feature in the future).
|
||||
|
||||
I would love to include your contribution in the script library. If you have a script of your own that you would like to share with the community, please open a [PR](https://github.com/zsviczian/obsidian-excalidraw-plugin/pulls) on GitHub. Be sure to include the following in your pull request
|
||||
- The [script file](https://github.com/zsviczian/obsidian-excalidraw-plugin/tree/master/ea-scripts) with a self explanetory name. The name of the file will be the name of the script in the Command Palette.
|
||||
@@ -243,7 +243,7 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/OCR%20-%20Optical%20Character%20Recognition.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/OCR%20-%20Optical%20Character%20Recognition.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">REQUIRES EXCALIDRAW 1.5.15<br>The script will 1) send the selected image file to [taskbone.com](https://taskbone.com) to exctract the text from the image, and 2) will add the text to your drawing as a text element.<br><mark>⚠ Note that you will need to manually paste your token into the script after the first run! ⚠</mark><br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-ocr.jpg'><br><iframe width="560" height="315" src="https://www.youtube.com/embed/W2NMzR8s4eE" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></td></tr></table>
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/OCR%20-%20Optical%20Character%20Recognition.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">REQUIRES EXCALIDRAW 1.5.15<br>The script will 1) send the selected image file to [taskbone.com](https://taskbone.com) to extract the text from the image, and 2) will add the text to your drawing as a text element.<br><mark>⚠ Note that you will need to manually paste your token into the script after the first run! ⚠</mark><br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-ocr.jpg'><br><iframe width="560" height="315" src="https://www.youtube.com/embed/W2NMzR8s4eE" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></td></tr></table>
|
||||
|
||||
## Organic Line
|
||||
```excalidraw-script-install
|
||||
|
||||
@@ -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 })
|
||||
|
||||
})();
|
||||
@@ -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 |
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
With This Script it is possible to make boolean Operations on Shapes.
|
||||
The style of the resulting shape will be the style of the highest ranking Element that was used.
|
||||
The ranking of the elemtns is based on their background. The "denser" the background, the higher the ranking (the order of backgroundstyles is shown below). If they have the same background the opacity will decide. If thats also the same its decided by the order they were created.
|
||||
The ranking is also important for the diffrence operation, so a tranparent object for example will cut a hole into a solid object.
|
||||
The ranking of the elements is based on their background. The "denser" the background, the higher the ranking (the order of backgroundstyles is shown below). If they have the same background the opacity will decide. If thats also the same its decided by the order they were created.
|
||||
The ranking is also important for the difference operation, so a transparent object for example will cut a hole into a solid object.
|
||||

|
||||

|
||||
|
||||
@@ -29,7 +29,7 @@ if(elements.length === 0) {
|
||||
}
|
||||
|
||||
const PolyBool = ea.getPolyBool();
|
||||
const polyboolAction = await utils.suggester(["union (a + b)", "intersect (a && b)", "diffrence (a - b)", "reversed diffrence (b - a)", "xor"], [
|
||||
const polyboolAction = await utils.suggester(["union (a + b)", "intersect (a && b)", "difference (a - b)", "reversed difference (b - a)", "xor"], [
|
||||
PolyBool.union, PolyBool.intersect, PolyBool.difference, PolyBool.differenceRev, PolyBool.xor
|
||||
], "What would you like todo with the object");
|
||||
|
||||
|
||||
15
ea-scripts/Custom Zoom.md
Normal file
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
You can set a custom zoom level with this script. This allows you to set a zoom level below 10% or set the zoom level to a specific value. Note however, that Excalidraw has a bug under 10% zoom, and a phantom copy of your image may appear on screen. If this happens, increase the zoom and the phantom should disappear, if it doesn't then close and open the drawing.
|
||||
|
||||
```js*/
|
||||
const api = ea.getExcalidrawAPI();
|
||||
const appState = api.getAppState();
|
||||
const zoomStr = await utils.inputPrompt("Zoom [%]",null,`${appState.zoom.value*100}%`);
|
||||
if(!zoomStr) return;
|
||||
const zoomNum = parseFloat(zoomStr.match(/^\d*/)[0]);
|
||||
if(isNaN(zoomNum)) {
|
||||
new Notice("You must provide a number");
|
||||
return;
|
||||
}
|
||||
|
||||
ea.getExcalidrawAPI().updateScene({appState:{zoom:{value: zoomNum/100 }}});
|
||||
1
ea-scripts/Custom Zoom.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-scan-search"><g stroke-width="2"><path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><circle cx="12" cy="12" r="3"/><path d="m16 16-1.9-1.9"/></g></svg>
|
||||
|
After Width: | Height: | Size: 444 B |
@@ -9,7 +9,7 @@ Select some elements in the scene. The script will take these elements and move
|
||||
|
||||
```javascript
|
||||
*/
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.9.19")) {
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.0.25")) {
|
||||
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
|
||||
return;
|
||||
}
|
||||
@@ -17,12 +17,12 @@ if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.9.19")) {
|
||||
// -------------------------------
|
||||
// Utility variables and functions
|
||||
// -------------------------------
|
||||
const excalidrawTemplate = app.metadataCache.getFirstLinkpathDest(ea.plugin.settings.templateFilePath,"");
|
||||
const excalidrawTemplates = ea.getListOfTemplateFiles();
|
||||
if(typeof window.ExcalidrawDeconstructElements === "undefined") {
|
||||
window.ExcalidrawDeconstructElements = {
|
||||
openDeconstructedImage: true,
|
||||
templatePath: excalidrawTemplate?.path??""
|
||||
};
|
||||
window.ExcalidrawDeconstructElements = {
|
||||
openDeconstructedImage: true,
|
||||
templatePath: excalidrawTemplates?.[0].path??""
|
||||
};
|
||||
}
|
||||
|
||||
const splitFolderAndFilename = (filepath) => {
|
||||
@@ -36,20 +36,30 @@ const splitFolderAndFilename = (filepath) => {
|
||||
let settings = ea.getScriptSettings();
|
||||
//set default values on first run
|
||||
if(!settings["Templates"]) {
|
||||
settings = {
|
||||
"Templates" : {
|
||||
value: "",
|
||||
settings = {
|
||||
"Templates" : {
|
||||
value: "",
|
||||
description: "Comma-separated list of template filepaths"
|
||||
}
|
||||
};
|
||||
await ea.setScriptSettings(settings);
|
||||
}
|
||||
};
|
||||
await ea.setScriptSettings(settings);
|
||||
}
|
||||
|
||||
if(!settings["Default file name"]) {
|
||||
settings["Default file name"] = {
|
||||
value: "deconstructed",
|
||||
description: "The default filename to use when deconstructing elements."
|
||||
};
|
||||
await ea.setScriptSettings(settings);
|
||||
}
|
||||
|
||||
const DEFAULT_FILENAME = settings["Default file name"].value;
|
||||
|
||||
const templates = settings["Templates"]
|
||||
.value
|
||||
.split(",")
|
||||
.map(p=>app.metadataCache.getFirstLinkpathDest(p.trim(),""))
|
||||
.concat(excalidrawTemplate)
|
||||
.concat(excalidrawTemplates)
|
||||
.filter(f=>Boolean(f))
|
||||
.sort((a,b) => a.basename.localeCompare(b.basename));
|
||||
|
||||
@@ -70,31 +80,31 @@ ea.getElements().filter(el=>el.type==="image").forEach(el=>{
|
||||
const img = ea.targetView.excalidrawData.getFile(el.fileId);
|
||||
const path = (img?.linkParts?.original)??(img?.file?.path);
|
||||
if(img && path) {
|
||||
ea.imagesDict[el.fileId] = {
|
||||
mimeType: img.mimeType,
|
||||
id: el.fileId,
|
||||
dataURL: img.img,
|
||||
created: img.mtime,
|
||||
file: path,
|
||||
hasSVGwithBitmap: img.isSVGwithBitmap,
|
||||
latex: null,
|
||||
};
|
||||
return;
|
||||
}
|
||||
const equation = ea.targetView.excalidrawData.getEquation(el.fileId);
|
||||
eqImg = ea.targetView.getScene()?.files[el.fileId]
|
||||
if(equation && eqImg) {
|
||||
ea.imagesDict[el.fileId] = {
|
||||
mimeType: eqImg.mimeType,
|
||||
id: el.fileId,
|
||||
dataURL: eqImg.dataURL,
|
||||
created: eqImg.created,
|
||||
file: null,
|
||||
hasSVGwithBitmap: null,
|
||||
latex: equation.latex,
|
||||
};
|
||||
return;
|
||||
}
|
||||
mimeType: img.mimeType,
|
||||
id: el.fileId,
|
||||
dataURL: img.img,
|
||||
created: img.mtime,
|
||||
file: path,
|
||||
hasSVGwithBitmap: img.isSVGwithBitmap,
|
||||
latex: null,
|
||||
};
|
||||
return;
|
||||
}
|
||||
const equation = ea.targetView.excalidrawData.getEquation(el.fileId);
|
||||
eqImg = ea.targetView.getScene()?.files[el.fileId]
|
||||
if(equation && eqImg) {
|
||||
ea.imagesDict[el.fileId] = {
|
||||
mimeType: eqImg.mimeType,
|
||||
id: el.fileId,
|
||||
dataURL: eqImg.dataURL,
|
||||
created: eqImg.created,
|
||||
file: null,
|
||||
hasSVGwithBitmap: null,
|
||||
latex: equation.latex,
|
||||
};
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -144,7 +154,7 @@ const customControls = (container) => {
|
||||
const path = await utils.inputPrompt(
|
||||
"Filename for new file",
|
||||
"Filename",
|
||||
await ea.getAttachmentFilepath("deconstructed"),
|
||||
await ea.getAttachmentFilepath(DEFAULT_FILENAME),
|
||||
actionButtons,
|
||||
2,
|
||||
false,
|
||||
@@ -177,8 +187,14 @@ if(!f || !ea.isExcalidrawFile(f)) {
|
||||
new Notice("Something went wrong");
|
||||
return;
|
||||
}
|
||||
|
||||
let padding = parseFloat(app.metadataCache.getCache(f.path)?.frontmatter["excalidraw-export-padding"]);
|
||||
if(isNaN(padding)) {
|
||||
padding = ea.plugin.settings.exportPaddingSVG;
|
||||
}
|
||||
|
||||
ea.getElements().forEach(el=>el.isDeleted = true);
|
||||
await ea.addImage(bb.topX,bb.topY,f,false, shouldAnchor);
|
||||
await ea.addImage(bb.topX-padding,bb.topY-padding,f,false, shouldAnchor);
|
||||
await ea.addElementsToView(false, true, true);
|
||||
ea.getExcalidrawAPI().history.clear();
|
||||
if(!window.ExcalidrawDeconstructElements.openDeconstructedImage) {
|
||||
|
||||
@@ -26,6 +26,10 @@ const outputTypes = {
|
||||
"image-gen": {
|
||||
instruction: "Return a single message with the generated image prompt in a codeblock",
|
||||
blocktype: "image"
|
||||
},
|
||||
"image-gen-silent": {
|
||||
instruction: "Return a single message with the generated image prompt in a codeblock",
|
||||
blocktype: "image-silent"
|
||||
},
|
||||
"image-edit": {
|
||||
instruction: "",
|
||||
@@ -49,6 +53,12 @@ const systemPrompts = {
|
||||
type: "svg",
|
||||
help: "Convert text prompts into simple icons inserted as Excalidraw elements. Expect only a text prompt. Experimental and may not produce good drawings."
|
||||
},
|
||||
|
||||
"Create a stick figure": {
|
||||
prompt: "You will receive a prompt from the user. Your task involves drawing a simple stick figure or a scene involving a few stick figures based on the user's prompt. Create the stickfigure based on the following style description. DO NOT add any detail, just use it AS-IS: Create a simple stick figure character with a large round head and a face in the style of sketchy caricatures. The stick figure should have a rudimentary body composed of straight lines representing the arms and legs. Hands and toes should be should be represented with round shapes, do not add details such as fingers or toes. Use fine lines, smooth curves, rounded shapes. The stick figure should retain a playful and childlike simplicity, reminiscent of a doodle someone might draw on the corner of a notebook page. Create a black and white drawing, a hand-drawn figure on white background.",
|
||||
type: "image-gen",
|
||||
help: "Send only the text prompt to OpenAI. Provide a detailed description; OpenAI will enrich your prompt automatically. To avoid it, start your prompt like this 'DO NOT add any detail, just use it AS-IS:'"
|
||||
},
|
||||
"Edit an image": {
|
||||
prompt: null,
|
||||
type: "image-edit",
|
||||
@@ -69,6 +79,11 @@ const systemPrompts = {
|
||||
type: "image-gen",
|
||||
help: "ExcaliAI will create an image prompt to illustrate your text input - a quote - with GPT, then generate an image using Dall-e. In case you include the Author's name, GPT will try to generate an image that in some way references the Author."
|
||||
},
|
||||
"Generate 4 icon-variants based on input image": {
|
||||
prompt: "Given a simple sketch and an optional text prompt from the user, your task is to generate a descriptive narrative tailored for effective image generation, capturing the style of the sketch. Utilize the text prompt to guide the description. Your objective is to instruct DALL-E to create a collage of four minimalist black and white hand-drawn pencil sketches in a 2x2 matrix format. Each sketch should convert the user's sketch into simple artistic SVG icons with transparent backgrounds. Ensure the resulting images remain text-free, maintaining a minimalist, easy-to-understand style, and omit framing borders. Only include a pencil in the drawing if it is explicitely metioned in the user prompt or included in the sketch.",
|
||||
type: "image-gen-silent",
|
||||
help: "Generate a collage of 4 icons based on the drawing using ChatGPT-Vision and Dall-e. You may provide a contextual text-prompt to improve accuracy of interpretation."
|
||||
},
|
||||
"Visual brainstorm": {
|
||||
prompt: "Your objective is to interpret a screenshot of a whiteboard, creating an image aimed at sparking further thoughts on the subject. The whiteboard will present diverse ideas about a specific topic. Your generated image should achieve one of two purposes: highlighting concepts that challenge, dispute, or contradict the whiteboard content, or introducing ideas that expand, complement, or enrich the user's thinking. You have the option to include multiple tiles in the resulting image, resembling a sequence akin to a comic strip. Ensure that the image remains devoid of text.",
|
||||
type: "image-gen",
|
||||
@@ -77,7 +92,7 @@ const systemPrompts = {
|
||||
"Wireframe to code": {
|
||||
prompt: `You are an expert tailwind developer. A user will provide you with a low-fidelity wireframe of an application and you will return a single html file that uses tailwind to create the website. Use creative license to make the application more fleshed out. Write the necessary javascript code. If you need to insert an image, use placehold.co to create a placeholder image.`,
|
||||
type: "html",
|
||||
help: "Use GPT Visions to interpret the wireframe and generate a web application. You may copy the resulting code from the active embeddable's top left menu."
|
||||
help: "Use GPT Visions to interpret the wireframe and generate a web application. YOu may copy the resulting code from the active embeddable's top left menu."
|
||||
},
|
||||
}
|
||||
|
||||
@@ -319,7 +334,7 @@ const setMermaidDataToStorage = (mermaidDefinition) => {
|
||||
// --------------------------------------
|
||||
// Submit Prompt
|
||||
// --------------------------------------
|
||||
const generateImage = async(text, spinnerID, bb) => {
|
||||
const generateImage = async(text, spinnerID, bb, silent=false) => {
|
||||
const requestObject = {
|
||||
text,
|
||||
imageGenerationProperties: {
|
||||
@@ -342,7 +357,7 @@ const generateImage = async(text, spinnerID, bb) => {
|
||||
const imageID = await ea.addImage(spinner.x, spinner.y, result.json.data[0].url);
|
||||
const imageEl = ea.getElement(imageID);
|
||||
const revisedPrompt = result.json.data[0].revised_prompt;
|
||||
if(revisedPrompt) {
|
||||
if(revisedPrompt && !silent) {
|
||||
ea.style.fontSize = 16;
|
||||
const rectID = ea.addText(imageEl.x+15, imageEl.y + imageEl.height + 50, revisedPrompt, {
|
||||
width: imageEl.width-30,
|
||||
@@ -356,6 +371,7 @@ const generateImage = async(text, spinnerID, bb) => {
|
||||
}
|
||||
|
||||
await ea.addElementsToView(false, true, true);
|
||||
if(silent) return;
|
||||
ea.getExcalidrawAPI().setToast({
|
||||
message: IMAGE_WARNING,
|
||||
duration: 15000,
|
||||
@@ -371,7 +387,7 @@ const run = async (text) => {
|
||||
|
||||
const systemPrompt = systemPrompts[agentTask];
|
||||
const outputType = outputTypes[systemPrompt.type];
|
||||
const isImageGenRequest = outputType.blocktype === "image";
|
||||
const isImageGenRequest = outputType.blocktype === "image" || outputType.blocktype === "image-silent";
|
||||
const isImageEditRequest = systemPrompt.type === "image-edit";
|
||||
|
||||
if(isImageEditRequest) {
|
||||
@@ -411,7 +427,7 @@ const run = async (text) => {
|
||||
|
||||
const requestObject = isImageEditRequest
|
||||
? {
|
||||
...imageDataURL ? {image: imageDataURL} : {},
|
||||
...imageDataURL ? {image: {url: imageDataURL}} : {},
|
||||
...(text && text.trim() !== "") ? {text} : {},
|
||||
imageGenerationProperties: {
|
||||
size: imageSize,
|
||||
@@ -421,7 +437,7 @@ const run = async (text) => {
|
||||
},
|
||||
}
|
||||
: {
|
||||
...imageDataURL ? {image: imageDataURL} : {},
|
||||
...imageDataURL ? {image: {url: imageDataURL}} : {},
|
||||
...(text && text.trim() !== "") ? {text} : {},
|
||||
systemPrompt: systemPrompt.prompt,
|
||||
instruction: outputType.instruction,
|
||||
@@ -473,7 +489,7 @@ const run = async (text) => {
|
||||
}
|
||||
|
||||
if(isImageGenRequest) {
|
||||
generateImage(content,spinnerID,bb);
|
||||
generateImage(content,spinnerID,bb,outputType.blocktype === "image-silent");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -519,7 +535,7 @@ const run = async (text) => {
|
||||
// --------------------------------------
|
||||
let previewDiv;
|
||||
const fragWithHTML = (html) => createFragment((frag) => (frag.createDiv().innerHTML = html));
|
||||
const isImageGenerationTask = () => systemPrompts[agentTask].type === "image-gen" || systemPrompts[agentTask].type === "image-edit";
|
||||
const isImageGenerationTask = () => systemPrompts[agentTask].type === "image-gen" || systemPrompts[agentTask].type === "image-gen-silent" || systemPrompts[agentTask].type === "image-edit";
|
||||
const addPreviewImage = () => {
|
||||
if(!previewDiv) return;
|
||||
previewDiv.empty();
|
||||
|
||||
259
ea-scripts/Excalidraw Writing Machine.md
Normal file
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
Generates a hierarchical Markdown document out of a visual layout of an article.
|
||||
Watch this video to understand how the script is intended to work:
|
||||

|
||||
You can download the sample Obsidian Templater file from [here](https://gist.github.com/zsviczian/bf49d4b2d401f5749aaf8c2fa8a513d9)
|
||||
You can download the demo PDF document showcased in the video from [here](https://zsviczian.github.io/DemoArticle-AtomicHabits.pdf)
|
||||
|
||||
```js*/
|
||||
const selectedElements = ea.getViewSelectedElements();
|
||||
if (selectedElements.length !== 1 || selectedElements[0].type === "arrow") {
|
||||
new Notice("Select a single element that is not an arrow and not a frame");
|
||||
return;
|
||||
}
|
||||
|
||||
const visited = new Set(); // Avoiding recursive infinite loops
|
||||
delete window.ewm;
|
||||
|
||||
await ea.targetView.save();
|
||||
|
||||
//------------------
|
||||
// Load Settings
|
||||
//------------------
|
||||
|
||||
let settings = ea.getScriptSettings();
|
||||
//set default values on first run
|
||||
let didSettingsChange = false;
|
||||
if(!settings["Template path"]) {
|
||||
settings = {
|
||||
"Template path" : {
|
||||
value: "",
|
||||
description: "The template file path that will receive the concatenated text. If the file includes <<<REPLACE ME>>> then it will be replaced with the generated text, if <<<REPLACE ME>>> is not present in the file the hierarchical markdown generated from the diagram will be added to the end of the template."
|
||||
},
|
||||
"ZK '# Summary' section": {
|
||||
value: "Summary",
|
||||
description: "The section in your visual zettelkasten file that contains the short written summary of the idea. This is the text that will be included in the hierarchical markdown file if visual ZK cards are included in your flow"
|
||||
},
|
||||
"ZK '# Source' section": {
|
||||
value: "Source",
|
||||
description: "The section in your visual zettelkasten file that contains the reference to your source. If present in the file, this text will be included in the output file as a reference"
|
||||
},
|
||||
"Embed image links": {
|
||||
value: true,
|
||||
description: "Should the resulting markdown document include the ![[embedded images]]?"
|
||||
}
|
||||
};
|
||||
didSettingsChange = true;
|
||||
}
|
||||
|
||||
if(!settings["Generate "]) {
|
||||
settings["Generate "] = {
|
||||
value: true,
|
||||
description: "If you turn this off the script will generate ![[wikilinks]] for images"
|
||||
}
|
||||
didSettingsChange = true;
|
||||
}
|
||||
|
||||
if(didSettingsChange) {
|
||||
await ea.setScriptSettings(settings);
|
||||
}
|
||||
|
||||
const ZK_SOURCE = settings["ZK '# Source' section"].value;
|
||||
const ZK_SECTION = settings["ZK '# Summary' section"].value;
|
||||
const INCLUDE_IMG_LINK = settings["Embed image links"].value;
|
||||
const MARKDOWN_LINKS = settings["Generate "].value;
|
||||
let templatePath = settings["Template path"].value;
|
||||
|
||||
//------------------
|
||||
// Select template file
|
||||
//------------------
|
||||
|
||||
const MSG = "Select another file"
|
||||
let selection = MSG;
|
||||
if(templatePath && app.vault.getAbstractFileByPath(templatePath)) {
|
||||
selection = await utils.suggester([templatePath, MSG],[templatePath, MSG], "Use previous template or select another?");
|
||||
if(!selection) {
|
||||
new Notice("process aborted");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(selection === MSG) {
|
||||
const files = app.vault.getMarkdownFiles().map(f=>f.path);
|
||||
selection = await utils.suggester(files,files,"Select the template to use. ESC to not use a tempalte");
|
||||
}
|
||||
|
||||
if(selection && selection !== templatePath) {
|
||||
settings["Template path"].value = selection;
|
||||
await ea.setScriptSettings(settings);
|
||||
}
|
||||
|
||||
templatePath = selection;
|
||||
|
||||
//------------------
|
||||
// supporting functions
|
||||
//------------------
|
||||
function getNextElementFollowingArrow(el, arrow) {
|
||||
if (arrow.startBinding?.elementId === el.id) {
|
||||
return ea.getViewElements().find(x => x.id === arrow.endBinding?.elementId);
|
||||
}
|
||||
if (arrow.endBinding?.elementId === el.id) {
|
||||
return ea.getViewElements().find(x => x.id === arrow.startBinding?.elementId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getImageLink(f) {
|
||||
if(MARKDOWN_LINKS) {
|
||||
return `})`;
|
||||
}
|
||||
return `![[${f.path}|${f.basename}]]`;
|
||||
}
|
||||
|
||||
function getBoundText(el) {
|
||||
const textId = el.boundElements?.find(x => x.type === "text")?.id;
|
||||
const text = ea.getViewElements().find(x => x.id === textId)?.originalText;
|
||||
return text ? text + "\n" : "";
|
||||
}
|
||||
|
||||
async function getSectionText(file, section) {
|
||||
const content = await app.vault.cachedRead(file);
|
||||
const metadata = app.metadataCache.getFileCache(file);
|
||||
|
||||
if (!metadata || !metadata.headings) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const targetHeading = metadata.headings.find(h => h.heading === section);
|
||||
if (!targetHeading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const startPos = targetHeading.position.start.offset;
|
||||
let endPos = content.length;
|
||||
|
||||
const nextHeading = metadata.headings.find(h => h.position.start.offset > startPos);
|
||||
if (nextHeading) {
|
||||
endPos = nextHeading.position.start.offset;
|
||||
}
|
||||
|
||||
let sectionContent = content.slice(startPos, endPos).trim();
|
||||
sectionContent = sectionContent.substring(sectionContent.indexOf('\n') + 1).trim();
|
||||
|
||||
// Remove Markdown comments enclosed in %%
|
||||
sectionContent = sectionContent.replace(/%%[\s\S]*?%%/g, '').trim();
|
||||
return sectionContent;
|
||||
}
|
||||
|
||||
async function getBlockText(file, blockref) {
|
||||
const content = await app.vault.cachedRead(file);
|
||||
const blockPattern = new RegExp(`\\^${blockref}\\b`, 'g');
|
||||
let blockPosition = content.search(blockPattern);
|
||||
|
||||
if (blockPosition === -1) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const startPos = content.lastIndexOf('\n', blockPosition) + 1;
|
||||
let endPos = content.indexOf('\n', blockPosition);
|
||||
|
||||
if (endPos === -1) {
|
||||
endPos = content.length;
|
||||
} else {
|
||||
const nextBlockOrHeading = content.slice(endPos).search(/(^# |^\^|\n)/gm);
|
||||
if (nextBlockOrHeading !== -1) {
|
||||
endPos += nextBlockOrHeading;
|
||||
} else {
|
||||
endPos = content.length;
|
||||
}
|
||||
}
|
||||
let blockContent = content.slice(startPos, endPos).trim();
|
||||
blockContent = blockContent.replace(blockPattern, '').trim();
|
||||
blockContent = blockContent.replace(/%%[\s\S]*?%%/g, '').trim();
|
||||
return blockContent;
|
||||
}
|
||||
|
||||
async function getElementText(el) {
|
||||
if (el.type === "text") {
|
||||
return el.originalText;
|
||||
}
|
||||
if (el.type === "image") {
|
||||
const f = ea.getViewFileForImageElement(el);
|
||||
if(!ea.isExcalidrawFile(f)) return f.name + (INCLUDE_IMG_LINK ? `\n${getImageLink(f)}\n` : "");
|
||||
let source = await getSectionText(f, ZK_SOURCE);
|
||||
source = source ? ` (source:: ${source})` : "";
|
||||
const summary = await getSectionText(f, ZK_SECTION) ;
|
||||
|
||||
if(summary) return (INCLUDE_IMG_LINK ? `${getImageLink(f)}\n${summary + source}` : summary + source) + "\n";
|
||||
return f.name + (INCLUDE_IMG_LINK ? `\n${getImageLink(f)}\n` : "");
|
||||
}
|
||||
if (el.type === "embeddable") {
|
||||
const linkWithRef = el.link.match(/\[\[([^\]]*)]]/)?.[1];
|
||||
if(!linkWithRef) return "";
|
||||
const path = linkWithRef.split("#")[0];
|
||||
const f = app.metadataCache.getFirstLinkpathDest(path, ea.targetView.file.path);
|
||||
if(!f) return "";
|
||||
if(f.extension !== "md") return f.name;
|
||||
const ref = linkWithRef.split("#")[1];
|
||||
if(!ref) return await app.vault.read(f);
|
||||
if(ref.startsWith("^")) {
|
||||
return await getBlockText(f, ref.substring(1));
|
||||
} else {
|
||||
return await getSectionText(f, ref);
|
||||
}
|
||||
}
|
||||
return getBoundText(el);
|
||||
}
|
||||
|
||||
//------------------
|
||||
// Navigating the hierarchy
|
||||
//------------------
|
||||
|
||||
async function crawl(el, level, isFirst = false) {
|
||||
visited.add(el.id);
|
||||
|
||||
let result = await getElementText(el) + "\n";
|
||||
|
||||
// Process all arrows connected to this element
|
||||
const boundElementsData = el.boundElements.filter(x => x.type === "arrow");
|
||||
const isFork = boundElementsData.length > (isFirst ? 1 : 2);
|
||||
if(isFork) level++;
|
||||
|
||||
for(const bindingData of boundElementsData) {
|
||||
const arrow = ea.getViewElements().find(x=> x.id === bindingData.id);
|
||||
const nextEl = getNextElementFollowingArrow(el, arrow);
|
||||
if (nextEl && !visited.has(nextEl.id)) {
|
||||
if(isFork) result += `\n${"#".repeat(level)} `;
|
||||
const arrowLabel = getBoundText(arrow);
|
||||
if (arrowLabel) {
|
||||
// If the arrow has a label, add it as an additional level
|
||||
result += arrowLabel + "\n";
|
||||
result += await crawl(nextEl, level);
|
||||
} else {
|
||||
// If no label, continue to the next element
|
||||
result += await crawl(nextEl, level);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
window.ewm = "## " + await crawl(selectedElements[0], 2, true);
|
||||
|
||||
const outputPath = await ea.getAttachmentFilepath(`EWM - ${ea.targetView.file.name}.md`);
|
||||
let result = templatePath
|
||||
? await app.vault.read(app.vault.getAbstractFileByPath(templatePath))
|
||||
: "";
|
||||
|
||||
if(result.match("<<<REPLACE ME>>>")) {
|
||||
result = result.replaceAll("<<<REPLACE ME>>>",window.ewm);
|
||||
} else {
|
||||
result += window.ewm;
|
||||
}
|
||||
|
||||
const outfile = await app.vault.create(outputPath,result);
|
||||
|
||||
setTimeout(()=>{
|
||||
ea.openFileInNewOrAdjacentLeaf(outfile);
|
||||
}, 250);
|
||||
11
ea-scripts/Excalidraw Writing Machine.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke="CurrentColor" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-keyboard">
|
||||
<path stroke-width="2" d="M10 8h.01"/>
|
||||
<path stroke-width="2" d="M12 12h.01"/>
|
||||
<path stroke-width="2" d="M14 8h.01"/>
|
||||
<path stroke-width="2" d="M16 12h.01"/>
|
||||
<path stroke-width="2" d="M18 8h.01"/>
|
||||
<path stroke-width="2" d="M6 8h.01"/>
|
||||
<path stroke-width="2" d="M7 16h10"/>
|
||||
<path stroke-width="2" d="M8 12h.01"/>
|
||||
<path fill="none" stroke-width="2" d="M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 611 B |
@@ -464,7 +464,7 @@ const run = async (text) => {
|
||||
return;
|
||||
}
|
||||
|
||||
//exctract codeblock and display result
|
||||
//extract codeblock and display result
|
||||
let content = ea.extractCodeBlocks(result.json.choices[0]?.message?.content)[0]?.data;
|
||||
|
||||
if(!content) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 })
|
||||
})();
|
||||
@@ -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 |
@@ -33,8 +33,20 @@ const invertColor = (color) => {
|
||||
}
|
||||
}
|
||||
|
||||
const invertPaletteColors = (palette) => Object.keys(palette).forEach(key => palette[key] = invertColor(palette[key]));
|
||||
Object.keys(colorPalette).forEach(key => invertPaletteColors(colorPalette[key]));
|
||||
function invertColorsRecursively(obj) {
|
||||
if (typeof obj === 'string') {
|
||||
return invertColor(obj);
|
||||
} else if (Array.isArray(obj)) {
|
||||
return obj.map(item => invertColorsRecursively(item));
|
||||
} else if (typeof obj === 'object' && obj !== null) {
|
||||
const result = {};
|
||||
Object.keys(obj).forEach(key => result[key] = invertColorsRecursively(obj[key]));
|
||||
return result;
|
||||
} else {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
colorPalette = invertColorsRecursively(colorPalette);
|
||||
|
||||
ea.copyViewElementsToEAforEditing(ea.getViewElements());
|
||||
ea.getElements().forEach(el=>{
|
||||
|
||||
@@ -58,9 +58,10 @@ Open the script you are interested in and save it to your Obsidian Vault includi
|
||||
|[Mindmap connector](Mindmap%20connector.md)|This script creates mindmap like lines (only right side and down available currently) for selected elements. The line will start according to the creation time of the elements. So you should create the header element first.||[@xllowl](https://github.com/xllowl)|
|
||||
|[Modify background color opacity](Modify%20background%20color%20opacity.md)|This script changes the opacity of the background color of the selected boxes. The default background color in Excalidraw is so dark that the text is hard to read. You can lighten the color a bit by setting transparency. And you can tweak the transparency over and over again until you're happy with it. Although excalidraw has the opacity option in its native property Settings, it also changes the transparency of the border. Use this script to change only the opacity of the background color without affecting the border.||[@1-2-3](https://github.com/1-2-3)|
|
||||
|[Normalize Selected Arrows](Normalize%20Selected%20Arrows.md)|This script will reset the start and end positions of the selected arrows. The arrow will point to the center of the connected box and will have a gap of 8px from the box.||[@1-2-3](https://github.com/1-2-3)|
|
||||
|[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 exctract the text from the image, and 2) will add the text to your drawing as a text element.||[@zsviczian](https://github.com/zsviczian)|
|
||||
|[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.||[@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.||[@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.||[@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.||[@firai](https://github.com/firai)|
|
||||
|[Reverse arrows](Reverse%20arrows.md)|Reverse the direction of **arrows** within the scope of selected elements.||[@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.||[@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.||[@zsviczian](https://github.com/zsviczian)|
|
||||
|
||||
32
ea-scripts/Reset LaTeX Size.md
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
/*
|
||||

|
||||
|
||||
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);
|
||||
1
ea-scripts/Reset LaTeX Size.svg
Normal 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 |
@@ -2,12 +2,12 @@
|
||||
|
||||

|
||||
|
||||
This script allows users to streamline their Obsidian-Excalidraw workflows by enabling the selection of elements based on similar properties. Users 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. Users 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. This script enhances control and efficiency in your Excalidraw experience.
|
||||
This script enables the selection of elements based on matching properties. Select the attributes (such as stroke color, fill style, font family, etc) that should match for selection. It's perfect for large scenes where manual selection of elements would be cumbersome. You can either run the script to select matching elements across the entire scene, or define a specific group of elements to apply the selection criteria to.
|
||||
|
||||
```js */
|
||||
|
||||
let config = window.ExcalidrawSelectConfig;
|
||||
config = config && (Date.now() - config.timestamp < 60000) ? config : null;
|
||||
config = Boolean(config) && (Date.now() - config.timestamp < 60000) ? config : null;
|
||||
|
||||
let elements = ea.getViewSelectedElements();
|
||||
if(!config && (elements.length !==1)) {
|
||||
@@ -19,7 +19,7 @@ if(!config && (elements.length !==1)) {
|
||||
}
|
||||
}
|
||||
|
||||
const {angle, backgroundColor, fillStyle, fontFamily, fontSize, height, width, opacity, roughness, roundness, strokeColor, strokeStyle, strokeWidth, type, startArrowhead, endArrowhead} = ea.getViewSelectedElement();
|
||||
const {angle, backgroundColor, fillStyle, fontFamily, fontSize, height, width, opacity, roughness, roundness, strokeColor, strokeStyle, strokeWidth, type, startArrowhead, endArrowhead, fileId} = ea.getViewSelectedElement();
|
||||
|
||||
const fragWithHTML = (html) => createFragment((frag) => (frag.createDiv().innerHTML = html));
|
||||
|
||||
@@ -27,14 +27,14 @@ const fragWithHTML = (html) => createFragment((frag) => (frag.createDiv().innerH
|
||||
// RUN
|
||||
//--------------------------
|
||||
const run = () => {
|
||||
selectedElements = ea.getViewElements().filter(el=>
|
||||
selectedElements = elements.filter(el=>
|
||||
((typeof config.angle === "undefined") || (el.angle === config.angle)) &&
|
||||
((typeof config.backgroundColor === "undefined") || (el.backgroundColor === config.backgroundColor)) &&
|
||||
((typeof config.fillStyle === "undefined") || (el.fillStyle === config.fillStyle)) &&
|
||||
((typeof config.fontFamily === "undefined") || (el.fontFamily === config.fontFamily)) &&
|
||||
((typeof config.fontSize === "undefined") || (el.fontSize === config.fontSize)) &&
|
||||
((typeof config.height === "undefined") || Math.abs(el.height - config.height) < 0.01) &&
|
||||
((typeof config.width === "undefined") || Math.abs(el.width - config.width) < 0.01) &&
|
||||
((typeof config.height === "undefined") || Math.abs(el.height - config.height) < 0.01) &&
|
||||
((typeof config.width === "undefined") || Math.abs(el.width - config.width) < 0.01) &&
|
||||
((typeof config.opacity === "undefined") || (el.opacity === config.opacity)) &&
|
||||
((typeof config.roughness === "undefined") || (el.roughness === config.roughness)) &&
|
||||
((typeof config.roundness === "undefined") || (el.roundness === config.roundness)) &&
|
||||
@@ -43,7 +43,8 @@ const run = () => {
|
||||
((typeof config.strokeWidth === "undefined") || (el.strokeWidth === config.strokeWidth)) &&
|
||||
((typeof config.type === "undefined") || (el.type === config.type)) &&
|
||||
((typeof config.startArrowhead === "undefined") || (el.startArrowhead === config.startArrowhead)) &&
|
||||
((typeof config.endArrowhead === "undefined") || (el.endArrowhead === config.endArrowhead))
|
||||
((typeof config.endArrowhead === "undefined") || (el.endArrowhead === config.endArrowhead)) &&
|
||||
((typeof config.fileId === "undefined") || (el.fileId === config.fileId))
|
||||
)
|
||||
ea.selectElementsInView(selectedElements);
|
||||
delete window.ExcalidrawSelectConfig;
|
||||
@@ -55,12 +56,12 @@ const run = () => {
|
||||
const showInstructions = () => {
|
||||
const instructionsModal = new ea.obsidian.Modal(app);
|
||||
instructionsModal.onOpen = () => {
|
||||
instructionsModal.contentEl.createEl("h2", {text: "Instructions"});
|
||||
instructionsModal.contentEl.createEl("h2", {text: "Instructions"});
|
||||
instructionsModal.contentEl.createEl("p", {text: "Step 1: Choose the attributes that you want the selected elements to match."});
|
||||
instructionsModal.contentEl.createEl("p", {text: "Step 2: Select an action:"});
|
||||
instructionsModal.contentEl.createEl("ul", {}, el => {
|
||||
el.createEl("li", {text: "Click 'RUN' to find matching elements throughout the entire scene."});
|
||||
el.createEl("li", {text: "Click 'SELECT' to first choose a specific group of elements. Then run the 'Select Similar Elements' script once more on that group within 1 minute."});
|
||||
el.createEl("li", {text: "Click 'SELECT' to 1) first choose a specific group of elements in the scene, then 2) run the 'Select Similar Elements' once more within 1 minute to apply the filter criteria only to that group of elements."});
|
||||
});
|
||||
instructionsModal.contentEl.createEl("p", {text: "Note: If you choose 'SELECT', make sure to click the 'Select Similar Elements' script again within 1 minute to apply your selection criteria to the group of elements you chose."});
|
||||
};
|
||||
@@ -70,14 +71,14 @@ const showInstructions = () => {
|
||||
const selectAttributesToCopy = () => {
|
||||
const configModal = new ea.obsidian.Modal(app);
|
||||
configModal.onOpen = () => {
|
||||
config = {};
|
||||
config = {};
|
||||
configModal.contentEl.createEl("h1", {text: "Select Similar Elements"});
|
||||
new ea.obsidian.Setting(configModal.contentEl)
|
||||
.setDesc("Choose the attributes you want the selected elements to match, then select an action.")
|
||||
.addButton(button => button
|
||||
.setButtonText("Instructions")
|
||||
.onClick(showInstructions)
|
||||
);
|
||||
new ea.obsidian.Setting(configModal.contentEl)
|
||||
.setDesc("Choose the attributes you want the selected elements to match, then select an action.")
|
||||
.addButton(button => button
|
||||
.setButtonText("Instructions")
|
||||
.onClick(showInstructions)
|
||||
);
|
||||
|
||||
|
||||
// Add Toggles for the rest of the attributes
|
||||
@@ -97,11 +98,12 @@ const selectAttributesToCopy = () => {
|
||||
{name: "End arrowhead", key: "endArrowhead"},
|
||||
{name: "Height", key: "height"},
|
||||
{name: "Width", key: "width"},
|
||||
{name: "ImageID", key: "fileId"},
|
||||
];
|
||||
|
||||
attributes.forEach(attr => {
|
||||
const attrValue = elements[0][attr.key];
|
||||
if(attrValue || (attr.key === "startArrowhead" && elements[0].type === "arrow") || (attr.key === "endArrowhead" && elements[0].type === "arrow")) {
|
||||
if((typeof attrValue !== "undefined" && attrValue !== null) || (attr.key === "startArrowhead" && elements[0].type === "arrow") || (attr.key === "endArrowhead" && elements[0].type === "arrow")) {
|
||||
let description = '';
|
||||
|
||||
switch(attr.key) {
|
||||
@@ -142,8 +144,6 @@ const selectAttributesToCopy = () => {
|
||||
description = `${attrValue}`;
|
||||
break;
|
||||
default:
|
||||
console.log(attr.key);
|
||||
console.log(attrValue);
|
||||
description = `${attrValue.charAt(0).toUpperCase() + attrValue.slice(1)}`;
|
||||
break;
|
||||
}
|
||||
@@ -165,7 +165,7 @@ const selectAttributesToCopy = () => {
|
||||
});
|
||||
|
||||
|
||||
//Add Toggle for the rest of the attirbutes. Organize attributes into a logical sequence or groups by adding
|
||||
//Add Toggle for the rest of the attributes. Organize attributes into a logical sequence or groups by adding
|
||||
//configModal.contentEl.createEl("h") or similar to the code
|
||||
|
||||
new ea.obsidian.Setting(configModal.contentEl)
|
||||
@@ -190,7 +190,9 @@ const selectAttributesToCopy = () => {
|
||||
|
||||
|
||||
configModal.onClose = () => {
|
||||
setTimeout(()=>delete configModal);
|
||||
setTimeout(()=>{
|
||||
delete configModal
|
||||
});
|
||||
}
|
||||
|
||||
configModal.open();
|
||||
|
||||
@@ -8,6 +8,46 @@ https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.h
|
||||
|
||||
```javascript
|
||||
*/
|
||||
if(ea.verifyMinimumPluginVersion && ea.verifyMinimumPluginVersion("2.4.0")) {
|
||||
|
||||
const api = ea.getExcalidrawAPI();
|
||||
let appState = api.getAppState();
|
||||
let gridFrequency = appState.gridStep;;
|
||||
|
||||
const customControls = (container) => {
|
||||
new ea.obsidian.Setting(container)
|
||||
.setName(`Major grid frequency`)
|
||||
.addDropdown(dropdown => {
|
||||
[2,3,4,5,6,7,8,9,10].forEach(grid=>dropdown.addOption(grid,grid));
|
||||
dropdown
|
||||
.setValue(gridFrequency)
|
||||
.onChange(value => {
|
||||
gridFrequency = value;
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const gridSize = parseInt(await utils.inputPrompt(
|
||||
"Grid size?",
|
||||
null,
|
||||
appState.GridSize?.toString()??"20",
|
||||
null,
|
||||
1,
|
||||
false,
|
||||
customControls
|
||||
));
|
||||
if(isNaN(gridSize)) return; //this is to avoid passing an illegal value to Excalidraw
|
||||
const gridStep = isNaN(parseInt(gridFrequency)) ? appState.gridStep : parseInt(gridFrequency);
|
||||
|
||||
api.updateScene({
|
||||
appState : {gridSize, gridStep, gridModeEnabled:true},
|
||||
commitToHistory:false
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------
|
||||
// old script
|
||||
// ----------------
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.9.19")) {
|
||||
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
|
||||
return;
|
||||
|
||||
@@ -9,7 +9,11 @@ https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.h
|
||||
```javascript
|
||||
*/
|
||||
let width = (ea.getViewSelectedElement().strokeWidth??1).toString();
|
||||
width = await utils.inputPrompt("Width?","number",width);
|
||||
width = parseFloat(await utils.inputPrompt("Width?","number",width));
|
||||
if(isNaN(width)) {
|
||||
new Notice("Invalid number");
|
||||
return;
|
||||
}
|
||||
const elements=ea.getViewSelectedElements();
|
||||
ea.copyViewElementsToEAforEditing(elements);
|
||||
ea.getElements().forEach((el)=>el.strokeWidth=width);
|
||||
|
||||
@@ -1,16 +1,27 @@
|
||||
/*
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/JwgtCrIVeEU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
# About the slideshow script
|
||||
The script will convert your drawing into a slideshow presentation.
|
||||

|
||||
|
||||

|
||||
The script will convert your drawing into a slideshow presentation.
|
||||
If you select an arrow or line element, the script will use that as the presentation path.
|
||||
If you select nothing, but the file has a hidden presentation path, the script will use that for determining the slide sequence.
|
||||
If there are frames, the script will use the frames for the presentation. Frames are played in alphabetical order of their titles.
|
||||
## Presentation options
|
||||
- If you select an arrow or line element, the script will use that as the presentation path.
|
||||
- If you select nothing, but the file has a hidden presentation path, the script will use that for determining the slide sequence.
|
||||
- If there are frames, the script will use the frames for the presentation. Frames are played in alphabetical order of their titles.
|
||||
# Keyboard shortcuts and modifier keys
|
||||
**Forward**: Arrow Down, Arrow Right, or SPACE
|
||||
**Backward**: Arrow Up, Arrow Left
|
||||
**Finish presentation**: Backspace, ESC (I had issues with ESC not working in full screen presentation mode on Mac)
|
||||
|
||||
**Run presentation in a window**: Hold down the ALT/OPT modifier key when clicking the presentation script button
|
||||
**Continue presentation**: Hold down SHIFT when clicking the presentation script button. (The feature also works in combination with the ALT/OPT modifier to start the presentation in a window). The feature will only resume while you are within the same Obsidian session (i.e. if you restart Obsidian, slideshow will no longer remember where you were). I have two use cases in mind for this feature:
|
||||
1) When you are designing your presentation you may want to test how a slide looks. Using this feature you can get back to where you left off by starting the presentation with SHIFT.
|
||||
2) During presentation you may want to exit presentation mode to show something additional to your audience. You stop the presentation, show the additional thing you wanted, now you want to continue from where you left off. Hold down SHIFT when clicking the slideshow button.
|
||||
|
||||
```javascript
|
||||
*/
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.9.23")) {
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.1.7")) {
|
||||
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
|
||||
return;
|
||||
}
|
||||
@@ -20,7 +31,9 @@ const hostView = hostLeaf.view;
|
||||
const statusBarElement = document.querySelector("div.status-bar");
|
||||
const ctrlKey = ea.targetView.modifierKeyDown.ctrlKey || ea.targetView.modifierKeyDown.metaKey;
|
||||
const altKey = ea.targetView.modifierKeyDown.altKey || ctrlKey;
|
||||
|
||||
const shiftKey = ea.targetView.modifierKeyDown.shiftKey;
|
||||
const shouldStartWithLastSlide = shiftKey && window.ExcalidrawSlideshow &&
|
||||
(window.ExcalidrawSlideshow.script === utils.scriptFile.path) && (typeof window.ExcalidrawSlideshow.slide === "number")
|
||||
//-------------------------------
|
||||
//constants
|
||||
//-------------------------------
|
||||
@@ -28,7 +41,7 @@ const TRANSITION_STEP_COUNT = 100;
|
||||
const TRANSITION_DELAY = 1000; //maximum time for transition between slides in milliseconds
|
||||
const FRAME_SLEEP = 1; //milliseconds
|
||||
const EDIT_ZOOMOUT = 0.7; //70% of original slide zoom, set to a value between 1 and 0
|
||||
const FADE_LEVEL = 0.15; //opacity of the slideshow controls after fade delay (value between 0 and 1)
|
||||
const FADE_LEVEL = 0.1; //opacity of the slideshow controls after fade delay (value between 0 and 1)
|
||||
//using outerHTML because the SVG object returned by Obsidin is in the main workspace window
|
||||
//but excalidraw might be open in a popout window which has a different document object
|
||||
const SVG_COG = ea.obsidian.getIcon("lucide-settings").outerHTML;
|
||||
@@ -45,10 +58,11 @@ const SVG_LASER_OFF = ea.obsidian.getIcon("lucide-wand").outerHTML;
|
||||
//utility & convenience functions
|
||||
//-------------------------------
|
||||
let isLaserOn = false;
|
||||
let slide = 0;
|
||||
let slide = shouldStartWithLastSlide ? window.ExcalidrawSlideshow.slide : 0;
|
||||
let isFullscreen = false;
|
||||
const ownerDocument = ea.targetView.ownerDocument;
|
||||
const startFullscreen = !altKey;
|
||||
|
||||
//The plugin and Obsidian App run in the window object
|
||||
//When Excalidraw is open in a popout window, the Excalidraw component will run in the ownerWindow
|
||||
//and in this case ownerWindow !== window
|
||||
@@ -176,7 +190,7 @@ let preventFullscreenExit = true;
|
||||
const gotoFullscreen = async () => {
|
||||
if(isFullscreen) return;
|
||||
preventFullscreenExit = true;
|
||||
if(app.isMobile) {
|
||||
if(ea.DEVICE.isMobile) {
|
||||
ea.viewToggleFullScreen();
|
||||
} else {
|
||||
await contentEl.webkitRequestFullscreen();
|
||||
@@ -192,8 +206,8 @@ const gotoFullscreen = async () => {
|
||||
const exitFullscreen = async () => {
|
||||
if(!isFullscreen) return;
|
||||
preventFullscreenExit = true;
|
||||
if(!app.isMobile && ownerDocument?.fullscreenElement) await ownerDocument.exitFullscreen();
|
||||
if(app.isMobile) ea.viewToggleFullScreen();
|
||||
if(!ea.DEVICE.isMobile && ownerDocument?.fullscreenElement) await ownerDocument.exitFullscreen();
|
||||
if(ea.DEVICE.isMobile) ea.viewToggleFullScreen();
|
||||
if(toggleFullscreenButton) toggleFullscreenButton.innerHTML = SVG_MAXIMIZE;
|
||||
await waitForExcalidrawResize();
|
||||
resetControlPanelElPosition();
|
||||
@@ -256,7 +270,7 @@ const getNavigationRect = ({ x1, y1, x2, y2 }) => {
|
||||
const { width, height } = excalidrawAPI.getAppState();
|
||||
const ratioX = width / Math.abs(x1 - x2);
|
||||
const ratioY = height / Math.abs(y1 - y2);
|
||||
let ratio = Math.min(Math.max(ratioX, ratioY), 10);
|
||||
let ratio = Math.min(Math.max(ratioX, ratioY), 30);
|
||||
|
||||
const scaledWidth = Math.abs(x1 - x2) * ratio;
|
||||
const scaledHeight = Math.abs(y1 - y2) * ratio;
|
||||
@@ -336,6 +350,9 @@ const navigate = async (dir) => {
|
||||
}
|
||||
if(selectSlideDropdown) selectSlideDropdown.value = slide+1;
|
||||
await scrollToNextRect(nextRect);
|
||||
if(window.ExcalidrawSlideshow && (typeof window.ExcalidrawSlideshow.slide === "number")) {
|
||||
window.ExcalidrawSlideshow.slide = slide;
|
||||
}
|
||||
}
|
||||
|
||||
const navigateToSlide = (slideNumber) => {
|
||||
@@ -532,9 +549,11 @@ const keydownListener = (e) => {
|
||||
if(hostLeaf.width === 0 && hostLeaf.height === 0) return;
|
||||
e.preventDefault();
|
||||
switch(e.key) {
|
||||
case "Backspace":
|
||||
case "Escape":
|
||||
exitPresentation();
|
||||
break;
|
||||
case "Space":
|
||||
case "ArrowRight":
|
||||
case "ArrowDown":
|
||||
navigate("fwd");
|
||||
@@ -630,7 +649,7 @@ const initializeEventListners = () => {
|
||||
controlPanelEl.removeEventListener('mouseenter', onMouseEnter, false);
|
||||
controlPanelEl.removeEventListener('mouseleave', onMouseLeave, false);
|
||||
controlPanelEl.parentElement?.removeChild(controlPanelEl);
|
||||
if(!app.isMobile) {
|
||||
if(!ea.DEVICE.isMobile) {
|
||||
contentEl.removeEventListener('webkitfullscreenchange', fullscreenListener);
|
||||
contentEl.removeEventListener('fullscreenchange', fullscreenListener);
|
||||
}
|
||||
@@ -645,7 +664,7 @@ const initializeEventListners = () => {
|
||||
return true;
|
||||
};
|
||||
|
||||
if(!app.isMobile) {
|
||||
if(!ea.DEVICE.isMobile) {
|
||||
contentEl.addEventListener('webkitfullscreenchange', fullscreenListener);
|
||||
contentEl.addEventListener('fullscreenchange', fullscreenListener);
|
||||
}
|
||||
@@ -708,7 +727,7 @@ const exitPresentation = async (openForEdit = false) => {
|
||||
//Resets pointer offsets. Ugly solution.
|
||||
//During testing offsets were wrong after presentation, but don't know why.
|
||||
//This should solve it even if they are wrong.
|
||||
hostView.refresh();
|
||||
hostView.refreshCanvasOffset();
|
||||
excalidrawAPI.setActiveTool({type: "selection"});
|
||||
})
|
||||
}
|
||||
@@ -719,6 +738,15 @@ const exitPresentation = async (openForEdit = false) => {
|
||||
const start = async () => {
|
||||
statusBarElement.style.display = "none";
|
||||
ea.setViewModeEnabled(true);
|
||||
const helpButton = ea.targetView.excalidrawContainer?.querySelector(".ToolIcon__icon.help-icon");
|
||||
if(helpButton) {
|
||||
helpButton.style.display = "none";
|
||||
}
|
||||
const zoomButton = ea.targetView.excalidrawContainer?.querySelector(".Stack.Stack_vertical.zoom-actions");
|
||||
if(zoomButton) {
|
||||
zoomButton.style.display = "none";
|
||||
}
|
||||
|
||||
createPresentationNavigationPanel();
|
||||
initializeEventListners();
|
||||
if(startFullscreen) {
|
||||
@@ -743,7 +771,8 @@ if(window.ExcalidrawSlideshow && (window.ExcalidrawSlideshow.script === utils.sc
|
||||
}
|
||||
window.ExcalidrawSlideshow = {
|
||||
script: utils.scriptFile.path,
|
||||
timestamp
|
||||
timestamp,
|
||||
slide: 0
|
||||
};
|
||||
window.ExcalidrawSlideshowStartTimer = window.setTimeout(start,500);
|
||||
}
|
||||
}
|
||||
@@ -15,12 +15,12 @@ In the `Command Palette` installed scripts are prefixed with `Downloaded/`, thus
|
||||
|
||||
## Attention developers and hobby hackers
|
||||
<img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/hobby-programmer.svg' align='left' style='background-color:whitesmoke; width:80px; margin-right:15px; margin-bottom:10px;'/>
|
||||
If you want to modify scripts, I recommend moving them to the `Excalidraw Automate script folder` or a different subfolder under the script folder. Scripts in the `Downloaded` folder will be overwritten when you click the `Update this script` button. Note also, that at this time, I do not check if the script file has been updated on GitHub, thus the `Update this script` button is always visible once you have installed a script, not only when an update is availble (hope to build this feature in the future).
|
||||
If you want to modify scripts, I recommend moving them to the `Excalidraw Automate script folder` or a different subfolder under the script folder. Scripts in the `Downloaded` folder will be overwritten when you click the `Update this script` button. Note also, that at this time, I do not check if the script file has been updated on GitHub, thus the `Update this script` button is always visible once you have installed a script, not only when an update is available (hope to build this feature in the future).
|
||||
|
||||
I would love to include your contribution in the script library. If you have a script of your own that you would like to share with the community, please open a [PR](https://github.com/zsviczian/obsidian-excalidraw-plugin/pulls) on GitHub. Be sure to include the following in your pull request
|
||||
- The [script file](https://github.com/zsviczian/obsidian-excalidraw-plugin/tree/master/ea-scripts) with a self explanetory name. The name of the file will be the name of the script in the Command Palette.
|
||||
- An [image](https://github.com/zsviczian/obsidian-excalidraw-plugin/tree/master/images) explaining the scripts purpose. Remember a picture speaks thousand words!
|
||||
- An update to this file [ea-scripts/index.md](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/index.md)
|
||||
- An update to this file [ea-scripts/index-new.md](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/index-new.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -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,12 +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]]|
|
||||
@@ -181,12 +182,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
|
||||
@@ -197,7 +192,7 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Boolean%20Operations.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/GColoy'>@GColoy</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/Boolean%20Operations.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">With This Script it is possible to make boolean Operations on Shapes.<br>The style of the resulting shape will be the style of the highest ranking Element that was used.<br>The ranking of the elemtns is based on their background. The "denser" the background, the higher the ranking (the order of backgroundstyles is shown below). If they have the same background the opacity will decide. If thats also the same its decided by the order they were created.<br>The ranking is also important for the diffrence operation, so a tranparent object for example will cut a hole into a solid object.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-boolean-operations-showcase.png'><br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-boolean-operations-element-ranking.png'></td></tr></table>
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/GColoy'>@GColoy</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/Boolean%20Operations.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">With This Script it is possible to make boolean Operations on Shapes.<br>The style of the resulting shape will be the style of the highest ranking Element that was used.<br>The ranking of the elements is based on their background. The "denser" the background, the higher the ranking (the order of backgroundstyles is shown below). If they have the same background the opacity will decide. If thats also the same its decided by the order they were created.<br>The ranking is also important for the difference operation, so a transparent object for example will cut a hole into a solid object.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-boolean-operations-showcase.png'><br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-boolean-operations-element-ranking.png'></td></tr></table>
|
||||
|
||||
|
||||
## Box Each Selected Groups
|
||||
@@ -272,6 +267,12 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/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
|
||||
```
|
||||
<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/Custom%20Zoom.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">You can set a custom zoom level with this script. This allows you to set a zoom level below 10% or set the zoom level to a specific value. Note however, that Excalidraw has a bug under 10% zoom... a phantom copy of your image may appear on screen. If this happens, increase the zoom and the phantom should disappear, if it doesn't, then close and open the drawing.</td></tr></table>
|
||||
|
||||
## Darken background color
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Darken%20background%20color.md
|
||||
@@ -382,19 +383,18 @@ 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/ExcaliAI.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Various AI features based on GPT Vision.<br><iframe width="400" height="225" src="https://www.youtube.com/embed/A1vrSGBbWgo" 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>
|
||||
|
||||
## Excalidraw Writing Machine
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Excalidraw%20Writing%20Machine.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Excalidraw%20Writing%20Machine.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Creates a hierarchical Markdown document out of a visual layout of an article that can be fed to Templater and converted into an article using AI for Templater.<br>Watch this video to understand how the script is intended to work:<br><iframe width="400" height="225" src="https://www.youtube.com/embed/zvRpCOZAUSs" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><br>You can download the sample Obsidian Templater file from <a href="https://gist.github.com/zsviczian/bf49d4b2d401f5749aaf8c2fa8a513d9">here</a>. You can download the demo PDF document showcased in the video from <a href="https://zsviczian.github.io/DemoArticle-AtomicHabits.pdf">here</a>.</td></tr></table>
|
||||
|
||||
## GPT Draw-a-UI
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/GPT-Draw-a-UI.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/GPT-Draw-a-UI.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script was discontinued in favor of ExcaliAI. Draw a UI and let GPT create the code for you.<br><iframe width="400" height="225" src="https://www.youtube.com/embed/y3kHl_6Ll4w" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-draw-a-ui.jpg'></td></tr></table>
|
||||
|
||||
|
||||
## Hardware Eraser Support
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Hardware%20Eraser%20Support.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>
|
||||
|
||||
## Invert colors
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Invert%20colors.md
|
||||
@@ -501,7 +501,15 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Select%20Similar%20Elements.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/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. This script enhances control and efficiency in your Excalidraw experience.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-select-similar-elements.png'></td></tr></table>
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/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
|
||||
|
||||
|
Before Width: | Height: | Size: 341 KiB After Width: | Height: | Size: 861 KiB |
BIN
images/scripts-reset-latex.jpg
Normal file
|
After Width: | Height: | Size: 16 KiB |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.0.1-beta-2",
|
||||
"version": "2.5.0-beta-2",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.0.19",
|
||||
"version": "2.4.3",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
"authorUrl": "https://zsolt.blog",
|
||||
"authorUrl": "https://www.zsolt.blog",
|
||||
"fundingUrl": "https://ko-fi.com/zsolt",
|
||||
"helpUrl": "https://github.com/zsviczian/obsidian-excalidraw-plugin#readme",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
}
|
||||
|
||||
43
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "obsidian-excalidraw-plugin",
|
||||
"version": "2.0.14",
|
||||
"version": "2.2.5",
|
||||
"description": "This is an Obsidian.md plugin that lets you view and edit Excalidraw drawings",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
@@ -18,55 +18,64 @@
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@zsviczian/excalidraw": "0.17.1-obsidian-13",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@zsviczian/excalidraw": "0.17.1-obsidian-51",
|
||||
"chroma-js": "^2.4.2",
|
||||
"clsx": "^2.0.0",
|
||||
"colormaster": "^1.2.1",
|
||||
"@zsviczian/colormaster": "^1.2.2",
|
||||
"gl-matrix": "^3.4.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lucide-react": "^0.263.1",
|
||||
"mathjax-full": "^3.2.2",
|
||||
"monkey-around": "^2.3.0",
|
||||
"nanoid": "^4.0.2",
|
||||
"opentype.js": "^1.3.4",
|
||||
"polybooljs": "^1.2.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"roughjs": "^4.5.2",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"nanoid": "^4.0.2",
|
||||
"lucide-react": "^0.263.1",
|
||||
"mathjax-full": "^3.2.2"
|
||||
"woff2sfnt-sfnt2woff": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"lz-string": "^1.5.0",
|
||||
"@babel/core": "^7.22.9",
|
||||
"@babel/preset-env": "^7.22.10",
|
||||
"@babel/preset-react": "^7.22.5",
|
||||
"@codemirror/commands": "^6.3.3",
|
||||
"@codemirror/language": "^6.10.0",
|
||||
"@codemirror/search": "^6.5.5",
|
||||
"@codemirror/state": "^6.4.0",
|
||||
"@codemirror/view": "^6.23.0",
|
||||
"@excalidraw/eslint-config": "^1.0.3",
|
||||
"@excalidraw/prettier-config": "^1.0.2",
|
||||
"@rollup/plugin-babel": "^6.0.3",
|
||||
"@rollup/plugin-commonjs": "^24.1.0",
|
||||
"@rollup/plugin-node-resolve": "^15.2.1",
|
||||
"@rollup/plugin-commonjs": "^26.0.1",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-replace": "^5.0.2",
|
||||
"@rollup/plugin-typescript": "^11.1.2",
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
"@types/chroma-js": "^2.4.0",
|
||||
"@types/js-beautify": "^1.14.0",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/node": "^20.10.5",
|
||||
"@types/opentype.js": "^1.3.8",
|
||||
"@types/react": "^18.2.45",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@zerollup/ts-transform-paths": "^1.7.18",
|
||||
"cross-env": "^7.0.3",
|
||||
"cssnano": "^6.0.2",
|
||||
"dotenv": "^16.4.5",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"obsidian": "^1.4.0",
|
||||
"lz-string": "^1.5.0",
|
||||
"obsidian": "1.5.7-1",
|
||||
"prettier": "^3.0.1",
|
||||
"rollup": "^2.70.1",
|
||||
"rollup-plugin-copy": "^3.5.0",
|
||||
"rollup-plugin-postprocess": "github:brettz9/rollup-plugin-postprocess#update",
|
||||
"@zsviczian/rollup-plugin-postprocess": "^1.0.3",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"rollup-plugin-typescript2": "^0.34.1",
|
||||
"rollup-plugin-web-worker-loader": "^1.6.1",
|
||||
"tslib": "^2.6.1",
|
||||
"ttypescript": "^1.5.15",
|
||||
"typescript": "^5.2.2",
|
||||
"cssnano": "^6.0.2"
|
||||
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"@typescript-eslint/typescript-estree": "5.3.0"
|
||||
|
||||
@@ -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'
|
||||
}]
|
||||
});
|
||||
143
rollup.config.js
@@ -1,19 +1,20 @@
|
||||
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import { env } from "process";
|
||||
import babel from '@rollup/plugin-babel';
|
||||
import replace from "@rollup/plugin-replace";
|
||||
import { terser } from "rollup-plugin-terser";
|
||||
import copy from "rollup-plugin-copy";
|
||||
import typescript2 from "rollup-plugin-typescript2";
|
||||
import webWorker from "rollup-plugin-web-worker-loader";
|
||||
import fs from'fs';
|
||||
import fs from 'fs';
|
||||
import LZString from 'lz-string';
|
||||
import postprocess from 'rollup-plugin-postprocess';
|
||||
import postprocess from '@zsviczian/rollup-plugin-postprocess';
|
||||
import cssnano from 'cssnano';
|
||||
|
||||
const DIST_FOLDER = 'dist';
|
||||
const isProd = (process.env.NODE_ENV === "production")
|
||||
// Load environment variables
|
||||
import dotenv from 'dotenv';
|
||||
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}`);
|
||||
|
||||
@@ -26,14 +27,15 @@ const react_pkg = isLib ? "" : isProd
|
||||
const reactdom_pkg = isLib ? "" : 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 = isLib ? "" : fs.readFileSync("./node_modules/lz-string/libs/lz-string.min.js", "utf8");
|
||||
if(!isLib) {
|
||||
if (!isLib) {
|
||||
const excalidraw_styles = isProd
|
||||
? fs.readFileSync("./node_modules/@zsviczian/excalidraw/dist/styles.production.css", "utf8")
|
||||
: fs.readFileSync("./node_modules/@zsviczian/excalidraw/dist/styles.development.css", "utf8");
|
||||
const plugin_styles = fs.readFileSync("./styles.css", "utf8")
|
||||
const plugin_styles = fs.readFileSync("./styles.css", "utf8");
|
||||
const styles = plugin_styles + excalidraw_styles;
|
||||
cssnano()
|
||||
cssnano()
|
||||
.process(styles) // Process the CSS
|
||||
.then(result => {
|
||||
fs.writeFileSync(`./${DIST_FOLDER}/styles.css`, result.css);
|
||||
@@ -45,84 +47,79 @@ if(!isLib) {
|
||||
|
||||
const manifestStr = isLib ? "" : fs.readFileSync("manifest.json", "utf-8");
|
||||
const manifest = isLib ? {} : JSON.parse(manifestStr);
|
||||
!isLib && console.log(manifest.version);
|
||||
if (!isLib) console.log(manifest.version);
|
||||
|
||||
const packageString = isLib
|
||||
? ""
|
||||
const packageString = isLib
|
||||
? ""
|
||||
: ';' + lzstring_pkg +
|
||||
'\nconst EXCALIDRAW_PACKAGES = "' + LZString.compressToBase64(react_pkg + reactdom_pkg + excalidraw_pkg) + '";\n' +
|
||||
'const {react, reactDOM, excalidrawLib} = window.eval.call(window, `(function() {' +
|
||||
'${LZString.decompressFromBase64(EXCALIDRAW_PACKAGES)};' +
|
||||
'return {react:React, reactDOM:ReactDOM, excalidrawLib: ExcalidrawLib};})();`);\n' +
|
||||
'const PLUGIN_VERSION="'+manifest.version+'";';
|
||||
'\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 BASE_CONFIG = {
|
||||
input: 'src/main.ts',
|
||||
external: ['obsidian', '@zsviczian/excalidraw', 'react', 'react-dom'],
|
||||
}
|
||||
external: [
|
||||
'@codemirror/autocomplete',
|
||||
'@codemirror/collab',
|
||||
'@codemirror/commands',
|
||||
'@codemirror/language',
|
||||
'@codemirror/lint',
|
||||
'@codemirror/search',
|
||||
'@codemirror/state',
|
||||
'@codemirror/view',
|
||||
'@lezer/common',
|
||||
'@lezer/highlight',
|
||||
'@lezer/lr',
|
||||
'obsidian',
|
||||
'@zsviczian/excalidraw',
|
||||
'react',
|
||||
'react-dom'
|
||||
],
|
||||
};
|
||||
|
||||
const getRollupPlugins = (tsconfig, ...plugins) =>
|
||||
[
|
||||
typescript2(tsconfig),
|
||||
nodeResolve({ browser: true }),
|
||||
commonjs(),
|
||||
webWorker({ inline: true, forceInline: true, targetPlatform: "browser" }),
|
||||
].concat(plugins);
|
||||
const getRollupPlugins = (tsconfig, ...plugins) => [
|
||||
typescript2(tsconfig),
|
||||
replace({
|
||||
preventAssignment: true,
|
||||
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
|
||||
}),
|
||||
commonjs(),
|
||||
nodeResolve({ browser: true, preferBuiltins: false }),
|
||||
].concat(plugins);
|
||||
|
||||
const BUILD_CONFIG = {
|
||||
...BASE_CONFIG,
|
||||
output: {
|
||||
dir: DIST_FOLDER,
|
||||
entryFileNames: 'main.js',
|
||||
//sourcemap: isProd?false:'inline',
|
||||
format: 'cjs',
|
||||
exports: 'default',
|
||||
},
|
||||
plugins: [
|
||||
typescript2({
|
||||
tsconfig: isProd ? "tsconfig.json" : "tsconfig.dev.json",
|
||||
//inlineSources: !isProd
|
||||
}),
|
||||
replace({
|
||||
preventAssignment: true,
|
||||
"process.env.NODE_ENV": JSON.stringify(env.NODE_ENV),
|
||||
}),
|
||||
babel({
|
||||
presets: [['@babel/preset-env', {
|
||||
targets: {
|
||||
esmodules: true,
|
||||
},
|
||||
}]],
|
||||
exclude: "node_modules/**"
|
||||
}),
|
||||
commonjs(),
|
||||
nodeResolve({ browser: true, preferBuiltins: false }),
|
||||
...isProd
|
||||
? [
|
||||
plugins: getRollupPlugins(
|
||||
{tsconfig: isProd ? "tsconfig.json" : "tsconfig.dev.json"},
|
||||
...(isProd ? [
|
||||
terser({
|
||||
toplevel: false,
|
||||
compress: {passes: 2}
|
||||
compress: { passes: 2 },
|
||||
format: {
|
||||
comments: false, // Remove all comments
|
||||
},
|
||||
}),
|
||||
//!postprocess - the version available on npmjs does not work, need this update:
|
||||
// npm install brettz9/rollup-plugin-postprocess#update --save-dev
|
||||
// https://github.com/developit/rollup-plugin-postprocess/issues/10
|
||||
postprocess([
|
||||
[/,React=require\("react"\);/, packageString],
|
||||
])
|
||||
]
|
||||
: [
|
||||
postprocess([
|
||||
[/var React = require\('react'\);/, packageString],
|
||||
])
|
||||
],
|
||||
[/React=require\("react"\),state=require\("@codemirror\/state"\),view=require\("@codemirror\/view"\)/,
|
||||
`state=require("@codemirror/state"),view=require("@codemirror/view")` + packageString],
|
||||
]),
|
||||
] : [
|
||||
postprocess([ [/var React = require\('react'\);/, packageString] ]),
|
||||
]),
|
||||
copy({
|
||||
targets: [
|
||||
{ src: 'manifest.json', dest: DIST_FOLDER },
|
||||
],
|
||||
verbose: true, // Optional: To display copied files in the console
|
||||
targets: [ { src: 'manifest.json', dest: DIST_FOLDER } ],
|
||||
verbose: true,
|
||||
}),
|
||||
],
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
const LIB_CONFIG = {
|
||||
...BASE_CONFIG,
|
||||
@@ -134,16 +131,16 @@ const LIB_CONFIG = {
|
||||
name: "Excalidraw (Library)",
|
||||
},
|
||||
plugins: getRollupPlugins(
|
||||
{ tsconfig: "tsconfig-lib.json"},
|
||||
copy({ targets: [{ src: "src/*.d.ts", dest: "lib/typings" }] })
|
||||
),
|
||||
}
|
||||
{ tsconfig: "tsconfig-lib.json" },
|
||||
copy({ targets: [{ src: "src/*.d.ts", dest: "lib/typings" }] })
|
||||
),
|
||||
};
|
||||
|
||||
let config = [];
|
||||
if(process.env.NODE_ENV === "lib") {
|
||||
if (process.env.NODE_ENV === "lib") {
|
||||
config.push(LIB_CONFIG);
|
||||
} else {
|
||||
config.push(BUILD_CONFIG);
|
||||
}
|
||||
|
||||
export default config;
|
||||
export default config;
|
||||
|
||||
49
src/CodeMirrorExtension/EditorHandler.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { Extension } from "@codemirror/state";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { HideTextBetweenCommentsExtension } from "./Fadeout";
|
||||
export const EDITOR_FADEOUT = "fadeOutExcalidrawMarkup";
|
||||
|
||||
const editorExtensions: {[key:string]:Extension}= {
|
||||
[EDITOR_FADEOUT]: HideTextBetweenCommentsExtension,
|
||||
}
|
||||
|
||||
export class EditorHandler {
|
||||
private activeEditorExtensions: Extension[] = [];
|
||||
|
||||
constructor(private plugin: ExcalidrawPlugin) {}
|
||||
|
||||
destroy(): void {
|
||||
this.plugin = null;
|
||||
}
|
||||
|
||||
setup(): void {
|
||||
this.plugin.registerEditorExtension(this.activeEditorExtensions);
|
||||
this.updateCMExtensionState(EDITOR_FADEOUT, this.plugin.settings.fadeOutExcalidrawMarkup);
|
||||
}
|
||||
|
||||
updateCMExtensionState(
|
||||
extensionIdentifier: string,
|
||||
extensionState: boolean,
|
||||
) {
|
||||
const extension = editorExtensions[extensionIdentifier];
|
||||
if(!extension) return;
|
||||
if (extensionState == true) {
|
||||
this.activeEditorExtensions.push(extension);
|
||||
// @ts-ignore
|
||||
this.activeEditorExtensions[this.activeEditorExtensions.length - 1].exID = extensionIdentifier;
|
||||
} else {
|
||||
for (let i = 0; i < this.activeEditorExtensions.length; i++) {
|
||||
const ext = this.activeEditorExtensions[i];
|
||||
// @ts-ignore
|
||||
if (ext.exID === extensionIdentifier) {
|
||||
this.activeEditorExtensions.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.plugin.app.workspace.updateOptions();
|
||||
}
|
||||
update(): void {
|
||||
this.plugin.app.workspace.updateOptions();
|
||||
}
|
||||
}
|
||||
66
src/CodeMirrorExtension/Fadeout.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { RangeSetBuilder } from "@codemirror/state";
|
||||
import { Decoration, DecorationSet, EditorView, ViewPlugin, ViewUpdate } from "@codemirror/view";
|
||||
|
||||
const o30 = Decoration.line({ attributes: {class: "ex-opacity-30"} });
|
||||
const o15 = Decoration.line({ attributes: {class: "ex-opacity-15"} });
|
||||
const o8 = Decoration.line({ attributes: {class: "ex-opacity-8"} });
|
||||
const o5 = Decoration.line({ attributes: {class: "ex-opacity-5"} });
|
||||
const o0 = Decoration.line({ attributes: {class: "ex-opacity-0"} });
|
||||
|
||||
export const HideTextBetweenCommentsExtension = ViewPlugin.fromClass(
|
||||
class {
|
||||
view: EditorView;
|
||||
decorations: DecorationSet;
|
||||
reExcalidrawData = /^%%(?:\r\n|\r|\n)# Excalidraw Data$/gm;
|
||||
reTextElements = /^%%(?:\r\n|\r|\n)# Text Elements$/gm;
|
||||
reDrawing = /^%%(?:\r\n|\r|\n)##? Drawing$/gm;
|
||||
linecount = 0;
|
||||
isExcalidraw = false;
|
||||
|
||||
constructor(view: EditorView) {
|
||||
this.view = view;
|
||||
this.isExcalidraw = view.state.doc.toString().search(/^excalidraw-plugin: /m) > 0;
|
||||
if(!this.isExcalidraw) {
|
||||
this.decorations = Decoration.none;
|
||||
return;
|
||||
}
|
||||
this.decorations = this.updateDecorations(view);
|
||||
}
|
||||
|
||||
updateDecorations (view: EditorView) {
|
||||
const { state } = view;
|
||||
const { doc } = state;
|
||||
|
||||
const text = doc.toString();
|
||||
|
||||
let start = text.search(this.reExcalidrawData);
|
||||
if(start == -1) {
|
||||
start = text.search(this.reTextElements);
|
||||
}
|
||||
if(start == -1) {
|
||||
start = text.search(this.reDrawing);
|
||||
}
|
||||
if(start == -1) return Decoration.none;
|
||||
|
||||
const startLine = doc.lineAt(start).number;
|
||||
const endLine = doc.lines;
|
||||
let builder = new RangeSetBuilder<Decoration>()
|
||||
for (let l = startLine; l <= endLine; l++) {
|
||||
const line = doc.line(l);
|
||||
const pos = l-startLine;
|
||||
builder.add(line.from, line.from,
|
||||
pos == 0 ? o30 : (pos == 1) ? o15 : (pos < 6) ? o8 : (pos < 12) ? o5 : o0);
|
||||
}
|
||||
return builder.finish()
|
||||
}
|
||||
|
||||
update(update: ViewUpdate) {
|
||||
if (this.isExcalidraw && update.docChanged) {
|
||||
this.decorations = this.updateDecorations(update.view)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
decorations: (x) => x.decorations,
|
||||
}
|
||||
);
|
||||
@@ -1,14 +1,9 @@
|
||||
//https://stackoverflow.com/questions/2068344/how-do-i-get-a-youtube-video-thumbnail-from-the-youtube-api
|
||||
//https://img.youtube.com/vi/uZz5MgzWXiM/maxresdefault.jpg
|
||||
|
||||
import { ExcalidrawElement, ExcalidrawImageElement, FileId } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ExcalidrawElement, FileId } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { BinaryFileData, DataURL } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { App, MarkdownRenderer, Notice, TFile } from "obsidian";
|
||||
import {
|
||||
ASSISTANT_FONT,
|
||||
CASCADIA_FONT,
|
||||
VIRGIL_FONT,
|
||||
} from "./constants/constFonts";
|
||||
import {
|
||||
DEFAULT_MD_EMBED_CSS,
|
||||
fileid,
|
||||
@@ -16,6 +11,7 @@ import {
|
||||
nanoid,
|
||||
THEME_FILTER,
|
||||
FRONTMATTER_KEYS,
|
||||
getCSSFontDefinition,
|
||||
} from "./constants/constants";
|
||||
import { createSVG } from "./ExcalidrawAutomate";
|
||||
import { ExcalidrawData, getTransclusion } from "./ExcalidrawData";
|
||||
@@ -23,7 +19,7 @@ import { ExportSettings } from "./ExcalidrawView";
|
||||
import { t } from "./lang/helpers";
|
||||
import { tex2dataURL } from "./LaTeX";
|
||||
import ExcalidrawPlugin from "./main";
|
||||
import { blobToBase64, getDataURLFromURL, getMimeType, getPDFDoc, getURLImageExtension, readLocalFileBinary } from "./utils/FileUtils";
|
||||
import { blobToBase64, getDataURLFromURL, getMimeType, getPDFDoc, getURLImageExtension, hasExcalidrawEmbeddedImagesTreeChanged, readLocalFileBinary } from "./utils/FileUtils";
|
||||
import {
|
||||
errorlog,
|
||||
getDataURL,
|
||||
@@ -38,10 +34,14 @@ import {
|
||||
LinkParts,
|
||||
svgToBase64,
|
||||
isMaskFile,
|
||||
getEmbeddedFilenameParts,
|
||||
cropCanvas,
|
||||
} from "./utils/Utils";
|
||||
import { ValueOf } from "./types";
|
||||
import { ValueOf } from "./types/types";
|
||||
import { getMermaidImageElements, getMermaidText, shouldRenderMermaid } from "./utils/MermaidUtils";
|
||||
import { mermaidToExcalidraw } from "src/constants/constants";
|
||||
import { ImageKey, imageCache } from "./utils/ImageCache";
|
||||
import { FILENAMEPARTS, PreviewImageType } from "./utils/UtilTypes";
|
||||
|
||||
//An ugly workaround for the following situation.
|
||||
//File A is a markdown file that has an embedded Excalidraw file B
|
||||
@@ -142,8 +142,6 @@ const replaceSVGColors = (svg: SVGSVGElement | string, colorMap: ColorMap | null
|
||||
return svg;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class EmbeddedFile {
|
||||
public file: TFile = null;
|
||||
public isSVGwithBitmap: boolean = false;
|
||||
@@ -154,6 +152,7 @@ export class EmbeddedFile {
|
||||
public mimeType: MimeType = "application/octet-stream";
|
||||
public size: Size = { height: 0, width: 0 };
|
||||
public linkParts: LinkParts;
|
||||
public filenameparts: FILENAMEPARTS
|
||||
private hostPath: string;
|
||||
public attemptCounter: number = 0;
|
||||
public isHyperLink: boolean = false;
|
||||
@@ -164,7 +163,7 @@ export class EmbeddedFile {
|
||||
constructor(plugin: ExcalidrawPlugin, hostPath: string, imgPath: string, colorMapJSON?: string) {
|
||||
this.plugin = plugin;
|
||||
this.resetImage(hostPath, imgPath);
|
||||
if(this.file && (this.plugin.ea.isExcalidrawFile(this.file) || this.file.extension.toLowerCase() === "svg")) {
|
||||
if(this.file && (this.plugin.isExcalidrawFile(this.file) || this.file.extension.toLowerCase() === "svg")) {
|
||||
try {
|
||||
this.colorMap = colorMapJSON ? JSON.parse(colorMapJSON.toLocaleLowerCase()) : null;
|
||||
} catch (error) {
|
||||
@@ -201,7 +200,7 @@ export class EmbeddedFile {
|
||||
if (!this.linkParts.height) {
|
||||
this.linkParts.height = this.plugin.settings.mdSVGmaxHeight;
|
||||
}
|
||||
this.file = app.metadataCache.getFirstLinkpathDest(
|
||||
this.file = this.plugin.app.metadataCache.getFirstLinkpathDest(
|
||||
this.linkParts.path,
|
||||
hostPath,
|
||||
);
|
||||
@@ -212,6 +211,9 @@ export class EmbeddedFile {
|
||||
5000,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.filenameparts = getEmbeddedFilenameParts(imgPath);
|
||||
this.filenameparts.filepath = this.file.path;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,7 +231,7 @@ export class EmbeddedFile {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return this.mtime != this.file.stat.mtime;
|
||||
return this.mtime !== this.file.stat.mtime;
|
||||
}
|
||||
|
||||
public setImage(
|
||||
@@ -357,23 +359,68 @@ export class EmbeddedFilesLoader {
|
||||
: false,
|
||||
withTheme: !!forceTheme,
|
||||
isMask,
|
||||
skipInliningFonts: false,
|
||||
};
|
||||
const svg = replaceSVGColors(
|
||||
await createSVG(
|
||||
file?.path,
|
||||
true,
|
||||
exportSettings,
|
||||
this,
|
||||
forceTheme,
|
||||
null,
|
||||
null,
|
||||
elements,
|
||||
this.plugin,
|
||||
depth+1,
|
||||
getExportPadding(this.plugin, file),
|
||||
),
|
||||
inFile instanceof EmbeddedFile ? inFile.colorMap : null
|
||||
) as SVGSVGElement;
|
||||
|
||||
const hasColorMap = Boolean(inFile instanceof EmbeddedFile ? inFile.colorMap : null);
|
||||
const shouldUseCache = !hasColorMap && this.plugin.settings.allowImageCacheInScene && file && imageCache.isReady();
|
||||
const hasFilenameParts = Boolean((inFile instanceof EmbeddedFile) && inFile.filenameparts);
|
||||
const filenameParts = hasFilenameParts ? (inFile as EmbeddedFile).filenameparts : null;
|
||||
const cacheKey:ImageKey = {
|
||||
...hasFilenameParts? {
|
||||
...filenameParts,
|
||||
inlineFonts: !exportSettings.skipInliningFonts,
|
||||
}: {
|
||||
filepath: file.path,
|
||||
hasBlockref: false,
|
||||
hasGroupref: false,
|
||||
hasTaskbone: false,
|
||||
hasArearef: false,
|
||||
hasFrameref: false,
|
||||
hasClippedFrameref: false,
|
||||
hasSectionref: false,
|
||||
inlineFonts: !exportSettings.skipInliningFonts,
|
||||
blockref: null,
|
||||
sectionref: null,
|
||||
linkpartReference: null,
|
||||
linkpartAlias: null,
|
||||
},
|
||||
isDark,
|
||||
previewImageType: PreviewImageType.SVG,
|
||||
scale: 1,
|
||||
isTransparent: !exportSettings.withBackground,
|
||||
}
|
||||
|
||||
const maybeSVG = shouldUseCache
|
||||
? await imageCache.getImageFromCache(cacheKey)
|
||||
: undefined;
|
||||
|
||||
const svg = (maybeSVG && (maybeSVG instanceof SVGSVGElement))
|
||||
? maybeSVG
|
||||
: replaceSVGColors(
|
||||
await createSVG(
|
||||
hasFilenameParts
|
||||
? (filenameParts.hasGroupref || filenameParts.hasBlockref ||
|
||||
filenameParts.hasSectionref || filenameParts.hasFrameref ||
|
||||
filenameParts.hasClippedFrameref
|
||||
? filenameParts.filepath + filenameParts.linkpartReference
|
||||
: file.path)
|
||||
: file?.path,
|
||||
false, //false
|
||||
hasFilenameParts && filenameParts.hasClippedFrameref
|
||||
? {...exportSettings, frameRendering: {enabled: true, name: false, outline: false, clip: true}}
|
||||
: exportSettings,
|
||||
this,
|
||||
forceTheme,
|
||||
null,
|
||||
null,
|
||||
elements,
|
||||
this.plugin,
|
||||
depth+1,
|
||||
getExportPadding(this.plugin, file),
|
||||
),
|
||||
inFile instanceof EmbeddedFile ? inFile.colorMap : null
|
||||
) as SVGSVGElement;
|
||||
|
||||
//https://stackoverflow.com/questions/51154171/remove-css-filter-on-child-elements
|
||||
const imageList = svg.querySelectorAll(
|
||||
@@ -382,7 +429,8 @@ export class EmbeddedFilesLoader {
|
||||
if (imageList.length > 0) {
|
||||
hasSVGwithBitmap = true;
|
||||
}
|
||||
if (hasSVGwithBitmap && isDark) {
|
||||
|
||||
if (hasSVGwithBitmap && isDark && !Boolean(maybeSVG)) {
|
||||
imageList.forEach((i) => {
|
||||
const id = i.parentElement?.id;
|
||||
svg.querySelectorAll(`use[href='#${id}']`).forEach((u) => {
|
||||
@@ -393,6 +441,20 @@ export class EmbeddedFilesLoader {
|
||||
if (!hasSVGwithBitmap && svg.getAttribute("hasbitmap")) {
|
||||
hasSVGwithBitmap = true;
|
||||
}
|
||||
if(shouldUseCache && !Boolean(maybeSVG)) {
|
||||
//cache SVG should have the width and height parameters and not the embedded font
|
||||
//see svgWithFont below
|
||||
imageCache.addImageToCache(cacheKey,"", svg);
|
||||
}
|
||||
|
||||
if(!svg.hasAttribute("width") && svg.hasAttribute("viewBox")){
|
||||
//2024.06.09
|
||||
//this addresses backward compatibility issues where the cache does not have the width and height attributes
|
||||
//this should be removed in the future
|
||||
const vb = svg.getAttr("viewBox").split(" ");
|
||||
Boolean(vb[2]) && svg.setAttribute("width", vb[2]);
|
||||
Boolean(vb[3]) && svg.setAttribute("height", vb[3]);
|
||||
}
|
||||
const dURL = svgToBase64(svg.outerHTML) as DataURL;
|
||||
return {dataURL: dURL as DataURL, hasSVGwithBitmap};
|
||||
};
|
||||
@@ -411,6 +473,8 @@ export class EmbeddedFilesLoader {
|
||||
return null;
|
||||
}
|
||||
|
||||
const app = this.plugin.app;
|
||||
|
||||
const isHyperLink = inFile instanceof EmbeddedFile ? inFile.isHyperLink : false;
|
||||
const isLocalLink = inFile instanceof EmbeddedFile ? inFile.isLocalLink : false;
|
||||
const hyperlink = inFile instanceof EmbeddedFile ? inFile.hyperlink : "";
|
||||
@@ -511,7 +575,8 @@ export class EmbeddedFilesLoader {
|
||||
return {
|
||||
mimeType,
|
||||
fileId: await generateIdFromFile(
|
||||
isHyperLink || isPDF ? (new TextEncoder()).encode(dataURL as string) : ab
|
||||
isHyperLink || isPDF ? (new TextEncoder()).encode(dataURL as string) : ab,
|
||||
inFile instanceof EmbeddedFile ? inFile.filenameparts?.linkpartReference : undefined
|
||||
),
|
||||
dataURL,
|
||||
created: isHyperLink || isLocalLink ? 0 : file.stat.mtime,
|
||||
@@ -526,9 +591,12 @@ export class EmbeddedFilesLoader {
|
||||
public async loadSceneFiles(
|
||||
excalidrawData: ExcalidrawData,
|
||||
addFiles: (files: FileData[], isDark: boolean, final?: boolean) => void,
|
||||
depth:number
|
||||
depth:number,
|
||||
isThemeChange:boolean = false,
|
||||
fileIDWhiteList?: Set<FileId>,
|
||||
) {
|
||||
if(depth > 4) {
|
||||
|
||||
if(depth > 7) {
|
||||
new Notice(t("INFINITE_LOOP_WARNING")+depth.toString(), 6000);
|
||||
return;
|
||||
}
|
||||
@@ -540,6 +608,7 @@ export class EmbeddedFilesLoader {
|
||||
let entry: IteratorResult<[FileId, EmbeddedFile]>;
|
||||
const files: FileData[] = [];
|
||||
while (!this.terminate && !(entry = entries.next()).done) {
|
||||
if(fileIDWhiteList && !fileIDWhiteList.has(entry.value[0])) continue;
|
||||
const embeddedFile: EmbeddedFile = entry.value[1];
|
||||
if (!embeddedFile.isLoaded(this.isDark)) {
|
||||
//debug({where:"EmbeddedFileLoader.loadSceneFiles",uid:this.uid,status:"embedded Files are not loaded"});
|
||||
@@ -562,7 +631,8 @@ export class EmbeddedFilesLoader {
|
||||
}
|
||||
//files.push(fileData);
|
||||
}
|
||||
} else if (embeddedFile.isSVGwithBitmap) {
|
||||
} 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],
|
||||
@@ -585,6 +655,7 @@ export class EmbeddedFilesLoader {
|
||||
let equation;
|
||||
const equations = excalidrawData.getEquationEntries();
|
||||
while (!this.terminate && !(equation = equations.next()).done) {
|
||||
if(fileIDWhiteList && !fileIDWhiteList.has(entry.value[0])) continue;
|
||||
if (!excalidrawData.getEquation(equation.value[0]).isLoaded) {
|
||||
const latex = equation.value[1].latex;
|
||||
const data = await tex2dataURL(latex);
|
||||
@@ -681,6 +752,8 @@ export class EmbeddedFilesLoader {
|
||||
}
|
||||
const pageNum = isNaN(linkParts.page) ? 1 : (linkParts.page??1);
|
||||
const scale = this.plugin.settings.pdfScale;
|
||||
const cropRect = linkParts.ref.split("rect=")[1]?.split(",").map(x=>parseInt(x));
|
||||
const validRect = cropRect && cropRect.length === 4 && cropRect.every(x=>!isNaN(x));
|
||||
|
||||
// Render the page
|
||||
const renderPage = async (num:number) => {
|
||||
@@ -701,6 +774,23 @@ export class EmbeddedFilesLoader {
|
||||
};
|
||||
|
||||
await page.render(renderCtx).promise;
|
||||
if(validRect) {
|
||||
const [left, bottom, _, top] = page.view;
|
||||
|
||||
const pageHeight = top - bottom;
|
||||
width = (cropRect[2] - cropRect[0]) * scale;
|
||||
height = (cropRect[3] - cropRect[1]) * scale;
|
||||
|
||||
const crop = validRect ? {
|
||||
left: (cropRect[0] - left) * scale,
|
||||
top: (bottom + pageHeight - cropRect[3]) * scale,
|
||||
width,
|
||||
height,
|
||||
} : undefined;
|
||||
if(crop) {
|
||||
return cropCanvas(canvas, crop);
|
||||
}
|
||||
}
|
||||
return canvas;
|
||||
};
|
||||
|
||||
@@ -751,13 +841,29 @@ export class EmbeddedFilesLoader {
|
||||
}
|
||||
switch (fontName) {
|
||||
case "Virgil":
|
||||
fontDef = VIRGIL_FONT;
|
||||
fontDef = await getCSSFontDefinition(1);
|
||||
break;
|
||||
case "Cascadia":
|
||||
fontDef = CASCADIA_FONT;
|
||||
fontDef = await getCSSFontDefinition(3);
|
||||
break;
|
||||
case "Assistant":
|
||||
fontDef = ASSISTANT_FONT;
|
||||
case "Assistant":
|
||||
case "Helvetica":
|
||||
fontDef = await getCSSFontDefinition(2);
|
||||
break;
|
||||
case "Excalifont":
|
||||
fontDef = await getCSSFontDefinition(5);
|
||||
break;
|
||||
case "Nunito":
|
||||
fontDef = await getCSSFontDefinition(6);
|
||||
break;
|
||||
case "Lilita One":
|
||||
fontDef = await getCSSFontDefinition(7);
|
||||
break;
|
||||
case "Comic Shanns":
|
||||
fontDef = await getCSSFontDefinition(8);
|
||||
break;
|
||||
case "Liberation Sans":
|
||||
fontDef = await getCSSFontDefinition(9);
|
||||
break;
|
||||
case "":
|
||||
fontDef = "";
|
||||
@@ -784,7 +890,7 @@ export class EmbeddedFilesLoader {
|
||||
? fileCache.frontmatter[FRONTMATTER_KEYS["md-css"].name] ?? ""
|
||||
: "";
|
||||
let frontmatterCSSisAfile = false;
|
||||
if (style && style != "") {
|
||||
if (style && style !== "") {
|
||||
const f = plugin.app.metadataCache.getFirstLinkpathDest(style, file.path);
|
||||
if (f) {
|
||||
style = await plugin.app.vault.read(f);
|
||||
@@ -840,12 +946,14 @@ export class EmbeddedFilesLoader {
|
||||
mdDIV.style.display = "block";
|
||||
mdDIV.style.color = fontColor && fontColor !== "" ? fontColor : "initial";
|
||||
|
||||
await MarkdownRenderer.renderMarkdown(text, mdDIV, file.path, plugin);
|
||||
|
||||
//await MarkdownRenderer.renderMarkdown(text, mdDIV, file.path, plugin);
|
||||
await MarkdownRenderer.render(this.plugin.app,text,mdDIV,file.path,this.plugin);
|
||||
|
||||
mdDIV
|
||||
.querySelectorAll(":scope > *[class^='frontmatter']")
|
||||
.forEach((el) => mdDIV.removeChild(el));
|
||||
|
||||
await replaceBlobWithBase64(mdDIV); //because image cache returns a blob
|
||||
const internalEmbeds = Array.from(mdDIV.querySelectorAll("span[class='internal-embed']"))
|
||||
for(let i=0;i<internalEmbeds.length;i++) {
|
||||
const el = internalEmbeds[i];
|
||||
@@ -957,7 +1065,7 @@ const getSVGData = async (app: App, file: TFile, colorMap: ColorMap | null): Pro
|
||||
return svgToBase64(svg) as DataURL;
|
||||
};
|
||||
|
||||
export const generateIdFromFile = async (file: ArrayBuffer): Promise<FileId> => {
|
||||
/*export const generateIdFromFile = async (file: ArrayBuffer): Promise<FileId> => {
|
||||
let id: FileId;
|
||||
try {
|
||||
const hashBuffer = await window.crypto.subtle.digest("SHA-1", file);
|
||||
@@ -972,4 +1080,53 @@ export const generateIdFromFile = async (file: ArrayBuffer): Promise<FileId> =>
|
||||
id = fileid() as FileId;
|
||||
}
|
||||
return id;
|
||||
};*/
|
||||
|
||||
export const generateIdFromFile = async (file: ArrayBuffer, key?: string): Promise<FileId> => {
|
||||
let id: FileId;
|
||||
try {
|
||||
// Convert the file ArrayBuffer to a Uint8Array
|
||||
const fileArray = new Uint8Array(file);
|
||||
|
||||
// If a key is provided, concatenate it to the file data
|
||||
let dataToHash: Uint8Array;
|
||||
if (key) {
|
||||
const encoder = new TextEncoder();
|
||||
const keyArray = encoder.encode(key);
|
||||
dataToHash = new Uint8Array(fileArray.length + keyArray.length);
|
||||
dataToHash.set(fileArray);
|
||||
dataToHash.set(keyArray, fileArray.length);
|
||||
} else {
|
||||
dataToHash = fileArray;
|
||||
}
|
||||
|
||||
// Hash the combined data (file and key, if provided)
|
||||
const hashBuffer = await window.crypto.subtle.digest("SHA-1", dataToHash);
|
||||
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;
|
||||
};
|
||||
|
||||
const replaceBlobWithBase64 = async (divElement: HTMLDivElement): Promise<void> => {
|
||||
const images = divElement.querySelectorAll<HTMLImageElement>('img[src^="blob:app://obsidian.md"]');
|
||||
|
||||
for (let img of images) {
|
||||
const blobUrl = img.src;
|
||||
try {
|
||||
const response = await fetch(blobUrl);
|
||||
const blob = await response.blob();
|
||||
const base64 = await blobToBase64(blob);
|
||||
img.src = `data:${blob.type};base64,${base64}`;
|
||||
} catch (error) {
|
||||
console.error(`Failed to fetch or convert blob: ${blobUrl}`, error);
|
||||
}
|
||||
}
|
||||
};
|
||||
63
src/ExcalidrawLib.d.ts
vendored
@@ -1,8 +1,9 @@
|
||||
import { RestoredDataState } from "@zsviczian/excalidraw/types/excalidraw/data/restore";
|
||||
import { ImportedDataState } from "@zsviczian/excalidraw/types/excalidraw/data/types";
|
||||
import { BoundingBox } from "@zsviczian/excalidraw/types/excalidraw/element/bounds";
|
||||
import { ExcalidrawBindableElement, ExcalidrawElement, ExcalidrawFrameElement, ExcalidrawTextElement, FontFamilyValues, FontString, NonDeleted, NonDeletedExcalidrawElement, Theme } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { AppState, BinaryFiles, ExportOpts, Point, Zoom } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
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 { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
|
||||
type EmbeddedLink =
|
||||
@@ -26,6 +27,7 @@ declare namespace ExcalidrawLib {
|
||||
appState?: Partial<Omit<AppState, "offsetTop" | "offsetLeft">>;
|
||||
files: BinaryFiles | null;
|
||||
maxWidthOrHeight?: number;
|
||||
exportingFrame?: ExcalidrawFrameLikeElement | null;
|
||||
getDimensions?: (
|
||||
width: number,
|
||||
height: number,
|
||||
@@ -46,6 +48,7 @@ declare namespace ExcalidrawLib {
|
||||
exportPadding?: number;
|
||||
exportingFrame: ExcalidrawFrameElement | null | undefined;
|
||||
renderEmbeddables?: boolean;
|
||||
skipInliningFonts?: boolean;
|
||||
}): Promise<SVGSVGElement>;
|
||||
|
||||
function sceneCoordsToViewportCoords(
|
||||
@@ -87,18 +90,36 @@ declare namespace ExcalidrawLib {
|
||||
elements: ExcalidrawElement[] | readonly NonDeleted<ExcalidrawElement>[],
|
||||
): BoundingBox;
|
||||
|
||||
function getContainerElement(
|
||||
element: ExcalidrawTextElement | null,
|
||||
elementsMap: ElementsMap,
|
||||
): ExcalidrawTextContainer | null;
|
||||
|
||||
function refreshTextDimensions(
|
||||
textElement: ExcalidrawTextElement,
|
||||
container: ExcalidrawTextContainer | null,
|
||||
elementsMap: ElementsMap,
|
||||
text: string,
|
||||
): {
|
||||
text: string,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
};
|
||||
|
||||
function getMaximumGroups(
|
||||
elements: ExcalidrawElement[],
|
||||
elementsMap: ElementsMap,
|
||||
): ExcalidrawElement[][];
|
||||
|
||||
function measureText(
|
||||
text: string,
|
||||
font: FontString,
|
||||
lineHeight: number,
|
||||
): { width: number; height: number; baseline: number };
|
||||
|
||||
function getDefaultLineHeight(fontFamily: FontFamilyValues): number;
|
||||
): { width: number; height: number; };
|
||||
|
||||
function getLineHeight (fontFamily: FontFamilyValues):number;
|
||||
function wrapText(text: string, font: FontString, maxWidth: number): string;
|
||||
|
||||
function getFontString({
|
||||
@@ -109,6 +130,13 @@ declare namespace ExcalidrawLib {
|
||||
fontFamily: FontFamilyValues;
|
||||
}): FontString;
|
||||
|
||||
|
||||
function getFontFamilyString ({
|
||||
fontFamily,
|
||||
}: {
|
||||
fontFamily: number;
|
||||
}): string;
|
||||
|
||||
function getBoundTextMaxWidth(container: ExcalidrawElement): number;
|
||||
|
||||
function exportToBlob(
|
||||
@@ -136,4 +164,27 @@ declare namespace ExcalidrawLib {
|
||||
files?: any;
|
||||
error?: string;
|
||||
} | undefined>;
|
||||
}
|
||||
|
||||
var getSceneVersion: any;
|
||||
var Excalidraw: any;
|
||||
var MainMenu: any;
|
||||
var WelcomeScreen: any;
|
||||
var TTDDialogTrigger: any;
|
||||
var TTDDialog: any;
|
||||
var DiagramToCodePlugin: (props: {
|
||||
generate: GenerateDiagramToCode;
|
||||
}) => any;
|
||||
|
||||
function getDataURL(file: Blob | File): Promise<DataURL>;
|
||||
function destroyObsidianUtils(): void;
|
||||
function registerLocalFont(fontMetrics: FontMetadata, uri: string): void;
|
||||
function getFontFamilies(): string[];
|
||||
function registerFontsInCSS(): Promise<void>;
|
||||
function getCSSFontDefinition(fontFamily: number): Promise<string>;
|
||||
function getTextFromElements (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
separator?: string,
|
||||
): string;
|
||||
function safelyParseJSON (json: string): Record<string, any> | null;
|
||||
}
|
||||
|
||||
|
||||
14
src/LaTeX.ts
@@ -7,21 +7,18 @@ import {RegisterHTMLHandler} from 'mathjax-full/js/handlers/html.js';
|
||||
import {AllPackages} from 'mathjax-full/js/input/tex/AllPackages.js';
|
||||
|
||||
import ExcalidrawView from "./ExcalidrawView";
|
||||
import ExcalidrawPlugin from "./main";
|
||||
import { FileData, MimeType } from "./EmbeddedFileLoader";
|
||||
import { FileId } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { getImageSize, svgToBase64 } from "./utils/Utils";
|
||||
import { fileid } from "./constants/constants";
|
||||
import { TFile } from "obsidian";
|
||||
import { MathDocument } from "mathjax-full/js/core/MathDocument";
|
||||
import { stripVTControlCharacters } from "util";
|
||||
|
||||
export const updateEquation = async (
|
||||
equation: string,
|
||||
fileId: string,
|
||||
view: ExcalidrawView,
|
||||
addFiles: Function,
|
||||
plugin: ExcalidrawPlugin,
|
||||
) => {
|
||||
const data = await tex2dataURL(equation);
|
||||
if (data) {
|
||||
@@ -40,11 +37,15 @@ export const updateEquation = async (
|
||||
};
|
||||
|
||||
let adaptor: LiteAdaptor;
|
||||
let input: TeX<unknown, unknown, unknown>;
|
||||
let output: SVG<unknown, unknown, unknown>;
|
||||
let html: MathDocument<any, any, any>;
|
||||
let preamble: string;
|
||||
|
||||
export const clearMathJaxVariables = () => {
|
||||
adaptor = null;
|
||||
html = null;
|
||||
preamble = null;
|
||||
};
|
||||
|
||||
//https://github.com/xldenis/obsidian-latex/blob/master/main.ts
|
||||
const loadPreamble = async () => {
|
||||
const file = app.vault.getAbstractFileByPath("preamble.sty");
|
||||
@@ -63,6 +64,9 @@ export async function tex2dataURL(
|
||||
created: number;
|
||||
size: { height: number; width: number };
|
||||
}> {
|
||||
let input: TeX<unknown, unknown, unknown>;
|
||||
let output: SVG<unknown, unknown, unknown>;
|
||||
|
||||
if(!adaptor) {
|
||||
await loadPreamble();
|
||||
adaptor = liteAdaptor();
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import {
|
||||
App,
|
||||
MarkdownPostProcessorContext,
|
||||
MetadataCache,
|
||||
PaneType,
|
||||
TFile,
|
||||
Vault,
|
||||
} from "obsidian";
|
||||
import { RERENDER_EVENT } from "./constants/constants";
|
||||
import { DEVICE, RERENDER_EVENT } from "./constants/constants";
|
||||
import { EmbeddedFilesLoader } from "./EmbeddedFileLoader";
|
||||
import { createPNG, createSVG } from "./ExcalidrawAutomate";
|
||||
import { ExportSettings } from "./ExcalidrawView";
|
||||
import ExcalidrawPlugin from "./main";
|
||||
import {getIMGFilename,} from "./utils/FileUtils";
|
||||
import {
|
||||
embedFontsInSVG,
|
||||
getEmbeddedFilenameParts,
|
||||
getExportTheme,
|
||||
getQuickImagePreview,
|
||||
@@ -26,7 +26,7 @@ import { getParentOfClass, isObsidianThemeDark, getFileCSSClasses } from "./util
|
||||
import { linkClickModifierType } from "./utils/ModifierkeyHelper";
|
||||
import { ImageKey, imageCache } from "./utils/ImageCache";
|
||||
import { FILENAMEPARTS, PreviewImageType } from "./utils/UtilTypes";
|
||||
import { CustomMutationObserver, isDebugMode } from "./utils/DebugHelper";
|
||||
import { CustomMutationObserver, debug, DEBUGGING } from "./utils/DebugHelper";
|
||||
import { getExcalidrawFileForwardLinks } from "./utils/ExcalidrawViewUtils";
|
||||
import { linkPrompt } from "./dialogs/Prompt";
|
||||
|
||||
@@ -39,21 +39,34 @@ interface imgElementAttributes {
|
||||
}
|
||||
|
||||
let plugin: ExcalidrawPlugin;
|
||||
let app: App;
|
||||
let vault: Vault;
|
||||
let metadataCache: MetadataCache;
|
||||
const DEBUGGING_MPP = false;
|
||||
|
||||
|
||||
const getDefaultWidth = (plugin: ExcalidrawPlugin): string => {
|
||||
const width = parseInt(plugin.settings.width);
|
||||
if (isNaN(width) || width === 0 || width === null) {
|
||||
if(getDefaultHeight(plugin)!=="") return "";
|
||||
return "400";
|
||||
}
|
||||
return plugin.settings.width;
|
||||
};
|
||||
|
||||
const getDefaultHeight = (plugin: ExcalidrawPlugin): string => {
|
||||
const height = parseInt(plugin.settings.height);
|
||||
if (isNaN(height) || height === 0 || height === null) {
|
||||
return "";
|
||||
}
|
||||
return plugin.settings.height;
|
||||
};
|
||||
|
||||
export const initializeMarkdownPostProcessor = (p: ExcalidrawPlugin) => {
|
||||
plugin = p;
|
||||
vault = p.app.vault;
|
||||
metadataCache = p.app.metadataCache;
|
||||
app = plugin.app;
|
||||
vault = app.vault;
|
||||
metadataCache = app.metadataCache;
|
||||
};
|
||||
|
||||
const _getPNG = async ({imgAttributes,filenameParts,theme,cacheReady,img,file,exportSettings,loader}:{
|
||||
@@ -66,6 +79,7 @@ const _getPNG = async ({imgAttributes,filenameParts,theme,cacheReady,img,file,ex
|
||||
exportSettings: ExportSettings,
|
||||
loader: EmbeddedFilesLoader,
|
||||
}):Promise<HTMLImageElement> => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(_getPNG, `MarkdownPostProcessor.ts > _getPNG`);
|
||||
const width = parseInt(imgAttributes.fwidth);
|
||||
const scale = width >= 2400
|
||||
? 5
|
||||
@@ -77,7 +91,14 @@ const _getPNG = async ({imgAttributes,filenameParts,theme,cacheReady,img,file,ex
|
||||
? 2
|
||||
: 1;
|
||||
|
||||
const cacheKey = {...filenameParts, isDark: theme==="dark", previewImageType: PreviewImageType.PNG, scale};
|
||||
const cacheKey = {
|
||||
...filenameParts,
|
||||
isDark: theme==="dark",
|
||||
previewImageType: PreviewImageType.PNG,
|
||||
scale,
|
||||
isTransparent: !exportSettings.withBackground,
|
||||
inlineFonts: true, //though for PNG this makes no difference, but the key requires it
|
||||
};
|
||||
|
||||
if(cacheReady) {
|
||||
const src = await imageCache.getImageFromCache(cacheKey);
|
||||
@@ -96,11 +117,13 @@ const _getPNG = async ({imgAttributes,filenameParts,theme,cacheReady,img,file,ex
|
||||
const png =
|
||||
quickPNG ??
|
||||
(await createPNG(
|
||||
(filenameParts.hasGroupref || filenameParts.hasFrameref)
|
||||
(filenameParts.hasGroupref || filenameParts.hasFrameref || filenameParts.hasClippedFrameref)
|
||||
? filenameParts.filepath + filenameParts.linkpartReference
|
||||
: file.path,
|
||||
scale,
|
||||
exportSettings,
|
||||
filenameParts.hasClippedFrameref
|
||||
? { ...exportSettings, frameRendering: { enabled: true, name: false, outline: false, clip: true}}
|
||||
: exportSettings,
|
||||
loader,
|
||||
theme,
|
||||
null,
|
||||
@@ -123,9 +146,15 @@ const setStyle = ({element,imgAttributes,onCanvas}:{
|
||||
onCanvas: boolean,
|
||||
}
|
||||
) => {
|
||||
let style = `max-width:${imgAttributes.fwidth}${imgAttributes.fwidth.match(/\d$/) ? "px":""}; `; //width:100%;`; //removed !important https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/886
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(setStyle, `MarkdownPostProcessor.ts > setStyle`);
|
||||
let style = "";
|
||||
if(imgAttributes.fwidth) {
|
||||
style = `max-width:${imgAttributes.fwidth}${imgAttributes.fwidth.match(/\d$/) ? "px":""}; `; //width:100%;`; //removed !important https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/886
|
||||
} else {
|
||||
style = "width: fit-content;"
|
||||
}
|
||||
if (imgAttributes.fheight) {
|
||||
style += `height:${imgAttributes.fheight}px;`;
|
||||
style += `${imgAttributes.fwidth?"min-":"max-"}height:${imgAttributes.fheight}px;`;
|
||||
}
|
||||
if(!onCanvas) element.setAttribute("style", style);
|
||||
element.classList.add(...Array.from(imgAttributes.style))
|
||||
@@ -149,7 +178,17 @@ const _getSVGIMG = async ({filenameParts,theme,cacheReady,img,file,exportSetting
|
||||
exportSettings: ExportSettings,
|
||||
loader: EmbeddedFilesLoader,
|
||||
}):Promise<HTMLImageElement> => {
|
||||
const cacheKey = {...filenameParts, isDark: theme==="dark", previewImageType: PreviewImageType.SVGIMG, scale:1};
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(_getSVGIMG, `MarkdownPostProcessor.ts > _getSVGIMG`);
|
||||
exportSettings.skipInliningFonts = false;
|
||||
const cacheKey = {
|
||||
...filenameParts,
|
||||
isDark: theme==="dark",
|
||||
previewImageType: PreviewImageType.SVGIMG,
|
||||
scale:1,
|
||||
isTransparent: !exportSettings.withBackground,
|
||||
inlineFonts: !exportSettings.skipInliningFonts,
|
||||
};
|
||||
|
||||
if(cacheReady) {
|
||||
const src = await imageCache.getImageFromCache(cacheKey);
|
||||
if(src && typeof src === "string") {
|
||||
@@ -168,13 +207,15 @@ const _getSVGIMG = async ({filenameParts,theme,cacheReady,img,file,exportSetting
|
||||
}
|
||||
}
|
||||
|
||||
let svg = convertSVGStringToElement((
|
||||
const svg = convertSVGStringToElement((
|
||||
await createSVG(
|
||||
filenameParts.hasGroupref || filenameParts.hasBlockref || filenameParts.hasSectionref || filenameParts.hasFrameref
|
||||
filenameParts.hasGroupref || filenameParts.hasBlockref || filenameParts.hasSectionref || filenameParts.hasFrameref || filenameParts.hasClippedFrameref
|
||||
? filenameParts.filepath + filenameParts.linkpartReference
|
||||
: file.path,
|
||||
true,
|
||||
exportSettings,
|
||||
filenameParts?.hasClippedFrameref
|
||||
? { ...exportSettings, frameRendering: { enabled: true, name: false, outline: false, clip: true}}
|
||||
: exportSettings,
|
||||
loader,
|
||||
theme,
|
||||
null,
|
||||
@@ -190,7 +231,6 @@ const _getSVGIMG = async ({filenameParts,theme,cacheReady,img,file,exportSetting
|
||||
return null;
|
||||
}
|
||||
|
||||
svg = embedFontsInSVG(svg, plugin, false);
|
||||
//need to remove width and height attributes to support area= embeds
|
||||
svg.removeAttribute("width");
|
||||
svg.removeAttribute("height");
|
||||
@@ -206,20 +246,31 @@ const _getSVGNative = async ({filenameParts,theme,cacheReady,containerElement,fi
|
||||
exportSettings: ExportSettings,
|
||||
loader: EmbeddedFilesLoader,
|
||||
}):Promise<HTMLDivElement> => {
|
||||
const cacheKey = {...filenameParts, isDark: theme==="dark", previewImageType: PreviewImageType.SVG, scale:1};
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(_getSVGNative, `MarkdownPostProcessor.ts > _getSVGNative`);
|
||||
exportSettings.skipInliningFonts = false;
|
||||
const cacheKey = {
|
||||
...filenameParts,
|
||||
isDark: theme==="dark",
|
||||
previewImageType: PreviewImageType.SVG,
|
||||
scale:1,
|
||||
isTransparent: !exportSettings.withBackground,
|
||||
inlineFonts: !exportSettings.skipInliningFonts,
|
||||
};
|
||||
let maybeSVG;
|
||||
if(cacheReady) {
|
||||
maybeSVG = await imageCache.getImageFromCache(cacheKey);
|
||||
}
|
||||
|
||||
let svg = maybeSVG && (maybeSVG instanceof SVGSVGElement)
|
||||
const svg = (maybeSVG && (maybeSVG instanceof SVGSVGElement))
|
||||
? maybeSVG
|
||||
: convertSVGStringToElement((await createSVG(
|
||||
filenameParts.hasGroupref || filenameParts.hasBlockref || filenameParts.hasSectionref || filenameParts.hasFrameref
|
||||
filenameParts.hasGroupref || filenameParts.hasBlockref || filenameParts.hasSectionref || filenameParts.hasFrameref || filenameParts.hasClippedFrameref
|
||||
? filenameParts.filepath + filenameParts.linkpartReference
|
||||
: file.path,
|
||||
false,
|
||||
exportSettings,
|
||||
filenameParts.hasClippedFrameref
|
||||
? { ...exportSettings, frameRendering: { enabled: true, name: false, outline: false, clip: true}}
|
||||
: exportSettings,
|
||||
loader,
|
||||
theme,
|
||||
null,
|
||||
@@ -236,11 +287,14 @@ const _getSVGNative = async ({filenameParts,theme,cacheReady,containerElement,fi
|
||||
return null;
|
||||
}
|
||||
|
||||
svg = embedFontsInSVG(svg, plugin, true);
|
||||
//cache SVG should have the width and height parameters and not the embedded font
|
||||
if(!Boolean(maybeSVG)) {
|
||||
cacheReady && imageCache.addImageToCache(cacheKey,"", svg);
|
||||
}
|
||||
|
||||
svg.removeAttribute("width");
|
||||
svg.removeAttribute("height");
|
||||
containerElement.append(svg);
|
||||
cacheReady && imageCache.addImageToCache(cacheKey,"", svg);
|
||||
return containerElement;
|
||||
}
|
||||
|
||||
@@ -255,6 +309,7 @@ const getIMG = async (
|
||||
imgAttributes: imgElementAttributes,
|
||||
onCanvas: boolean = false,
|
||||
): Promise<HTMLImageElement | HTMLDivElement> => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(getIMG, `MarkdownPostProcessor.ts > getIMG`, imgAttributes);
|
||||
let file = imgAttributes.file;
|
||||
if (!imgAttributes.file) {
|
||||
const f = vault.getAbstractFileByPath(imgAttributes.fname?.split("#")[0]);
|
||||
@@ -302,22 +357,23 @@ const getIMG = async (
|
||||
case PreviewImageType.PNG: {
|
||||
const img = createEl("img");
|
||||
setStyle({element:img,imgAttributes,onCanvas});
|
||||
return _getPNG({imgAttributes,filenameParts,theme,cacheReady,img,file,exportSettings,loader});
|
||||
return await _getPNG({imgAttributes,filenameParts,theme,cacheReady,img,file,exportSettings,loader});
|
||||
}
|
||||
case PreviewImageType.SVGIMG: {
|
||||
const img = createEl("img");
|
||||
setStyle({element:img,imgAttributes,onCanvas});
|
||||
return _getSVGIMG({filenameParts,theme,cacheReady,img,file,exportSettings,loader});
|
||||
return await _getSVGIMG({filenameParts,theme,cacheReady,img,file,exportSettings,loader});
|
||||
}
|
||||
case PreviewImageType.SVG: {
|
||||
const img = createEl("div");
|
||||
setStyle({element:img,imgAttributes,onCanvas});
|
||||
return _getSVGNative({filenameParts,theme,cacheReady,containerElement: img,file,exportSettings,loader});
|
||||
return await _getSVGNative({filenameParts,theme,cacheReady,containerElement: img,file,exportSettings,loader});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
const blob = new Blob([svgString], { type: 'image/svg+xml' });
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
@@ -330,6 +386,7 @@ const createImgElement = async (
|
||||
attr: imgElementAttributes,
|
||||
onCanvas: boolean = false,
|
||||
) :Promise<HTMLElement> => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(createImgElement, `MarkdownPostProcessor.ts > createImgElement`);
|
||||
const imgOrDiv = await getIMG(attr,onCanvas);
|
||||
if(!imgOrDiv) {
|
||||
return null;
|
||||
@@ -344,7 +401,7 @@ const createImgElement = async (
|
||||
imgOrDiv.setAttribute("draggable","false");
|
||||
imgOrDiv.setAttribute("onCanvas",onCanvas?"true":"false");
|
||||
|
||||
let timer:NodeJS.Timeout;
|
||||
let timer:number;
|
||||
const clickEvent = (ev:PointerEvent) => {
|
||||
if(!(ev.target instanceof Element)) {
|
||||
return;
|
||||
@@ -363,7 +420,7 @@ const createImgElement = async (
|
||||
const linkModifier = linkClickModifierType(ev);
|
||||
if (plugin.isExcalidrawFile(f) && isMaskFile(plugin, f)) {
|
||||
(async () => {
|
||||
const linkString = `[[${f.path}${srcParts[2]?"#"+srcParts[2]:""}]] ${getExcalidrawFileForwardLinks(plugin.app, f)}`;
|
||||
const linkString = `[[${f.path}${srcParts[2]?"#"+srcParts[2]:""}]] ${getExcalidrawFileForwardLinks(plugin.app, f, new Set<string>())}`;
|
||||
const result = await linkPrompt(linkString, plugin.app);
|
||||
if(!result) return;
|
||||
const [file, linkText, subpath] = result;
|
||||
@@ -397,17 +454,20 @@ const createImgElement = async (
|
||||
eventElement.addEventListener("pointermove",(ev)=>{
|
||||
if(!timer) return;
|
||||
if(Math.abs(ev.screenX-pointerDownEvent.screenX)>10 || Math.abs(ev.screenY-pointerDownEvent.screenY)>10) {
|
||||
clearTimeout(timer);
|
||||
window.clearTimeout(timer);
|
||||
timer = null;
|
||||
}
|
||||
});
|
||||
eventElement.addEventListener("pointerdown",(ev)=>{
|
||||
if(imgOrDiv?.parentElement?.hasClass("canvas-node-content")) return;
|
||||
timer = setTimeout(()=>clickEvent(ev),500);
|
||||
//@ts-ignore
|
||||
const PLUGIN = app.plugins.plugins["obsidian-excalidraw-plugin"] as ExcalidrawPlugin;
|
||||
const timeoutValue = DEVICE.isDesktop ? PLUGIN.settings.longPressDesktop : PLUGIN.settings.longPressMobile;
|
||||
timer = window.setTimeout(()=>clickEvent(ev),timeoutValue);
|
||||
pointerDownEvent = ev;
|
||||
});
|
||||
eventElement.addEventListener("pointerup",()=>{
|
||||
if(timer) clearTimeout(timer);
|
||||
if(timer) window.clearTimeout(timer);
|
||||
timer = null;
|
||||
})
|
||||
eventElement.addEventListener("dblclick",clickEvent);
|
||||
@@ -454,6 +514,7 @@ const createImageDiv = async (
|
||||
attr: imgElementAttributes,
|
||||
onCanvas: boolean = false
|
||||
): Promise<HTMLDivElement> => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(createImageDiv, `MarkdownPostProcessor.ts > createImageDiv`);
|
||||
const img = await createImgElement(attr, onCanvas);
|
||||
return createDiv(attr.style.join(" "), (el) => el.append(img));
|
||||
};
|
||||
@@ -462,6 +523,7 @@ const processReadingMode = async (
|
||||
embeddedItems: NodeListOf<Element> | [HTMLElement],
|
||||
ctx: MarkdownPostProcessorContext,
|
||||
) => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING_MPP && debug(processReadingMode, `MarkdownPostProcessor.ts > processReadingMode`);
|
||||
//We are processing a non-excalidraw file in reading mode
|
||||
//Embedded files will be displayed in an .internal-embed container
|
||||
|
||||
@@ -493,6 +555,7 @@ const processReadingMode = async (
|
||||
};
|
||||
|
||||
const processInternalEmbed = async (internalEmbedEl: Element, file: TFile ):Promise<HTMLDivElement> => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING_MPP && debug(processInternalEmbed, `MarkdownPostProcessor.ts > processInternalEmbed`, internalEmbedEl);
|
||||
const attr: imgElementAttributes = {
|
||||
fname: "",
|
||||
fheight: "",
|
||||
@@ -510,9 +573,11 @@ const processInternalEmbed = async (internalEmbedEl: Element, file: TFile ):Prom
|
||||
internalEmbedEl.addClass("image-embed");
|
||||
|
||||
attr.fwidth = internalEmbedEl.getAttribute("width")
|
||||
? internalEmbedEl.getAttribute("width")
|
||||
: getDefaultWidth(plugin);
|
||||
attr.fheight = internalEmbedEl.getAttribute("height");
|
||||
? internalEmbedEl.getAttribute("width")
|
||||
: getDefaultWidth(plugin);
|
||||
attr.fheight = internalEmbedEl.getAttribute("height")
|
||||
? internalEmbedEl.getAttribute("height")
|
||||
: getDefaultHeight(plugin);
|
||||
let alt = internalEmbedEl.getAttribute("alt");
|
||||
attr.style = ["excalidraw-svg"];
|
||||
processAltText(src.split("#")[0],alt,attr);
|
||||
@@ -527,6 +592,7 @@ const processAltText = (
|
||||
alt:string,
|
||||
attr: imgElementAttributes
|
||||
) => {
|
||||
(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*%?))?\|?(.*)/);
|
||||
@@ -546,17 +612,22 @@ const processAltText = (
|
||||
}
|
||||
|
||||
const isTextOnlyEmbed = (internalEmbedEl: Element):boolean => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(isTextOnlyEmbed, `MarkdownPostProcessor.ts > isTextOnlyEmbed`);
|
||||
const src = internalEmbedEl.getAttribute("src");
|
||||
if(!src) return true; //technically this does not mean this is a text only embed, but still should abort further processing
|
||||
const fnameParts = getEmbeddedFilenameParts(src);
|
||||
return !(fnameParts.hasArearef || fnameParts.hasGroupref || fnameParts.hasFrameref) &&
|
||||
return !(fnameParts.hasArearef || fnameParts.hasGroupref || fnameParts.hasFrameref || fnameParts.hasClippedFrameref) &&
|
||||
(fnameParts.hasBlockref || fnameParts.hasSectionref)
|
||||
}
|
||||
|
||||
const tmpObsidianWYSIWYG = async (
|
||||
el: HTMLElement,
|
||||
ctx: MarkdownPostProcessorContext,
|
||||
isPrinting: boolean,
|
||||
isMarkdownReadingMode: boolean,
|
||||
isHoverPopover: boolean,
|
||||
) => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING_MPP && debug(tmpObsidianWYSIWYG, `MarkdownPostProcessor.ts > tmpObsidianWYSIWYG`);
|
||||
const file = app.vault.getAbstractFileByPath(ctx.sourcePath);
|
||||
if(!(file instanceof TFile)) return;
|
||||
if(!plugin.isExcalidrawFile(file)) return;
|
||||
@@ -573,8 +644,18 @@ const tmpObsidianWYSIWYG = async (
|
||||
|
||||
//@ts-ignore
|
||||
const containerEl = ctx.containerEl;
|
||||
|
||||
if(!plugin.settings.renderImageInMarkdownReadingMode && isMarkdownReadingMode) { // containerEl.parentElement?.parentElement?.hasClass("markdown-reading-view")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!plugin.settings.renderImageInMarkdownToPDF && isPrinting) { //containerEl.parentElement?.hasClass("print")) {
|
||||
return;
|
||||
}
|
||||
|
||||
let internalEmbedDiv: HTMLElement = containerEl;
|
||||
while (
|
||||
!internalEmbedDiv.hasClass("print") &&
|
||||
!internalEmbedDiv.hasClass("dataview") &&
|
||||
!internalEmbedDiv.hasClass("cm-preview-code-block") &&
|
||||
!internalEmbedDiv.hasClass("cm-embed-block") &&
|
||||
@@ -585,7 +666,7 @@ const tmpObsidianWYSIWYG = async (
|
||||
) {
|
||||
internalEmbedDiv = internalEmbedDiv.parentElement;
|
||||
}
|
||||
|
||||
|
||||
if(
|
||||
internalEmbedDiv.hasClass("dataview") ||
|
||||
internalEmbedDiv.hasClass("cm-preview-code-block") ||
|
||||
@@ -594,23 +675,50 @@ const tmpObsidianWYSIWYG = async (
|
||||
return; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/835
|
||||
}
|
||||
|
||||
|
||||
if(!plugin.settings.renderImageInHoverPreviewForMDNotes) {
|
||||
//const isHoverPopover = internalEmbedDiv.parentElement?.hasClass("hover-popover");
|
||||
const shouldOpenMD = Boolean(ctx.frontmatter?.["excalidraw-open-md"]);
|
||||
if(isHoverPopover && shouldOpenMD) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//const isPrinting = Boolean(internalEmbedDiv.hasClass("print"));
|
||||
|
||||
const attr: imgElementAttributes = {
|
||||
fname: ctx.sourcePath,
|
||||
fheight: "",
|
||||
fwidth: getDefaultWidth(plugin),
|
||||
fheight: isPrinting ? "100%" : getDefaultHeight(plugin),
|
||||
fwidth: isPrinting ? "100%" : getDefaultWidth(plugin),
|
||||
style: ["excalidraw-svg"],
|
||||
};
|
||||
|
||||
attr.file = file;
|
||||
|
||||
const markdownEmbed = internalEmbedDiv.hasClass("markdown-embed");
|
||||
const markdownReadingView = internalEmbedDiv.hasClass("markdown-reading-view");
|
||||
const markdownReadingView = isPrinting || isMarkdownReadingMode; //internalEmbedDiv.hasClass("markdown-reading-view")
|
||||
if (!internalEmbedDiv.hasClass("internal-embed") && (markdownEmbed || markdownReadingView)) {
|
||||
if(isPrinting) {
|
||||
internalEmbedDiv = containerEl;
|
||||
}
|
||||
//We are processing the markdown preview of an actual Excalidraw file
|
||||
//the excalidraw file in markdown preview mode
|
||||
const isFrontmatterDiv = Boolean(el.querySelector(".frontmatter"));
|
||||
el.empty();
|
||||
if(!isFrontmatterDiv) {
|
||||
let areaPreview = false;
|
||||
if(Boolean(ctx.frontmatter)) {
|
||||
el.empty();
|
||||
} else {
|
||||
const warningEl = el.querySelector("div>h3[data-heading^='Unable to find section #^");
|
||||
if(warningEl) {
|
||||
const ref = warningEl.getAttr("data-heading").match(/Unable to find section (#\^(?:group=|area=|frame=|clippedframe=)[^ ]*)/)?.[1];
|
||||
if(ref) {
|
||||
attr.fname = file.path + ref;
|
||||
areaPreview = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if(!isFrontmatterDiv && !areaPreview) {
|
||||
if(el.parentElement === containerEl) containerEl.removeChild(el);
|
||||
return;
|
||||
}
|
||||
@@ -652,22 +760,22 @@ const tmpObsidianWYSIWYG = async (
|
||||
internalEmbedDiv.appendChild(imgDiv);
|
||||
|
||||
//timer to avoid the image flickering when the user is typing
|
||||
let timer: NodeJS.Timeout = null;
|
||||
let timer: number = null;
|
||||
const markdownObserverFn: MutationCallback = (m) => {
|
||||
if (!["alt", "width", "height"].contains(m[0]?.attributeName)) {
|
||||
return;
|
||||
}
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
window.clearTimeout(timer);
|
||||
}
|
||||
timer = setTimeout(async () => {
|
||||
timer = window.setTimeout(async () => {
|
||||
timer = null;
|
||||
internalEmbedDiv.empty();
|
||||
const imgDiv = await processInternalEmbed(internalEmbedDiv,file);
|
||||
internalEmbedDiv.appendChild(imgDiv);
|
||||
}, 500);
|
||||
}
|
||||
const observer = isDebugMode
|
||||
const observer = DEBUGGING
|
||||
? new CustomMutationObserver(markdownObserverFn, "markdowPostProcessorObserverFn")
|
||||
: new MutationObserver(markdownObserverFn);
|
||||
observer.observe(internalEmbedDiv, {
|
||||
@@ -675,6 +783,7 @@ const tmpObsidianWYSIWYG = async (
|
||||
});
|
||||
};
|
||||
|
||||
const docIDs = new Set<string>();
|
||||
/**
|
||||
*
|
||||
* @param el
|
||||
@@ -684,12 +793,43 @@ export const markdownPostProcessor = async (
|
||||
el: HTMLElement,
|
||||
ctx: MarkdownPostProcessorContext,
|
||||
) => {
|
||||
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");
|
||||
if(isPrinting && isFrontmatter) {
|
||||
return;
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
const containerEl = ctx.containerEl;
|
||||
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING_MPP && debug(markdownPostProcessor, `MarkdownPostProcessor.ts > markdownPostProcessor`, ctx, el);
|
||||
|
||||
//check to see if we are rendering in editing mode or live preview
|
||||
//if yes, then there should be no .internal-embed containers
|
||||
//if yes, then there should be no .internal-embed containers
|
||||
const isMarkdownReadingMode = Boolean(containerEl && getParentOfClass(containerEl, "markdown-reading-view"));
|
||||
const isHoverPopover = Boolean(containerEl && getParentOfClass(containerEl, "hover-popover"));
|
||||
const isPreview = (isHoverPopover && Boolean(ctx?.frontmatter?.["excalidraw-open-md"]) && !plugin.settings.renderImageInHoverPreviewForMDNotes);
|
||||
const embeddedItems = el.querySelectorAll(".internal-embed");
|
||||
if (embeddedItems.length === 0) {
|
||||
tmpObsidianWYSIWYG(el, ctx);
|
||||
|
||||
if(isPrinting && plugin.settings.renderImageInMarkdownToPDF) {
|
||||
await tmpObsidianWYSIWYG(el, ctx, isPrinting, isMarkdownReadingMode, isHoverPopover);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isPreview && embeddedItems.length === 0) {
|
||||
if(isFrontmatter) {
|
||||
docIDs.add(ctx.docId);
|
||||
} else {
|
||||
if(docIDs.has(ctx.docId) && !el.hasChildNodes()) {
|
||||
docIDs.delete(ctx.docId);
|
||||
}
|
||||
const isAreaGroupFrameRef = el.querySelectorAll('[data-heading^="Unable to find"]').length === 1;
|
||||
if(!isAreaGroupFrameRef) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
await tmpObsidianWYSIWYG(el, ctx, isPrinting, isMarkdownReadingMode, isHoverPopover);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -698,7 +838,7 @@ export const markdownPostProcessor = async (
|
||||
//transcluded text element or some other transcluded content inside the Excalidraw file
|
||||
//in reading mode these elements should be hidden
|
||||
const excalidrawFile = Boolean(ctx.frontmatter?.hasOwnProperty("excalidraw-plugin"));
|
||||
if (excalidrawFile) {
|
||||
if (!(isPreview || isMarkdownReadingMode || isPrinting) && excalidrawFile) {
|
||||
el.style.display = "none";
|
||||
return;
|
||||
}
|
||||
@@ -760,10 +900,10 @@ const legacyExcalidrawPopoverObserverFn: MutationCallback = async (m) => {
|
||||
if (!plugin.hover.linkText) {
|
||||
return;
|
||||
}
|
||||
if (m.length != 1) {
|
||||
if (m.length !== 1) {
|
||||
return;
|
||||
}
|
||||
if (m[0].addedNodes.length != 1) {
|
||||
if (m[0].addedNodes.length !== 1) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
@@ -775,7 +915,7 @@ const legacyExcalidrawPopoverObserverFn: MutationCallback = async (m) => {
|
||||
node.empty();
|
||||
|
||||
//this div will be on top of original DIV. By stopping the propagation of the click
|
||||
//I prevent the default Obsidian feature of openning the link in the native app
|
||||
//I prevent the default Obsidian feature of opening the link in the native app
|
||||
const img = await getIMG({
|
||||
file,
|
||||
fname: file.path,
|
||||
@@ -800,7 +940,7 @@ const legacyExcalidrawPopoverObserverFn: MutationCallback = async (m) => {
|
||||
node.appendChild(div);
|
||||
};
|
||||
|
||||
export const legacyExcalidrawPopoverObserver = isDebugMode
|
||||
export const legacyExcalidrawPopoverObserver = DEBUGGING
|
||||
? new CustomMutationObserver(legacyExcalidrawPopoverObserverFn, "legacyExcalidrawPopoverObserverFn")
|
||||
: new MutationObserver(legacyExcalidrawPopoverObserverFn);
|
||||
|
||||
|
||||
147
src/Scripts.ts
@@ -12,6 +12,8 @@ import { ButtonDefinition, GenericInputPrompt, GenericSuggester } from "./dialog
|
||||
import { getIMGFilename } from "./utils/FileUtils";
|
||||
import { splitFolderAndFilename } from "./utils/FileUtils";
|
||||
import { getEA } from "src";
|
||||
import { ExcalidrawAutomate } from "./ExcalidrawAutomate";
|
||||
import { WeakArray } from "./utils/WeakArray";
|
||||
|
||||
export type ScriptIconMap = {
|
||||
[key: string]: { name: string; group: string; svgString: string };
|
||||
@@ -22,6 +24,7 @@ export class ScriptEngine {
|
||||
private scriptPath: string;
|
||||
//https://stackoverflow.com/questions/60218638/how-to-force-re-render-if-map-value-changes
|
||||
public scriptIconMap: ScriptIconMap;
|
||||
eaInstances = new WeakArray<ExcalidrawAutomate>();
|
||||
|
||||
constructor(plugin: ExcalidrawPlugin) {
|
||||
this.plugin = plugin;
|
||||
@@ -30,65 +33,95 @@ export class ScriptEngine {
|
||||
this.registerEventHandlers();
|
||||
}
|
||||
|
||||
registerEventHandlers() {
|
||||
const handleSvgFileChange = (path: string) => {
|
||||
if (!path.endsWith(".svg")) {
|
||||
return;
|
||||
public removeViewEAs(view: ExcalidrawView) {
|
||||
const eas = new Set<ExcalidrawAutomate>();
|
||||
this.eaInstances.forEach((ea) => {
|
||||
if (ea.targetView === view) {
|
||||
eas.add(ea);
|
||||
ea.destroy();
|
||||
}
|
||||
const scriptFile = app.vault.getAbstractFileByPath(
|
||||
getIMGFilename(path, "md"),
|
||||
);
|
||||
if (scriptFile && scriptFile instanceof TFile) {
|
||||
this.unloadScript(this.getScriptName(scriptFile), scriptFile.path);
|
||||
this.loadScript(scriptFile);
|
||||
}
|
||||
};
|
||||
});
|
||||
this.eaInstances.removeObjects(eas);
|
||||
}
|
||||
|
||||
const deleteEventHandler = async (file: TFile) => {
|
||||
if (!(file instanceof TFile)) {
|
||||
return;
|
||||
}
|
||||
if (!file.path.startsWith(this.scriptPath)) {
|
||||
return;
|
||||
}
|
||||
this.unloadScript(this.getScriptName(file), file.path);
|
||||
handleSvgFileChange(file.path);
|
||||
};
|
||||
this.plugin.registerEvent(
|
||||
app.vault.on("delete", deleteEventHandler),
|
||||
public destroy() {
|
||||
this.eaInstances.forEach((ea) => ea.destroy());
|
||||
this.eaInstances.clear();
|
||||
this.eaInstances = null;
|
||||
this.scriptIconMap = null;
|
||||
this.plugin = null;
|
||||
this.scriptPath = null;
|
||||
}
|
||||
|
||||
private handleSvgFileChange (path: string) {
|
||||
if (!path.endsWith(".svg")) {
|
||||
return;
|
||||
}
|
||||
const scriptFile = app.vault.getAbstractFileByPath(
|
||||
getIMGFilename(path, "md"),
|
||||
);
|
||||
if (scriptFile && scriptFile instanceof TFile) {
|
||||
this.unloadScript(this.getScriptName(scriptFile), scriptFile.path);
|
||||
this.loadScript(scriptFile);
|
||||
}
|
||||
}
|
||||
|
||||
const createEventHandler = async (file: TFile) => {
|
||||
if (!(file instanceof TFile)) {
|
||||
return;
|
||||
}
|
||||
if (!file.path.startsWith(this.scriptPath)) {
|
||||
return;
|
||||
}
|
||||
private async deleteEventHandler (file: TFile) {
|
||||
if (!(file instanceof TFile)) {
|
||||
return;
|
||||
}
|
||||
if (!file.path.startsWith(this.scriptPath)) {
|
||||
return;
|
||||
}
|
||||
this.unloadScript(this.getScriptName(file), file.path);
|
||||
this.handleSvgFileChange(file.path);
|
||||
};
|
||||
|
||||
private async createEventHandler (file: TFile) {
|
||||
if (!(file instanceof TFile)) {
|
||||
return;
|
||||
}
|
||||
if (!file.path.startsWith(this.scriptPath)) {
|
||||
return;
|
||||
}
|
||||
this.loadScript(file);
|
||||
this.handleSvgFileChange(file.path);
|
||||
};
|
||||
|
||||
private async renameEventHandler (file: TAbstractFile, oldPath: string) {
|
||||
if (!(file instanceof TFile)) {
|
||||
return;
|
||||
}
|
||||
const oldFileIsScript = oldPath.startsWith(this.scriptPath);
|
||||
const newFileIsScript = file.path.startsWith(this.scriptPath);
|
||||
if (oldFileIsScript) {
|
||||
this.unloadScript(this.getScriptName(oldPath), oldPath);
|
||||
this.handleSvgFileChange(oldPath);
|
||||
}
|
||||
if (newFileIsScript) {
|
||||
this.loadScript(file);
|
||||
handleSvgFileChange(file.path);
|
||||
};
|
||||
this.plugin.registerEvent(
|
||||
app.vault.on("create", createEventHandler),
|
||||
);
|
||||
this.handleSvgFileChange(file.path);
|
||||
}
|
||||
}
|
||||
|
||||
const renameEventHandler = async (file: TAbstractFile, oldPath: string) => {
|
||||
if (!(file instanceof TFile)) {
|
||||
return;
|
||||
}
|
||||
const oldFileIsScript = oldPath.startsWith(this.scriptPath);
|
||||
const newFileIsScript = file.path.startsWith(this.scriptPath);
|
||||
if (oldFileIsScript) {
|
||||
this.unloadScript(this.getScriptName(oldPath), oldPath);
|
||||
handleSvgFileChange(oldPath);
|
||||
}
|
||||
if (newFileIsScript) {
|
||||
this.loadScript(file);
|
||||
handleSvgFileChange(file.path);
|
||||
}
|
||||
};
|
||||
registerEventHandlers() {
|
||||
this.plugin.registerEvent(
|
||||
app.vault.on("rename", renameEventHandler),
|
||||
this.plugin.app.vault.on(
|
||||
"delete",
|
||||
(file: TFile)=>this.deleteEventHandler(file)
|
||||
),
|
||||
);
|
||||
this.plugin.registerEvent(
|
||||
this.plugin.app.vault.on(
|
||||
"create",
|
||||
(file: TFile)=>this.createEventHandler(file)
|
||||
),
|
||||
);
|
||||
this.plugin.registerEvent(
|
||||
this.plugin.app.vault.on(
|
||||
"rename",
|
||||
(file: TAbstractFile, oldPath: string)=>this.renameEventHandler(file, oldPath)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -172,6 +205,7 @@ export class ScriptEngine {
|
||||
(async()=>{
|
||||
const script = await app.vault.read(f);
|
||||
if(script) {
|
||||
//remove YAML frontmatter if present
|
||||
this.executeScript(view, script, scriptName,f);
|
||||
}
|
||||
})()
|
||||
@@ -212,7 +246,9 @@ export class ScriptEngine {
|
||||
if (!view || !script || !title) {
|
||||
return;
|
||||
}
|
||||
script = script.replace(/^---.*?---\n/gs, "");
|
||||
const ea = getEA(view);
|
||||
this.eaInstances.push(ea);
|
||||
ea.activeScript = title;
|
||||
|
||||
//https://stackoverflow.com/questions/45381204/get-asyncfunction-constructor-in-typescript changed tsconfig to es2017
|
||||
@@ -251,7 +287,7 @@ export class ScriptEngine {
|
||||
instructions?: Instruction[],
|
||||
) =>
|
||||
ScriptEngine.suggester(
|
||||
app,
|
||||
this.plugin.app,
|
||||
displayItems,
|
||||
items,
|
||||
hint,
|
||||
@@ -263,13 +299,12 @@ export class ScriptEngine {
|
||||
new Notice(t("SCRIPT_EXECUTION_ERROR"), 4000);
|
||||
errorlog({ script: this.plugin.ea.activeScript, error: e });
|
||||
}*/
|
||||
//ea.activeScript = null;
|
||||
return result;
|
||||
return result;
|
||||
}
|
||||
|
||||
private updateToolPannels() {
|
||||
const leaves =
|
||||
app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
this.plugin.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
leaves.forEach((leaf: WorkspaceLeaf) => {
|
||||
const excalidrawView = leaf.view as ExcalidrawView;
|
||||
excalidrawView.toolsPanelRef?.current?.updateScriptIconMap(
|
||||
|
||||
3
src/constants/constSettingsTags.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const TAG_PDFEXPORT = "PDFExport";
|
||||
export const TAG_MDREADINGMODE = "MDReadingMode";
|
||||
export const TAG_AUTOEXPORT = "Autoexport";
|
||||
@@ -1,9 +1,26 @@
|
||||
import { customAlphabet } from "nanoid";
|
||||
import { DeviceType } from "../types";
|
||||
import { DeviceType } from "../types/types";
|
||||
import { ExcalidrawLib } from "../ExcalidrawLib";
|
||||
import { moment } from "obsidian";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
//This is only for backward compatibility because an early version of obsidian included an encoding to avoid fantom links from littering Obsidian graph view
|
||||
declare const PLUGIN_VERSION:string;
|
||||
export let EXCALIDRAW_PLUGIN: ExcalidrawPlugin = null;
|
||||
export const setExcalidrawPlugin = (plugin: ExcalidrawPlugin) => {
|
||||
EXCALIDRAW_PLUGIN = plugin;
|
||||
};
|
||||
export const THEME = {
|
||||
LIGHT: "light",
|
||||
DARK: "dark",
|
||||
} as const;
|
||||
|
||||
const MD_EXCALIDRAW = "# Excalidraw Data";
|
||||
const MD_TEXTELEMENTS = "## Text Elements";
|
||||
const MD_ELEMENTLINKS = "## Element Links";
|
||||
const MD_EMBEDFILES = "## Embedded Files";
|
||||
const MD_DRAWING = "## Drawing";
|
||||
|
||||
export const MD_EX_SECTIONS = [MD_EXCALIDRAW, MD_TEXTELEMENTS, MD_ELEMENTLINKS, MD_EMBEDFILES, MD_DRAWING];
|
||||
|
||||
export const ERROR_IFRAME_CONVERSION_CANCELED = "iframe conversion canceled";
|
||||
|
||||
@@ -73,7 +90,7 @@ export const {
|
||||
getCommonBoundingBox,
|
||||
getMaximumGroups,
|
||||
measureText,
|
||||
getDefaultLineHeight,
|
||||
getLineHeight,
|
||||
wrapText,
|
||||
getFontString,
|
||||
getBoundTextMaxWidth,
|
||||
@@ -82,8 +99,14 @@ export const {
|
||||
mutateElement,
|
||||
restore,
|
||||
mermaidToExcalidraw,
|
||||
getFontFamilyString,
|
||||
getContainerElement,
|
||||
refreshTextDimensions,
|
||||
getCSSFontDefinition,
|
||||
} = excalidrawLib;
|
||||
|
||||
export const FONTS_STYLE_ID = "excalidraw-custom-fonts";
|
||||
|
||||
export function JSON_parse(x: string): any {
|
||||
return JSON.parse(x.replaceAll("[", "["));
|
||||
}
|
||||
@@ -98,7 +121,7 @@ export const DEVICE: DeviceType = {
|
||||
isMacOS: document.body.hasClass("mod-macos") && ! document.body.hasClass("is-ios"),
|
||||
isWindows: document.body.hasClass("mod-windows"),
|
||||
isIOS: document.body.hasClass("is-ios"),
|
||||
isAndroid: document.body.hasClass("is-android")
|
||||
isAndroid: document.body.hasClass("is-android"),
|
||||
};
|
||||
|
||||
export const ROOTELEMENTSIZE = (() => {
|
||||
@@ -152,6 +175,7 @@ export const FRONTMATTER_KEYS:{[key:string]: {name: string, type: string, depric
|
||||
"export-svgpadding": {name: "excalidraw-export-svgpadding", type: "number", depricated: true},
|
||||
"export-padding": {name: "excalidraw-export-padding", type: "number"},
|
||||
"export-pngscale": {name: "excalidraw-export-pngscale", type: "number"},
|
||||
"export-embed-scene": {name: "excalidraw-export-embed-scene", type: "checkbox"},
|
||||
"link-prefix": {name: "excalidraw-link-prefix", type: "text"},
|
||||
"url-prefix": {name: "excalidraw-url-prefix", type: "text"},
|
||||
"link-brackets": {name: "excalidraw-link-brackets", type: "checkbox"},
|
||||
@@ -162,8 +186,10 @@ export const FRONTMATTER_KEYS:{[key:string]: {name: string, type: string, depric
|
||||
"font-color": {name: "excalidraw-font-color", type: "text"},
|
||||
"border-color": {name: "excalidraw-border-color", type: "text"},
|
||||
"md-css": {name: "excalidraw-css", type: "text"},
|
||||
"autoexport": {name: "excalidraw-autoexport", type: "checkbox"},
|
||||
"iframe-theme": {name: "excalidraw-iframe-theme", type: "text"},
|
||||
"autoexport": {name: "excalidraw-autoexport", type: "text"},
|
||||
"iframe-theme": {name: "excalidraw-iframe-theme", type: "text", depricated: true},
|
||||
"embeddable-theme": {name: "excalidraw-embeddable-theme", type: "text"},
|
||||
"open-as-markdown": {name: "excalidraw-open-md", type: "checkbox"},
|
||||
};
|
||||
|
||||
export const EMBEDDABLE_THEME_FRONTMATTER_VALUES = ["light", "dark", "auto", "dafault"];
|
||||
@@ -183,7 +209,7 @@ export const FRONTMATTER = [
|
||||
"tags: [excalidraw]",
|
||||
"",
|
||||
"---",
|
||||
"==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠==",
|
||||
"==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== You can decompress Drawing data with the command palette: 'Decompress current Excalidraw file'. For more info check in plugin settings under 'Saving'",
|
||||
"",
|
||||
"",
|
||||
].join("\n");
|
||||
|
||||
@@ -19,20 +19,22 @@ declare module "obsidian" {
|
||||
}
|
||||
}
|
||||
|
||||
const getTheme = (view: ExcalidrawView, theme:string): string => view.excalidrawData.embeddableTheme === "dark"
|
||||
function getTheme (view: ExcalidrawView, theme:string): string {
|
||||
return view.excalidrawData.embeddableTheme === "dark"
|
||||
? "theme-dark"
|
||||
: view.excalidrawData.embeddableTheme === "light"
|
||||
? "theme-light"
|
||||
: view.excalidrawData.embeddableTheme === "auto"
|
||||
? theme === "dark" ? "theme-dark" : "theme-light"
|
||||
: isObsidianThemeDark() ? "theme-dark" : "theme-light";
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
//Render webview for anything other than Vimeo and Youtube
|
||||
//Vimeo and Youtube are rendered by Excalidraw because of the window messaging
|
||||
//required to control the video
|
||||
//--------------------------------------------------------------------------------
|
||||
export const renderWebView = (src: string, view: ExcalidrawView, id: string, appState: UIAppState):JSX.Element =>{
|
||||
export function renderWebView (src: string, view: ExcalidrawView, id: string, _: UIAppState):JSX.Element {
|
||||
const isDataURL = src.startsWith("data:");
|
||||
if(DEVICE.isDesktop && !isDataURL) {
|
||||
return (
|
||||
@@ -86,36 +88,36 @@ function RenderObsidianView(
|
||||
if (!file) {
|
||||
return null;
|
||||
}
|
||||
const react = view.plugin.getPackage(view.ownerWindow).react;
|
||||
const React = view.packages.react;
|
||||
|
||||
//@ts-ignore
|
||||
const leafRef = react.useRef<{leaf: WorkspaceLeaf; node?: ObsidianCanvasNode} | null>(null);
|
||||
const isEditingRef = react.useRef(false);
|
||||
const isActiveRef = react.useRef(false);
|
||||
const themeRef = react.useRef(theme);
|
||||
const elementRef = react.useRef(element);
|
||||
const leafRef = React.useRef<{leaf: WorkspaceLeaf; node?: ObsidianCanvasNode, editNode?: Function} | null>(null);
|
||||
const isEditingRef = React.useRef(false);
|
||||
const isActiveRef = React.useRef(false);
|
||||
const themeRef = React.useRef(theme);
|
||||
const elementRef = React.useRef(element);
|
||||
|
||||
// Update themeRef when theme changes
|
||||
react.useEffect(() => {
|
||||
React.useEffect(() => {
|
||||
themeRef.current = theme;
|
||||
}, [theme]);
|
||||
|
||||
// Update elementRef when element changes
|
||||
react.useEffect(() => {
|
||||
React.useEffect(() => {
|
||||
elementRef.current = element;
|
||||
}, [element]);
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
//block propagation of events to the parent if the iframe element is active
|
||||
//--------------------------------------------------------------------------------
|
||||
const stopPropagation = react.useCallback((event:React.PointerEvent<HTMLElement>) => {
|
||||
const stopPropagation = React.useCallback((event:React.PointerEvent<HTMLElement>) => {
|
||||
if(isActiveRef.current) {
|
||||
event.stopPropagation(); // Stop the event from propagating up the DOM tree
|
||||
}
|
||||
}, [isActiveRef.current]);
|
||||
|
||||
//runs once after mounting of the component and when the component is unmounted
|
||||
react.useEffect(() => {
|
||||
React.useEffect(() => {
|
||||
if(!containerRef?.current) {
|
||||
return;
|
||||
}
|
||||
@@ -134,7 +136,7 @@ function RenderObsidianView(
|
||||
}, []);
|
||||
|
||||
//blocking or not the propagation of events to the parent if the iframe is active
|
||||
react.useEffect(() => {
|
||||
React.useEffect(() => {
|
||||
EXTENDED_EVENT_TYPES.forEach((type) => containerRef.current.removeEventListener(type, stopPropagation));
|
||||
if(!containerRef?.current) {
|
||||
return;
|
||||
@@ -154,9 +156,9 @@ function RenderObsidianView(
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
//mount the workspace leaf or the canvas node depending on subpath
|
||||
//Mount the workspace leaf or the canvas node depending on subpath
|
||||
//--------------------------------------------------------------------------------
|
||||
react.useEffect(() => {
|
||||
React.useEffect(() => {
|
||||
if(!containerRef?.current) {
|
||||
return;
|
||||
}
|
||||
@@ -175,8 +177,9 @@ function RenderObsidianView(
|
||||
rootSplit.containerEl.style.height = '100%';
|
||||
rootSplit.containerEl.style.borderRadius = "var(--embeddable-radius)";
|
||||
leafRef.current = {
|
||||
leaf: app.workspace.createLeafInParent(rootSplit, 0),
|
||||
node: null
|
||||
leaf: view.app.workspace.createLeafInParent(rootSplit, 0),
|
||||
node: null,
|
||||
editNode: null,
|
||||
};
|
||||
|
||||
const setKeepOnTop = () => {
|
||||
@@ -220,17 +223,29 @@ function RenderObsidianView(
|
||||
} else {
|
||||
const workspaceLeaf:HTMLDivElement = rootSplit.containerEl.querySelector("div.workspace-leaf");
|
||||
if(workspaceLeaf) workspaceLeaf.style.borderRadius = "var(--embeddable-radius)";
|
||||
rootSplit.containerEl.addClass("mod-visible");
|
||||
containerRef.current.appendChild(rootSplit.containerEl);
|
||||
setColors(containerRef.current, element, mdProps, canvasColor);
|
||||
}
|
||||
patchMobileView(view);
|
||||
view.updateEmbeddableLeafRef(element.id, leafRef.current);
|
||||
})();
|
||||
}
|
||||
|
||||
return () => {}; //cleanup on unmount
|
||||
return () => {
|
||||
if(!leafRef.current) {
|
||||
return;
|
||||
}
|
||||
view.canvasNodeFactory.removeNode(leafRef.current.node);
|
||||
leafRef.current.leaf?.detach();
|
||||
leafRef.current = null;
|
||||
}; //cleanup on unmount
|
||||
}, [linkText, subpath, containerRef]);
|
||||
|
||||
const setColors = (canvasNode: HTMLDivElement, element: NonDeletedExcalidrawElement, mdProps: EmbeddableMDCustomProps, canvasColor: string) => {
|
||||
//--------------------------------------------------------------------------------
|
||||
//Set colors of the canvas node
|
||||
//--------------------------------------------------------------------------------
|
||||
function setColors (canvasNode: HTMLDivElement, element: NonDeletedExcalidrawElement, mdProps: EmbeddableMDCustomProps, canvasColor: string) {
|
||||
if(!mdProps) return;
|
||||
if (!leafRef.current?.hasOwnProperty("node")) return;
|
||||
|
||||
@@ -289,7 +304,10 @@ function RenderObsidianView(
|
||||
}
|
||||
}
|
||||
|
||||
react.useEffect(() => {
|
||||
//--------------------------------------------------------------------------------
|
||||
//Set colors of the canvas node
|
||||
//--------------------------------------------------------------------------------
|
||||
React.useEffect(() => {
|
||||
if(!containerRef.current) {
|
||||
return;
|
||||
}
|
||||
@@ -304,7 +322,10 @@ function RenderObsidianView(
|
||||
canvasColor,
|
||||
])
|
||||
|
||||
react.useEffect(() => {
|
||||
//--------------------------------------------------------------------------------
|
||||
//Switch to preview mode when the iframe is not active
|
||||
//--------------------------------------------------------------------------------
|
||||
React.useEffect(() => {
|
||||
if(isEditingRef.current) {
|
||||
if(leafRef.current?.node) {
|
||||
containerRef.current?.addClasses(["is-editing", "is-focused"]);
|
||||
@@ -314,13 +335,12 @@ function RenderObsidianView(
|
||||
}
|
||||
}, [isEditingRef.current, leafRef]);
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
//Switch to edit mode when markdown view is clicked
|
||||
//--------------------------------------------------------------------------------
|
||||
const handleClick = react.useCallback((event: React.PointerEvent<HTMLElement>) => {
|
||||
const handleClick = React.useCallback((event?: React.PointerEvent<HTMLElement>) => {
|
||||
if(isActiveRef.current) {
|
||||
event.stopPropagation();
|
||||
event?.stopPropagation();
|
||||
}
|
||||
|
||||
if (isActiveRef.current && !isEditingRef.current && leafRef.current?.leaf) {
|
||||
@@ -349,10 +369,26 @@ function RenderObsidianView(
|
||||
}
|
||||
}, [leafRef.current?.leaf, element.id, view, themeRef.current]);
|
||||
|
||||
if(leafRef.current) leafRef.current.editNode = handleClick;
|
||||
// Event listener for key press
|
||||
React.useEffect(() => {
|
||||
const handleKeyPress = (event: KeyboardEvent) => {
|
||||
if (event.key === "Enter") {
|
||||
handleClick(event); // Call handleClick function when Enter key is pressed
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("keydown", handleKeyPress); // Add event listener for key press
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handleKeyPress); // Remove event listener when component unmounts
|
||||
};
|
||||
}, [handleClick]);
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
// Set isActiveRef and switch to preview mode when the iframe is not active
|
||||
//--------------------------------------------------------------------------------
|
||||
react.useEffect(() => {
|
||||
React.useEffect(() => {
|
||||
if(!containerRef?.current || !leafRef?.current) {
|
||||
return;
|
||||
}
|
||||
@@ -380,8 +416,14 @@ function RenderObsidianView(
|
||||
}
|
||||
} else if (leafRef.current?.node) {
|
||||
//Handle canvas node
|
||||
containerRef.current?.removeClasses(["is-editing", "is-focused"]);
|
||||
view.canvasNodeFactory.stopEditing(leafRef.current.node);
|
||||
if(view.plugin.settings.markdownNodeOneClickEditing && !containerRef.current?.hasClass("is-editing")) {
|
||||
const newTheme = getTheme(view, themeRef.current);
|
||||
containerRef.current?.addClasses(["is-editing", "is-focused"]);
|
||||
view.canvasNodeFactory.startEditing(leafRef.current.node, newTheme);
|
||||
} else {
|
||||
containerRef.current?.removeClasses(["is-editing", "is-focused"]);
|
||||
view.canvasNodeFactory.stopEditing(leafRef.current.node);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
containerRef,
|
||||
@@ -392,7 +434,8 @@ function RenderObsidianView(
|
||||
element,
|
||||
view,
|
||||
isEditingRef,
|
||||
view.canvasNodeFactory
|
||||
view.canvasNodeFactory,
|
||||
themeRef.current
|
||||
]);
|
||||
|
||||
return null;
|
||||
@@ -400,11 +443,10 @@ function RenderObsidianView(
|
||||
|
||||
|
||||
export const CustomEmbeddable: React.FC<{element: NonDeletedExcalidrawElement; view: ExcalidrawView; appState: UIAppState; linkText: string}> = ({ element, view, appState, linkText }) => {
|
||||
const react = view.plugin.getPackage(view.ownerWindow).react;
|
||||
const containerRef: React.RefObject<HTMLDivElement> = react.useRef(null);
|
||||
const React = view.packages.react;
|
||||
const containerRef: React.RefObject<HTMLDivElement> = React.useRef(null);
|
||||
const theme = getTheme(view, appState.theme);
|
||||
const mdProps: EmbeddableMDCustomProps = element.customData?.mdProps || null;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
|
||||
@@ -49,7 +49,7 @@ export class EmbeddableSettings extends Modal {
|
||||
this.zoomValue = element.scale[0];
|
||||
this.isYouTube = isYouTube(this.element.link);
|
||||
this.notExcalidrawIsInternal = this.file && !this.view.plugin.isExcalidrawFile(this.file)
|
||||
this.isMDFile = this.file && this.file.extension === "md" && !this.view.plugin.isExcalidrawFile(this.file);
|
||||
this.isMDFile = this.file && this.file.extension === "md"; // && !this.view.plugin.isExcalidrawFile(this.file);
|
||||
this.isLocalURI = this.element.link.startsWith("file://");
|
||||
if(isYouTube) this.youtubeStart = getYouTubeStartAt(this.element.link);
|
||||
|
||||
@@ -62,7 +62,6 @@ export class EmbeddableSettings extends Modal {
|
||||
this.mdCustomData.borderColor = borderCM.stringHEX({alpha: false});
|
||||
this.mdCustomData.borderOpacity = element.opacity;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
onOpen(): void {
|
||||
@@ -73,8 +72,16 @@ export class EmbeddableSettings extends Modal {
|
||||
|
||||
onClose() {
|
||||
this.containerEl.removeEventListener("keydown",this.onKeyDown);
|
||||
this.plugin = null;
|
||||
this.view = null;
|
||||
this.file = null;
|
||||
this.element = null;
|
||||
this.ea.destroy();
|
||||
this.ea = null;
|
||||
this.mdCustomData = null;
|
||||
}
|
||||
|
||||
|
||||
async createForm() {
|
||||
|
||||
this.contentEl.createEl("h1",{text: t("ES_TITLE")});
|
||||
@@ -140,16 +147,14 @@ export class EmbeddableSettings extends Modal {
|
||||
button
|
||||
.setButtonText(t("PROMPT_BUTTON_CANCEL"))
|
||||
.setTooltip("ESC")
|
||||
.onClick(() => {
|
||||
this.close();
|
||||
})
|
||||
.onClick(this.close.bind(this))
|
||||
)
|
||||
.addButton(button =>
|
||||
button
|
||||
.setButtonText(t("PROMPT_BUTTON_OK"))
|
||||
.setTooltip("CTRL/Opt+Enter")
|
||||
.setCta()
|
||||
.onClick(()=>this.applySettings())
|
||||
.onClick(this.applySettings.bind(this))
|
||||
)
|
||||
|
||||
|
||||
@@ -163,8 +168,6 @@ export class EmbeddableSettings extends Modal {
|
||||
this.containerEl.ownerDocument.addEventListener("keydown",onKeyDown);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private async applySettings() {
|
||||
let dirty = false;
|
||||
const el = this.ea.getElement(this.element.id) as Mutable<ExcalidrawEmbeddableElement>;
|
||||
@@ -174,16 +177,24 @@ export class EmbeddableSettings extends Modal {
|
||||
const fnparts = splitFolderAndFilename(newPathWithExt);
|
||||
const newPath = getNewUniqueFilepath(
|
||||
this.app.vault,
|
||||
fnparts.folderpath,
|
||||
fnparts.filename,
|
||||
fnparts.folderpath,
|
||||
);
|
||||
await this.app.vault.rename(this.file,newPath);
|
||||
el.link = this.element.link.replace(
|
||||
/(\[\[)([^#\]]*)([^\]]*]])/,`$1${
|
||||
this.plugin.app.metadataCache.fileToLinktext(
|
||||
this.file,this.view.file.path,true)
|
||||
}$3`);
|
||||
dirty = true;
|
||||
if(this.app.vault.getAbstractFileByPath(newPath)) {
|
||||
new Notice("File rename failed. A file with this name already exists.\n"+newPath,10000);
|
||||
} else {
|
||||
try {
|
||||
await this.app.fileManager.renameFile(this.file,newPath);
|
||||
el.link = this.element.link.replace(
|
||||
/(\[\[)([^#\]]*)([^\]]*]])/,`$1${
|
||||
this.plugin.app.metadataCache.fileToLinktext(
|
||||
this.file,this.view.file.path,true)
|
||||
}$3`);
|
||||
dirty = true;
|
||||
} catch(e) {
|
||||
new Notice("File rename failed. "+e,10000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(this.isYouTube && this.youtubeStart !== getYouTubeStartAt(this.element.link)) {
|
||||
@@ -212,9 +223,15 @@ export class EmbeddableSettings extends Modal {
|
||||
el.scale = [this.zoomValue,this.zoomValue];
|
||||
}
|
||||
if(dirty) {
|
||||
this.ea.addElementsToView();
|
||||
(async() => {
|
||||
await this.ea.addElementsToView();
|
||||
//@ts-ignore
|
||||
this.ea.viewUpdateScene({appState: {}, storeAction: "update"});
|
||||
this.close(); //close should only run once update scene is done
|
||||
})();
|
||||
} else {
|
||||
this.close();
|
||||
}
|
||||
this.close();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { DEVICE } from "src/constants/constants";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { fragWithHTML, getExportPadding, getExportTheme, getPNGScale, getWithBackground } from "src/utils/Utils";
|
||||
import { fragWithHTML, getExportPadding, getExportTheme, getPNGScale, getWithBackground, shouldEmbedScene } from "src/utils/Utils";
|
||||
|
||||
export class ExportDialog extends Modal {
|
||||
private ea: ExcalidrawAutomate;
|
||||
@@ -33,20 +33,33 @@ export class ExportDialog extends Modal {
|
||||
private view: ExcalidrawView,
|
||||
private file: TFile,
|
||||
) {
|
||||
super(app);
|
||||
super(plugin.app);
|
||||
this.ea = getEA(this.view);
|
||||
this.api = this.ea.getExcalidrawAPI() as ExcalidrawImperativeAPI;
|
||||
this.padding = getExportPadding(this.plugin,this.file);
|
||||
this.scale = getPNGScale(this.plugin,this.file)
|
||||
this.theme = getExportTheme(this.plugin, this.file, (this.api).getAppState().theme)
|
||||
this.boundingBox = this.ea.getBoundingBox(this.ea.getViewElements());
|
||||
this.embedScene = false;
|
||||
this.embedScene = shouldEmbedScene(this.plugin, this.file);
|
||||
this.exportSelectedOnly = false;
|
||||
this.saveToVault = true;
|
||||
this.transparent = !getWithBackground(this.plugin, this.file);
|
||||
this.saveSettings = false;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.app = null;
|
||||
this.plugin = null;
|
||||
this.ea.destroy();
|
||||
this.ea = null;
|
||||
this.view = null;
|
||||
this.file = null;
|
||||
this.api = null;
|
||||
this.theme = null;
|
||||
this.selectedOnlySetting = null;
|
||||
this.containerEl.remove();
|
||||
}
|
||||
|
||||
onOpen(): void {
|
||||
this.containerEl.classList.add("excalidraw-release");
|
||||
this.titleEl.setText(`Export Image`);
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
SuggestModal,
|
||||
Scope,
|
||||
} from "obsidian";
|
||||
import { t } from "../lang/helpers";
|
||||
import { createPopper, Instance as PopperInstance } from "@popperjs/core";
|
||||
|
||||
class Suggester<T> {
|
||||
@@ -132,7 +131,7 @@ class Suggester<T> {
|
||||
export abstract class SuggestionModal<T> extends FuzzySuggestModal<T> {
|
||||
items: T[] = [];
|
||||
suggestions: HTMLDivElement[];
|
||||
popper: PopperInstance;
|
||||
popper: WeakRef<PopperInstance>;
|
||||
//@ts-ignore
|
||||
scope: Scope = new Scope(this.app.scope);
|
||||
suggester: Suggester<FuzzyMatch<T>>;
|
||||
@@ -197,7 +196,7 @@ export abstract class SuggestionModal<T> extends FuzzySuggestModal<T> {
|
||||
this.app.keymap.pushScope(this.scope);
|
||||
|
||||
this.inputEl.ownerDocument.body.appendChild(this.suggestEl);
|
||||
this.popper = createPopper(this.inputEl, this.suggestEl, {
|
||||
this.popper = new WeakRef(createPopper(this.inputEl, this.suggestEl, {
|
||||
placement: "bottom-start",
|
||||
modifiers: [
|
||||
{
|
||||
@@ -213,7 +212,7 @@ export abstract class SuggestionModal<T> extends FuzzySuggestModal<T> {
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
onEscape(): void {
|
||||
@@ -225,11 +224,15 @@ export abstract class SuggestionModal<T> extends FuzzySuggestModal<T> {
|
||||
this.app.keymap.popScope(this.scope);
|
||||
|
||||
this.suggester.setSuggestions([]);
|
||||
if (this.popper) {
|
||||
this.popper.destroy();
|
||||
if (this.popper?.deref()) {
|
||||
this.popper.deref().destroy();
|
||||
}
|
||||
|
||||
this.suggestEl.detach();
|
||||
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) {
|
||||
|
||||
83
src/dialogs/FrameSettings.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { t } from "src/lang/helpers";
|
||||
|
||||
export const showFrameSettings = (ea: ExcalidrawAutomate) => {
|
||||
const {enabled, clip, name, outline} = ea.getExcalidrawAPI().getAppState().frameRendering;
|
||||
|
||||
// Create modal dialog
|
||||
const frameSettingsModal = new ea.obsidian.Modal(app);
|
||||
|
||||
frameSettingsModal.onOpen = () => {
|
||||
const {contentEl} = frameSettingsModal;
|
||||
|
||||
contentEl.createEl("h1", {text: t("FRAME_SETTINGS_TITLE")});
|
||||
|
||||
const settings = { enabled, clip, name, outline };
|
||||
|
||||
// Add toggles
|
||||
const enableFramesSetting = new ea.obsidian.Setting(contentEl)
|
||||
.setName(t("FRAME_SETTINGS_ENABLE"))
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(settings.enabled)
|
||||
.onChange(value => {
|
||||
settings.enabled = value;
|
||||
hideComponent(displayFrameNameSetting, !value);
|
||||
hideComponent(displayFrameOutlineSetting, !value);
|
||||
hideComponent(enableFrameClippingSetting, !value);
|
||||
})
|
||||
);
|
||||
|
||||
const displayFrameNameSetting = new ea.obsidian.Setting(contentEl)
|
||||
.setName(t("FRAME_SETTIGNS_NAME"))
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(settings.name)
|
||||
.onChange(value => settings.name = value)
|
||||
);
|
||||
|
||||
const displayFrameOutlineSetting = new ea.obsidian.Setting(contentEl)
|
||||
.setName(t("FRAME_SETTINGS_OUTLINE"))
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(settings.outline)
|
||||
.onChange(value => settings.outline = value)
|
||||
);
|
||||
|
||||
const enableFrameClippingSetting = new ea.obsidian.Setting(contentEl)
|
||||
.setName(t("FRAME_SETTINGS_CLIP"))
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(settings.clip)
|
||||
.onChange(value => settings.clip = value)
|
||||
);
|
||||
|
||||
// Hide or show components based on initial state
|
||||
hideComponent(displayFrameNameSetting, !settings.enabled);
|
||||
hideComponent(displayFrameOutlineSetting, !settings.enabled);
|
||||
hideComponent(enableFrameClippingSetting, !settings.enabled);
|
||||
|
||||
// Add OK button
|
||||
new ea.obsidian.Setting(contentEl)
|
||||
.addButton(button => button
|
||||
.setButtonText("OK")
|
||||
.onClick(() => {
|
||||
// Update appState with new settings
|
||||
ea.viewUpdateScene({
|
||||
// @ts-ignore
|
||||
appState: {
|
||||
frameRendering: settings
|
||||
},
|
||||
storeAction: "update",
|
||||
});
|
||||
frameSettingsModal.close();
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
frameSettingsModal.onClose = () => {
|
||||
ea.destroy();
|
||||
}
|
||||
frameSettingsModal.open();
|
||||
};
|
||||
|
||||
// Function to hide or show a component
|
||||
function hideComponent(comp:any, value:any) {
|
||||
comp.settingEl.style.display = value ? "none" : "";
|
||||
}
|
||||
162
src/dialogs/HotkeyEditor.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
import { BaseComponent, Setting, Modifier } from 'obsidian';
|
||||
import { DEVICE } from 'src/constants/constants';
|
||||
import { t } from 'src/lang/helpers';
|
||||
import { ExcalidrawSettings } from 'src/settings';
|
||||
import { modifierLabel } from 'src/utils/ModifierkeyHelper';
|
||||
import { fragWithHTML } from 'src/utils/Utils';
|
||||
|
||||
export class HotkeyEditor extends BaseComponent {
|
||||
private settings: ExcalidrawSettings;
|
||||
private containerEl: HTMLElement;
|
||||
private capturing: boolean = false;
|
||||
private activeModifiers: Modifier[] = [];
|
||||
public isDirty: boolean = false;
|
||||
private applySettingsUpdate: Function;
|
||||
|
||||
// Store bound event handlers
|
||||
private boundKeydownHandler: (event: KeyboardEvent) => void;
|
||||
private boundKeyupHandler: (event: KeyboardEvent) => void;
|
||||
|
||||
constructor(containerEl: HTMLElement, settings: ExcalidrawSettings, applySettingsUpdate: Function) {
|
||||
super();
|
||||
this.containerEl = containerEl.createDiv();
|
||||
this.settings = settings;
|
||||
this.applySettingsUpdate = applySettingsUpdate;
|
||||
|
||||
// Bind the event handlers once in the constructor
|
||||
this.boundKeydownHandler = this.onKeydown.bind(this);
|
||||
this.boundKeyupHandler = this.onKeyup.bind(this);
|
||||
}
|
||||
|
||||
onload(): void {
|
||||
this.render();
|
||||
}
|
||||
|
||||
private render(): void {
|
||||
// Clear previous content
|
||||
this.containerEl.empty();
|
||||
|
||||
// Render current overrides
|
||||
this.settings.modifierKeyOverrides.forEach((override, index) => {
|
||||
const key = override.key.toUpperCase();
|
||||
new Setting(this.containerEl)
|
||||
.setDesc(fragWithHTML(`<b>Code:</b> <kbd>${override.modifiers.join("+")} + ${key}</kbd> | ` +
|
||||
`<b>Apple:</b> <kbd>${modifierLabel(override.modifiers, "Mac")} + ${key}</kbd> | ` +
|
||||
`<b>Windows:</b> <kbd>${modifierLabel(override.modifiers, "Other")} + ${key}</kbd>`))
|
||||
.addButton((button) =>
|
||||
button
|
||||
.setButtonText(t("HOTKEY_BUTTON_REMOVE"))
|
||||
.setCta()
|
||||
.onClick(() => {
|
||||
this.settings.modifierKeyOverrides.splice(index, 1);
|
||||
this.isDirty = true;
|
||||
this.applySettingsUpdate();
|
||||
this.render();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Render Add New Override or Capture Instruction
|
||||
if (this.capturing) {
|
||||
new Setting(this.containerEl)
|
||||
.setName(t("HOTKEY_PRESS_COMBO_NANE"))
|
||||
.setDesc(t("HOTKEY_PRESS_COMBO_DESC"))
|
||||
.controlEl.style.cursor = 'pointer';
|
||||
} else {
|
||||
new Setting(this.containerEl)
|
||||
.addButton((button) =>
|
||||
button
|
||||
.setButtonText(t("HOTKEY_BUTTON_ADD_OVERRIDE"))
|
||||
.setCta()
|
||||
.onClick(() => this.startCapture())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private startCapture(): void {
|
||||
this.capturing = true;
|
||||
this.activeModifiers = [];
|
||||
this.render();
|
||||
// Use the pre-bound handlers
|
||||
window.addEventListener('keydown', this.boundKeydownHandler);
|
||||
window.addEventListener('keyup', this.boundKeyupHandler);
|
||||
}
|
||||
|
||||
private onKeydown(event: KeyboardEvent): void {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const modifiers = this.getModifiersFromEvent(event);
|
||||
|
||||
// If only modifiers are pressed, update activeModifiers and continue listening
|
||||
if (['Control', 'Shift', 'Alt', 'Meta'].includes(event.key)) {
|
||||
this.activeModifiers = modifiers;
|
||||
return;
|
||||
}
|
||||
|
||||
const key = event.key.length === 1 ? event.key.toLowerCase() : event.key;
|
||||
|
||||
// Check for duplicate overrides
|
||||
const exists = this.settings.modifierKeyOverrides.some(
|
||||
(override) =>
|
||||
override.key === key &&
|
||||
override.modifiers.length === modifiers.length &&
|
||||
override.modifiers.every((mod) => modifiers.includes(mod))
|
||||
);
|
||||
|
||||
if (!exists) {
|
||||
this.settings.modifierKeyOverrides.push({ modifiers, key });
|
||||
this.isDirty = true;
|
||||
this.applySettingsUpdate();
|
||||
}
|
||||
|
||||
this.stopCapture();
|
||||
}
|
||||
|
||||
private onKeyup(event: KeyboardEvent): void {
|
||||
// If all modifier keys are released, stop capturing
|
||||
if (!event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) {
|
||||
this.stopCapture();
|
||||
}
|
||||
}
|
||||
|
||||
private stopCapture(): void {
|
||||
this.capturing = false;
|
||||
// Use the pre-bound handlers for removal
|
||||
window.removeEventListener('keydown', this.boundKeydownHandler);
|
||||
window.removeEventListener('keyup', this.boundKeyupHandler);
|
||||
this.render();
|
||||
}
|
||||
|
||||
public unload(): void {
|
||||
// Ensure listeners are removed when the component is unloaded
|
||||
this.stopCapture();
|
||||
}
|
||||
|
||||
private getModifiersFromEvent(event: KeyboardEvent): Modifier[] {
|
||||
const modifiers: Modifier[] = [];
|
||||
|
||||
if (DEVICE.isMacOS && event.metaKey) {
|
||||
modifiers.push('Mod');
|
||||
} else if (!DEVICE.isMacOS && event.ctrlKey) {
|
||||
modifiers.push('Mod');
|
||||
}
|
||||
|
||||
if (DEVICE.isMacOS && event.ctrlKey) {
|
||||
modifiers.push('Ctrl');
|
||||
}
|
||||
|
||||
if (!DEVICE.isMacOS && event.metaKey) {
|
||||
modifiers.push('Meta');
|
||||
}
|
||||
|
||||
if (event.shiftKey) {
|
||||
modifiers.push('Shift');
|
||||
}
|
||||
if (event.altKey) {
|
||||
modifiers.push('Alt');
|
||||
}
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,15 @@ import { getEA } from "src";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
|
||||
export class ImportSVGDialog extends FuzzySuggestModal<TFile> {
|
||||
public app: App;
|
||||
public plugin: ExcalidrawPlugin;
|
||||
private view: ExcalidrawView;
|
||||
|
||||
destroy() {
|
||||
this.app = null;
|
||||
this.plugin = null;
|
||||
this.view = null;
|
||||
}
|
||||
|
||||
constructor(plugin: ExcalidrawPlugin) {
|
||||
super(plugin.app);
|
||||
this.plugin = plugin;
|
||||
@@ -38,14 +43,23 @@ export class ImportSVGDialog extends FuzzySuggestModal<TFile> {
|
||||
return item.path;
|
||||
}
|
||||
|
||||
async onChooseItem(item: TFile, event: KeyboardEvent): Promise<void> {
|
||||
async onChooseItem(item: TFile, _: KeyboardEvent): Promise<void> {
|
||||
if(!item) return;
|
||||
const ea = getEA(this.view) as ExcalidrawAutomate;
|
||||
const svg = await app.vault.read(item);
|
||||
const svg = await this.app.vault.read(item);
|
||||
if(!svg || svg === "") return;
|
||||
ea.importSVG(svg);
|
||||
ea.addToGroup(ea.getElements().map(el=>el.id));
|
||||
ea.addElementsToView(true, true, true,true);
|
||||
await ea.addElementsToView(true, true, true,true);
|
||||
ea.destroy();
|
||||
}
|
||||
|
||||
onClose(): void {
|
||||
//deley this.view destruction until onChooseItem is called
|
||||
window.setTimeout(() => {
|
||||
this.view = null;
|
||||
});
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
public start(view: ExcalidrawView) {
|
||||
|
||||
@@ -3,9 +3,13 @@ import { REG_LINKINDEX_INVALIDCHARS } from "../constants/constants";
|
||||
import { t } from "../lang/helpers";
|
||||
|
||||
export class InsertCommandDialog extends FuzzySuggestModal<TFile> {
|
||||
public app: App;
|
||||
private addText: Function;
|
||||
|
||||
destroy() {
|
||||
this.app = null;
|
||||
this.addText = null;
|
||||
}
|
||||
|
||||
constructor(app: App) {
|
||||
super(app);
|
||||
this.app = app;
|
||||
@@ -32,10 +36,18 @@ export class InsertCommandDialog extends FuzzySuggestModal<TFile> {
|
||||
onChooseItem(item: any): void {
|
||||
const cmdId = item?.id;
|
||||
this.addText(`⚙️[${item.name}](cmd://${item.id})`);
|
||||
this.addText = null;
|
||||
}
|
||||
|
||||
public start(addText: Function) {
|
||||
this.addText = addText;
|
||||
this.open();
|
||||
}
|
||||
|
||||
onClose(): void {
|
||||
window.setTimeout(()=>{
|
||||
this.addText = null;
|
||||
}) //onChooseItem must run first
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,27 @@ import { DEVICE, IMAGE_TYPES, REG_LINKINDEX_INVALIDCHARS } from "../constants/co
|
||||
import ExcalidrawView from "../ExcalidrawView";
|
||||
import { t } from "../lang/helpers";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { getEA } from "src";
|
||||
|
||||
export class InsertImageDialog extends FuzzySuggestModal<TFile> {
|
||||
public app: App;
|
||||
public plugin: ExcalidrawPlugin;
|
||||
private view: ExcalidrawView;
|
||||
|
||||
destroy() {
|
||||
this.app = null;
|
||||
this.plugin = null;
|
||||
this.view = null;
|
||||
this.inputEl.onkeyup = null;
|
||||
}
|
||||
|
||||
onClose(): void {
|
||||
//deley this.view destruction until onChooseItem is called
|
||||
window.setTimeout(() => {
|
||||
this.view = null;
|
||||
});
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
constructor(plugin: ExcalidrawPlugin) {
|
||||
super(plugin.app);
|
||||
this.plugin = plugin;
|
||||
@@ -55,13 +70,14 @@ export class InsertImageDialog extends FuzzySuggestModal<TFile> {
|
||||
}
|
||||
|
||||
onChooseItem(item: TFile, event: KeyboardEvent): void {
|
||||
const ea = this.plugin.ea.getAPI(this.view);
|
||||
const ea = getEA(this.view);
|
||||
ea.canvas.theme = this.view.excalidrawAPI.getAppState().theme;
|
||||
const scaleToFullsize = scaleToFullsizeModifier(event);
|
||||
(async () => {
|
||||
//this.view.currentPosition = this.position;
|
||||
await ea.addImage(0, 0, item, !scaleToFullsize);
|
||||
ea.addElementsToView(true, true, true);
|
||||
await ea.addElementsToView(true, true, true);
|
||||
ea.destroy();
|
||||
})();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,15 @@ import ExcalidrawPlugin from "src/main";
|
||||
import { getLink } from "src/utils/FileUtils";
|
||||
|
||||
export class InsertLinkDialog extends FuzzySuggestModal<TFile> {
|
||||
public app: App;
|
||||
private addText: Function;
|
||||
private drawingPath: string;
|
||||
|
||||
destroy() {
|
||||
this.app = null;
|
||||
this.addText = null;
|
||||
this.drawingPath = null;
|
||||
}
|
||||
|
||||
constructor(private plugin: ExcalidrawPlugin) {
|
||||
super(plugin.app);
|
||||
this.app = plugin.app;
|
||||
@@ -51,6 +56,13 @@ export class InsertLinkDialog extends FuzzySuggestModal<TFile> {
|
||||
this.addText(getLink(this.plugin,{embed: false, path: filepath, alias: item.alias}), filepath, item.alias);
|
||||
}
|
||||
|
||||
onClose(): void {
|
||||
window.setTimeout(()=>{
|
||||
this.addText = null
|
||||
}); //make sure this happens after onChooseItem runs
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
public start(drawingPath: string, addText: Function) {
|
||||
this.addText = addText;
|
||||
this.drawingPath = drawingPath;
|
||||
|
||||
@@ -2,12 +2,18 @@ import { App, FuzzySuggestModal, TFile } from "obsidian";
|
||||
import ExcalidrawView from "../ExcalidrawView";
|
||||
import { t } from "../lang/helpers";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { getEA } from "src";
|
||||
|
||||
export class InsertMDDialog extends FuzzySuggestModal<TFile> {
|
||||
public app: App;
|
||||
public plugin: ExcalidrawPlugin;
|
||||
private view: ExcalidrawView;
|
||||
|
||||
destroy() {
|
||||
this.app = null;
|
||||
this.plugin = null;
|
||||
this.view = null;
|
||||
}
|
||||
|
||||
constructor(plugin: ExcalidrawPlugin) {
|
||||
super(plugin.app);
|
||||
this.plugin = plugin;
|
||||
@@ -34,12 +40,11 @@ export class InsertMDDialog extends FuzzySuggestModal<TFile> {
|
||||
}
|
||||
|
||||
onChooseItem(item: TFile): void {
|
||||
const ea = this.plugin.ea;
|
||||
ea.reset();
|
||||
ea.setView(this.view);
|
||||
const ea = getEA(this.view);
|
||||
(async () => {
|
||||
await ea.addImage(0, 0, item);
|
||||
ea.addElementsToView(true, false, true);
|
||||
await ea.addElementsToView(true, false, true);
|
||||
ea.destroy();
|
||||
})();
|
||||
}
|
||||
|
||||
@@ -47,4 +52,12 @@ export class InsertMDDialog extends FuzzySuggestModal<TFile> {
|
||||
this.view = view;
|
||||
this.open();
|
||||
}
|
||||
|
||||
onClose(): void {
|
||||
//deley this.view destruction until onChooseItem is called
|
||||
window.setTimeout(() => {
|
||||
this.view = null;
|
||||
});
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ButtonComponent, TFile } from "obsidian";
|
||||
import { ButtonComponent, TFile, ToggleComponent } from "obsidian";
|
||||
import ExcalidrawView from "../ExcalidrawView";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { getPDFDoc } from "src/utils/FileUtils";
|
||||
@@ -7,9 +7,11 @@ import { FileSuggestionModal } from "./FolderSuggester";
|
||||
import { getEA } from "src";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { t } from "src/lang/helpers";
|
||||
|
||||
export class InsertPDFModal extends Modal {
|
||||
private borderBox: boolean = true;
|
||||
private frame: boolean = false;
|
||||
private gapSize:number = 20;
|
||||
private groupPages: boolean = false;
|
||||
private direction: "down" | "right" = "right";
|
||||
@@ -28,7 +30,7 @@ export class InsertPDFModal extends Modal {
|
||||
private plugin: ExcalidrawPlugin,
|
||||
private view: ExcalidrawView,
|
||||
) {
|
||||
super(app);
|
||||
super(plugin.app);
|
||||
}
|
||||
|
||||
open (file?: TFile) {
|
||||
@@ -48,18 +50,24 @@ export class InsertPDFModal extends Modal {
|
||||
if(this.dirty) {
|
||||
this.plugin.settings.pdfImportScale = this.importScale;
|
||||
this.plugin.settings.pdfBorderBox = this.borderBox;
|
||||
this.plugin.settings.pdfFrame = this.frame;
|
||||
this.plugin.settings.pdfGapSize = this.gapSize;
|
||||
this.plugin.settings.pdfGroupPages = this.groupPages;
|
||||
this.plugin.settings.pdfNumColumns = this.numColumns;
|
||||
this.plugin.settings.pdfNumRows = this.numRows;
|
||||
this.plugin.settings.pdfDirection = this.direction;
|
||||
this.plugin.settings.pdfLockAfterImport = this.lockAfterImport;
|
||||
this.plugin.saveSettings();
|
||||
await this.plugin.saveSettings();
|
||||
}
|
||||
if(this.pdfDoc) {
|
||||
this.pdfDoc.destroy();
|
||||
this.pdfDoc = null;
|
||||
}
|
||||
this.plugin = null;
|
||||
this.view = null;
|
||||
this.app = null;
|
||||
this.imageSizeMessage.remove();
|
||||
this.setImageSizeMessage = null;
|
||||
}
|
||||
|
||||
private async getPageDimensions (pdfDoc: any) {
|
||||
@@ -115,6 +123,7 @@ export class InsertPDFModal extends Modal {
|
||||
async createForm() {
|
||||
await this.plugin.loadSettings();
|
||||
this.borderBox = this.plugin.settings.pdfBorderBox;
|
||||
this.frame = this.plugin.settings.pdfFrame;
|
||||
this.gapSize = this.plugin.settings.pdfGapSize;
|
||||
this.groupPages = this.plugin.settings.pdfGroupPages;
|
||||
this.numColumns = this.plugin.settings.pdfNumColumns;
|
||||
@@ -133,13 +142,13 @@ export class InsertPDFModal extends Modal {
|
||||
|
||||
const importButtonMessages = () => {
|
||||
if(!this.pdfDoc) {
|
||||
importMessage.innerText = "Please select a PDF file";
|
||||
importMessage.innerText = t("IPM_SELECT_PDF");
|
||||
importButton.buttonEl.style.display="none";
|
||||
return;
|
||||
}
|
||||
if(this.pagesToImport.length === 0) {
|
||||
importButton.buttonEl.style.display="none";
|
||||
importMessage.innerText = "Please select pages to import";
|
||||
importMessage.innerText = t("IPM_SELECT_PAGES_TO_IMPORT");
|
||||
return
|
||||
}
|
||||
if(Math.max(...this.pagesToImport) <= this.pdfDoc.numPages) {
|
||||
@@ -156,7 +165,7 @@ export class InsertPDFModal extends Modal {
|
||||
|
||||
const numPagesMessages = () => {
|
||||
if(numPages === 0) {
|
||||
numPagesMessage.innerText = "Please select a PDF file";
|
||||
numPagesMessage.innerText = t("IPM_SELECT_PDF");
|
||||
return;
|
||||
}
|
||||
numPagesMessage.innerHTML = `There are <b>${numPages}</b> pages in the selected document.`;
|
||||
@@ -206,7 +215,7 @@ export class InsertPDFModal extends Modal {
|
||||
numPagesMessage = ce.createEl("p", {text: ""});
|
||||
numPagesMessages();
|
||||
new Setting(ce)
|
||||
.setName("Pages to import")
|
||||
.setName(t("IPM_PAGES_TO_IMPORT_NAME"))
|
||||
.setDesc("e.g.: 1,3-5,7,9-10")
|
||||
.addText(text => {
|
||||
pageRangesTextComponent = text;
|
||||
@@ -217,18 +226,52 @@ export class InsertPDFModal extends Modal {
|
||||
})
|
||||
importPagesMessage = ce.createEl("p", {text: ""});
|
||||
|
||||
new Setting(ce)
|
||||
.setName("Add border box")
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.borderBox)
|
||||
.onChange((value) => {
|
||||
this.borderBox = value;
|
||||
this.dirty = true;
|
||||
}))
|
||||
let bbToggle: ToggleComponent;
|
||||
let fToggle: ToggleComponent;
|
||||
let laiToggle: ToggleComponent;
|
||||
|
||||
this.frame = this.borderBox ? false : this.frame;
|
||||
|
||||
new Setting(ce)
|
||||
.setName("Group pages")
|
||||
.setDesc("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.")
|
||||
.setName(t("IPM_ADD_BORDER_BOX_NAME"))
|
||||
.addToggle(toggle => {
|
||||
bbToggle = toggle;
|
||||
toggle
|
||||
.setValue(this.borderBox)
|
||||
.onChange((value) => {
|
||||
this.borderBox = value;
|
||||
if(value) {
|
||||
this.frame = false;
|
||||
fToggle.setValue(false);
|
||||
}
|
||||
this.dirty = true;
|
||||
})
|
||||
})
|
||||
|
||||
new Setting(ce)
|
||||
.setName(t("IPM_ADD_FRAME_NAME"))
|
||||
.setDesc(t("IPM_ADD_FRAME_DESC"))
|
||||
.addToggle(toggle => {
|
||||
fToggle = toggle;
|
||||
toggle
|
||||
.setValue(this.frame)
|
||||
.onChange((value) => {
|
||||
this.frame = value;
|
||||
if(value) {
|
||||
this.borderBox = false;
|
||||
bbToggle.setValue(false);
|
||||
if(!this.lockAfterImport) {
|
||||
this.lockAfterImport = true;
|
||||
laiToggle.setValue(true);
|
||||
}
|
||||
}
|
||||
this.dirty = true;
|
||||
})
|
||||
})
|
||||
|
||||
new Setting(ce)
|
||||
.setName(t("IPM_GROUP_PAGES_NAME"))
|
||||
.setDesc(t("IPM_GROUP_PAGES_DESC"))
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.groupPages)
|
||||
.onChange((value) => {
|
||||
@@ -239,12 +282,15 @@ export class InsertPDFModal extends Modal {
|
||||
|
||||
new Setting(ce)
|
||||
.setName("Lock pages on canvas after import")
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.lockAfterImport)
|
||||
.onChange((value) => {
|
||||
this.lockAfterImport = value
|
||||
this.dirty = true;
|
||||
}))
|
||||
.addToggle(toggle => {
|
||||
laiToggle = toggle;
|
||||
toggle
|
||||
.setValue(this.lockAfterImport)
|
||||
.onChange((value) => {
|
||||
this.lockAfterImport = value
|
||||
this.dirty = true;
|
||||
})
|
||||
})
|
||||
|
||||
let numColumnsSetting: Setting;
|
||||
let numRowsSetting: Setting;
|
||||
@@ -386,6 +432,12 @@ export class InsertPDFModal extends Modal {
|
||||
if(this.lockAfterImport) imgEl.locked = true;
|
||||
|
||||
ea.addToGroup([boxID,imageID]);
|
||||
|
||||
if(this.frame) {
|
||||
const frameID = ea.addFrame(topX, topY,imgWidth,imgHeight,`${page}`);
|
||||
ea.addElementsToFrame(frameID, [boxID,imageID]);
|
||||
ea.getElement(frameID).link = this.pdfFile.path + `#page=${page}`;
|
||||
}
|
||||
|
||||
switch(this.direction) {
|
||||
case "right":
|
||||
@@ -399,7 +451,9 @@ export class InsertPDFModal extends Modal {
|
||||
}
|
||||
}
|
||||
if(this.groupPages) {
|
||||
const ids = ea.getElements().map(el => el.id);
|
||||
const ids = ea.getElements()
|
||||
.filter(el=>!this.frame || (el.type === "frame"))
|
||||
.map(el => el.id);
|
||||
ea.addToGroup(ids);
|
||||
}
|
||||
await ea.addElementsToView(true,true,false);
|
||||
@@ -408,6 +462,7 @@ export class InsertPDFModal extends Modal {
|
||||
const viewElements = ea.getViewElements().filter(el => ids.includes(el.id));
|
||||
api.selectElements(viewElements);
|
||||
api.zoomToFit(viewElements);
|
||||
ea.destroy();
|
||||
this.close();
|
||||
})
|
||||
importButton = button;
|
||||
|
||||
@@ -6,7 +6,7 @@ If you'd like to learn more, please subscribe to my YouTube channel: [Visual PKM
|
||||
Thank you & Enjoy!
|
||||
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/o0exK-xFP3k" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
<iframe src="https://www.youtube.com/embed/P_Q6avJGoWI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
`;
|
||||
|
||||
@@ -17,6 +17,566 @@ I develop this plugin as a hobby, spending my free time doing this. If you find
|
||||
|
||||
<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>
|
||||
`,
|
||||
"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)
|
||||
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/2poSS-Z91lY" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## New
|
||||
- Elbow connectors: https://x.com/excalidraw/status/1819084086222393554
|
||||
|
||||
## Fixed
|
||||
- Convert Markdown to Excalidraw did not work correctly when there was ${String.fromCharCode(96)}---${String.fromCharCode(96)} anywhere in the file, but no frontmatter (e.g. a table)
|
||||
- Fixed Obsidian move tab to new window
|
||||
- Fixed duplicating bound arrows without its bound elements throwing error [#8315](https://github.com/excalidraw/excalidraw/issues/8315)
|
||||
`,
|
||||
"2.2.13": `
|
||||
## Fixed
|
||||
- Could not undo element after pasting [#1906](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1906)
|
||||
- Links broke after renaming an Excalidraw file using the F2 shortcut [#1907](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1907)
|
||||
- Unable to open or convert very large ${String.fromCharCode(96)}.excalidraw${String.fromCharCode(96)} file, e.g. BoaPs you can download from [here](https://ko-fi.com/zsolt/shop)
|
||||
`,
|
||||
"2.2.12": `
|
||||
## Fixed
|
||||
- Rename moved files to root folder [#1905](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1905)
|
||||
- Fonts not displaying correctly in cached image previews
|
||||
`,
|
||||
"2.2.11": `
|
||||
<img alt="badges" src="https://github.com/user-attachments/assets/7591b523-6bc6-46ff-b552-5c3492139e4c" referrerpolicy="no-referrer" style="width: 100%;">
|
||||
|
||||
## New
|
||||
- Font picker with additional fonts (not yet fully configurable, but that will come in due time) [#8012](https://github.com/excalidraw/excalidraw/pull/8012)
|
||||
- Introducing Visual Thinking Badges. The more you use Excalidraw the higher your rank will be. Levels are: Bronze, Silver, Gold and Platinum.
|
||||
|
||||
## Fixed
|
||||
- Embedded PDF was not visible on phones [#1904](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1904)
|
||||
- F2 does not rename files in Excalidraw View [#1900](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1900)
|
||||
- Wireframe to Code now honors the GPT model settings in plugin settings. [#1901](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1901)
|
||||
- Updated ExcaliAI to support gpt-4o for vision. [#1859](https://github.com/zsviczian/obsidian-excalidraw-plugin/pull/1859) 🙏@Saik0s
|
||||
- Minor fixes from excalidraw.com [#8287](https://github.com/excalidraw/excalidraw/pull/8287), [#8285](https://github.com/excalidraw/excalidraw/pull/8285)
|
||||
`,
|
||||
"2.2.10": `
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/sjZfdqpxqsg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## Fixed
|
||||
- Drastically degraded rendering performance when zoomed in and when arrows with labels are used. [#8267](https://github.com/excalidraw/excalidraw/pull/8267), [#8266](https://github.com/excalidraw/excalidraw/pull/8266), [#1893](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1893)
|
||||
- Frame title font in exports.
|
||||
|
||||
## New
|
||||
- Area, Group, Frame, and Clipped-Frame references to images now also work when pasting images to Excalidraw.
|
||||
- The new reference type ${String.fromCharCode(96)}clippedframe=${String.fromCharCode(96)} works in the same way as ${String.fromCharCode(96)}frame=${String.fromCharCode(96)} but will display the elements clipped by the frame. ${String.fromCharCode(96)}clippedframe=${String.fromCharCode(96)} will always display the image with zero padding.
|
||||
- New command palette action: ${String.fromCharCode(96)}Frame Settings${String.fromCharCode(96)} gives you fine-grained control over how frames are rendered. Frame settings will also be reflected in image exports. For example, if you hide the frame name or outline, then in exports they will not be visible.
|
||||
`,
|
||||
"2.2.9": `
|
||||
## New
|
||||
- Improved the "Open the back-of-the-note of the selected Excalidraw image" action. It now works with grouped elements and keeps the popout window within the visible screen area when elements are close to the top of the canvas. Note: Due to an Obsidian bug, I do not recommend using this feature with versions 1.6.0 - 1.6.6, if you have Obsidian Sync enabled, because Obsidian may freeze when closing the popout window. It functions properly in Obsidian versions before 1.6.0 and from 1.6.7 onwards.
|
||||
|
||||
## Fixed
|
||||
- Drag and drop from a local folder (outside Obsidian) resulted in duplicate images.
|
||||
- Insert Link Action did not work [#1873](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1873)
|
||||
- Insert Obsidian Command Action did not work
|
||||
- Element link for text element got deleted when editing the text. [#1878](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1878)
|
||||
- When back-of-the-drawing Section Headings have spaces in them, clicking the link opens the drawing side not the markdown side. [#1877](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1877)
|
||||
- obsidian:// links did not work as expected. [#1872](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1872)
|
||||
- copying and moving a rectangle with text, moves the text unexpectedly. The issue should now be resolved (at least much less likely to occur) [#1867](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1867)
|
||||
`,
|
||||
"2.2.8": `
|
||||
While this release may appear modest with no new features, it represents nearly 50 hours of dedicated work. Here's what's been improved:
|
||||
|
||||
- **Enhanced Memory Management**: Significant improvements to optimize memory usage.
|
||||
- Bug Fixes:
|
||||
- Support for multi-file drag and drop from the operating system.
|
||||
- Correct theming of animated GIFs as Embeddables.
|
||||
- Several other minor bug fixes.
|
||||
|
||||
Please note that due to extensive refactoring of the codebase, there may be some unexpected issues. Thanks for your understanding and patience.
|
||||
`,
|
||||
"2.2.7": `
|
||||
## New
|
||||
- In Miscellaneous Settings: added **Load Excalidraw Properties into Obsidian Suggester**. This setting toggles the automatic loading of Excalidraw properties at startup. Enabled by default for easy use of front matter properties. Disabling it prevents auto-loading, but you'll need to manually remove unwanted properties using Obsidian properties view. A plugin restart is required after enabling auto-loading.
|
||||
|
||||
## Fixed
|
||||
- Zotero support [1835](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1835)
|
||||
- Lines binding to elements and selections [#8146](https://github.com/excalidraw/excalidraw/issues/8146), and plugin getting stuck with dragging an element [#8131](https://github.com/excalidraw/excalidraw/issues/8131)`
|
||||
,
|
||||
"2.2.6": `
|
||||
## Fixed
|
||||
- CTRL+F search for text elements in drawing, the result did not get selected. This was a regression in 2.2.5 [#1822](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1822)
|
||||
|
||||
## New
|
||||
- Zotero compatibility support for back-of-the-side markdown notes. This needs to be enabled in plugin settings under Compatibility [#1820](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1820)
|
||||
|
||||
## New from Excalidraw.com
|
||||
- ${String.fromCharCode(96)}Stats & Element Properties${String.fromCharCode(96)}, accessible via the context menu, is now editable, e.g. you can type in the exact position and size of objects, change font size and set element angle.
|
||||
- Pasting mermaid diagrams from chatGPT will embed a diagram instead of the text
|
||||
`,
|
||||
"2.2.5": `
|
||||
## Fixed
|
||||
- Cursor visibility in dark mode [#1812](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1812)
|
||||
- SVG to Excalidraw now...
|
||||
- converts elements inside the ${String.fromCharCode(96)}<switch>${String.fromCharCode(96)} tag, improving compatibility with SVGs from [The Noun Project](https://thenounproject.com/)
|
||||
- sets visibility for all elements, preventing invisible converted images.
|
||||
- Cached images sometimes lost their font face and natural size when nested in an Excalidraw scene. This issue occurred when drawings were embedded in a markdown note (native SVG) and nested in a drawing simultaneously. Depending on the update and render sequence, these drawings sometimes appeared incorrectly in the Excalidraw scene.
|
||||
`,
|
||||
"2.2.4":`
|
||||
<div style="text-align: center;">
|
||||
<a data-tooltip-position="top" aria-label="https://youtube.com/shorts/zF1p2yfk4f4" rel="noopener" class="external-link" href="https://youtube.com/shorts/zF1p2yfk4f4" target="_blank">
|
||||
<img src="https://private-user-images.githubusercontent.com/14358394/335857018-c4f5c4c7-9b8f-427f-aa6f-8c1b189610af.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTczMjQxNDksIm5iZiI6MTcxNzMyMzg0OSwicGF0aCI6Ii8xNDM1ODM5NC8zMzU4NTcwMTgtYzRmNWM0YzctOWI4Zi00MjdmLWFhNmYtOGMxYjE4OTYxMGFmLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA2MDIlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwNjAyVDEwMjQwOVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTdhZGUwNDRkZmM2NmJjNTNiYjUwNjMxMmU2MGEyZTQwZGQwNmUyZmI5ZDFhNzMwMzg2OThjMjhmZmJkNzNiZDkmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.wZfkXmBRcXwz0pN6q0EEvmwtxVAB9ymPk9a9upmGXYE" referrerpolicy="no-referrer" style="width: 150px; margin: 0 auto;">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## New from Excalidraw.com
|
||||
- You can now set the text width even during creation. Simply drag with the text tool. Note, there's a minimum distance before the text enters the wrapped mode so there aren't false positives. [See example here](https://x.com/excalidraw/status/1795468201335075000)
|
||||
|
||||
## New
|
||||
- Updated zh-cn translation. Thank you @dmscode
|
||||
- New context menu and command palette action: "Move back-of-note card to File". This is only active when an eligible embeddable element is selected.
|
||||
|
||||
## Fixed
|
||||
- Setting different autosaveIntervals on Desktop and Mobile will no longer trigger unnecessary commits to settings each time you open Excalidraw on a different device. Thanks @jmhammond for the contribution! [#1805](https://github.com/zsviczian/obsidian-excalidraw-plugin/pull/1805), [#1652](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1652), [#888](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/888)
|
||||
|
||||
## Fixed in ExcalidrawAutomate
|
||||
- ${String.fromCharCode(96)}getCM(color)${String.fromCharCode(96)} was missing from ${String.fromCharCode(96)}ea.help()${String.fromCharCode(96)}. It is now added. getCM returns a ColorMaster object. ColorMaster is a powerful library should you want to create scripts to manipulate colors. Check out my [Scripting Colors](https://youtu.be/7gJDwNgQ6NU) video should you want to learn more. [#1806](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1806)
|
||||
`,
|
||||
"2.2.3":`
|
||||
## Fixed
|
||||
- Undo history was not properly initialized [#1791](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1785)
|
||||
- Excalidraw did not save edits when switching to markdown view mode with a hotkey or terminating the popout window
|
||||
- SVG export did not maintain the aspect ratio of manually distorted images [#1780](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1780)
|
||||
|
||||
## New
|
||||
- In pen mode, double tapping the screen will toggle the eraser tool when using freedraw tool, or one of the other tools in locked mode.
|
||||
- New setting under "Excalidraw appearance and behavior" to disable rendering of Excalidraw drawings in hover previews, in case the file has the ${String.fromCharCode(96)}excalidraw-open-md: true${String.fromCharCode(96)} frontmatter property [#1795](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1795)
|
||||
- Additional foolproofing of ${String.fromCharCode(96)}# Excalidraw Data${String.fromCharCode(96)}. The file is now more resilient to automated linting and other changes. There is also a new setting under "Compatibility Features" to add a dummy first text element to ${String.fromCharCode(96)}## Text Elements${String.fromCharCode(96)}. You can use this feature if your auto-linter adds empty lines after section headings.
|
||||
- Pasting markdown code blocks will create a back-of-the-note card with the code block. CTRL+SHIFT+V will paste the text as a normal text element. When copying code from Chat GPT the markdown code fence (triple backtick) is missing. In this case, you may use the new context menu action "Paste code block" to create a back of the note card with the code block.
|
||||
- Pasting long text will be wrapped in the text element.
|
||||
|
||||
## New in ExcalidrawAutomate
|
||||
- Updated viewUpdateScene: This now implements the [new Excalidraw API](https://github.com/excalidraw/excalidraw/pull/7898)
|
||||
${String.fromCharCode(96, 96, 96)}ts
|
||||
viewUpdateScene (
|
||||
scene: {
|
||||
elements?: ExcalidrawElement[],
|
||||
appState?: AppState,
|
||||
files?: BinaryFileData,
|
||||
commitToHistory?: boolean,
|
||||
storeAction?: "capture" | "none" | "update",
|
||||
},
|
||||
restore: boolean = false,
|
||||
):void ;
|
||||
${String.fromCharCode(96, 96, 96)}
|
||||
- Updated addText. The function now supports the new text-wrapping feature
|
||||
${String.fromCharCode(96, 96, 96)}ts
|
||||
addText(
|
||||
topX: number,
|
||||
topY: number,
|
||||
text: string,
|
||||
formatting?: {
|
||||
autoResize?: boolean; //Default is true. Setting this to false will wrap the text in the text element without the need for the container. If set to false, you must set a width value as well.
|
||||
wrapAt?: number; //wrapAt is ignored if autoResize is set to false (and width is provided)
|
||||
width?: number;
|
||||
height?: number;
|
||||
textAlign?: "left" | "center" | "right";
|
||||
box?: boolean | "box" | "blob" | "ellipse" | "diamond";
|
||||
boxPadding?: number;
|
||||
boxStrokeColor?: string;
|
||||
textVerticalAlign?: "top" | "middle" | "bottom";
|
||||
},
|
||||
id?: string,
|
||||
): string
|
||||
${String.fromCharCode(96, 96, 96)}
|
||||
`,
|
||||
"2.2.2":`
|
||||
## Fixed
|
||||
- ExcaliBrain stopped working with 2.2.0
|
||||
|
||||

|
||||
`,
|
||||
"2.2.1":`
|
||||
## Fixed
|
||||
- Text height becomes unreadable after 2.2.0 update [#1784](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1784)
|
||||
- Images are loaded with a rounded border when loading old Excalidraw files
|
||||
- Embedded Excalidraw images cache gets stuck with old version of the image
|
||||
- Extremely long loading times with legacy (3+ years old) Excalidraw files
|
||||
`,
|
||||
"2.2.0":`
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/dV0NEOwn5NM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
⚠️⚠️⚠️ BREAKING CHANGE ⚠️⚠️⚠️
|
||||
Files you save with 2.2.0 are not backward compatible with earlier plugin versions!
|
||||
|
||||
## New from excalidraw.com
|
||||
- Wrapable text elements (without the need for transparent sticky notes!)
|
||||
|
||||
## New
|
||||
- File format. I nested all Excalidraw markup under ${String.fromCharCode(96)}# Excalidraw Data${String.fromCharCode(96)}. Here's the new structure.
|
||||
${String.fromCharCode(96,96,96)}markdown
|
||||
---
|
||||
excalidraw-plugin: parsed
|
||||
other-frontmatter-properties: values
|
||||
---
|
||||
back of the note bla bla bla
|
||||
|
||||
# Excalidraw Data
|
||||
## Text Element
|
||||
## Element Links
|
||||
## Embedded Files
|
||||
%%
|
||||
## Drawing
|
||||
%%
|
||||
${String.fromCharCode(96,96,96)}
|
||||
- When opening Excalidraw in Markdown ${String.fromCharCode(96)}# Excalidraw Data${String.fromCharCode(96)} will be folded.
|
||||
- New command palette action: ${String.fromCharCode(96)}Open the back-of-the-note of the selected Excalidraw image${String.fromCharCode(96)}. The action is only visible when selecting an embedded Excalidraw drawing in the Scene. On a desktop, the command will open the back of the selected card in a popout window, and on a mobile, in a new tab.
|
||||
|
||||
## Fixed
|
||||
- Drag and drop from Finder/Explorer (OS external). Images will retain their filenames. PDFs will be imported to the Vault. [#1779](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1779)
|
||||
`,
|
||||
"2.1.8":`
|
||||
## Fixed
|
||||
- Fixing issues that surfaced after upgrading to Obsidian 1.6.0
|
||||
- Fixed Excalidraw hover previews
|
||||
- Cursor for editing links, text elements, and frame names became virtually invisible if Obsidian was in dark mode and Excalidraw in light mode and vica versa.
|
||||
- Rendering Excalidraw drawings in Markdown views, right after Obsidian loaded did not work.
|
||||
- I implemented more graceful fail if you submitted a malformed SVG for SVG to Excalidraw conversation. [#1768](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1768)
|
||||
|
||||
## New
|
||||
- New setting under "Save" in plugin settings to NOT decompress JSON when switching to Markdown view mode. For details see description under "Save" settings. The benefit is smaller file size and fewer results in the Obsidian search. If you want to edit the JSON, you can always manually decompress JSON in markdown view mode with the command palette action "Excalidraw: Decompress JSON".
|
||||
`,
|
||||
"2.1.7:":`
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/P_Q6avJGoWI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## Updates from Excalidraw.com
|
||||
- Improved undo management.
|
||||
- Improved handle to scale images from the side.
|
||||
- Changed arrow binding behavior.
|
||||
- Many other minor fixes and improvements.
|
||||
|
||||
## New
|
||||
- Introduced image caching for nested (embedded) Excalidraw drawings on the scene. This enhancement should lead to improved scene loading times, especially when dealing with numerous embedded Excalidraw drawings.
|
||||
- Added new OCR Command Palette actions. Users can now re-run OCR and run OCR for selected elements.
|
||||
|
||||
## Fixed
|
||||
- Fixed an issue where cropping an embeddable PDF frame in the Excalidraw Scene caused distortion based on the embeddable element's aspect ratio. ([#1756](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1756))
|
||||
- Removed the listing of ${String.fromCharCode(96)}# Embedded files${String.fromCharCode(96)} section when adding a "Back of the note card". ([#1754](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1754))
|
||||
- Resolved the issue where closing the on-screen keyboard with the keyboard hide button of your phone, instead of tapping somewhere else on the Excalidraw scene, did not resize the scene correctly. ([#1729](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1729))
|
||||
- Fixed the problem where pasting a text element as text into markdown incorrectly pasted the text to the end of the MD note, with line breaks as rendered on screen in Excalidraw. Also addressed the issue where pasting an image element as an image resulted in it being pasted to the end of the document. ([#1749](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1749))
|
||||
- Corrected the color inversion of embedded images when changing the theme from light to dark, then back from dark to light, and again from light to dark on the third change.
|
||||
- Addressed the problem where cropping an image while unlocking and rotating it in the cropper did not reflect the rotation. Note that rotating the image in Cropper required switching to markdown view mode, changing the "locked": true property to false, then switching back to Excalidraw mode. This issue likely impacted only a very few power users. ([#1745](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1745))
|
||||
|
||||
## New in ExcalidrawAutomate
|
||||
${String.fromCharCode(96,96,96)}ts
|
||||
/**
|
||||
* Retruns the embedded images in the scene recursively. If excalidrawFile is not provided,
|
||||
* the function will use ea.targetView.file
|
||||
* @param excalidrawFile
|
||||
* @returns TFile[] of all nested images and Excalidraw drawings recursively
|
||||
*/
|
||||
public getEmbeddedImagesFiletree(excalidrawFile?: TFile): TFile[];
|
||||
${String.fromCharCode(96,96,96)}
|
||||
|
||||
`,
|
||||
"2.1.6":`
|
||||
## Two minor fixes
|
||||
- Scaling of LaTeX formulas when the formula is changed
|
||||
- If the back of the note card only contains a block embed ${String.fromCharCode(96)}![[embed]]${String.fromCharCode(96)} this got removed when saving the Excalidraw file. This issue has been present since November, 2021 (v1.4.9).
|
||||
`,
|
||||
"2.1.5":`
|
||||
## New
|
||||
- Save "Snap to objects" with the scene state. If this is the only change you make to the scene, force save it using CTRL+S (note, use CTRL on Mac as well).
|
||||
- Added "Copy markdown link" to the context menu.
|
||||
|
||||
## Fixed
|
||||
- Paste operation occasionally duplicated text elements.
|
||||
- Pasting multiple instances of the same image from excalidraw.com or another instance of Obsidian, or pasting an image from anywhere and making copies with ALT/OPT + drag immediately after pasting (before autosave triggered) led to broken images when reopening the drawing.
|
||||
- CTRL/CMD+Click on a Text Element with an element link did not work (previously, you had to click the top right link indicator). Now, you can click anywhere on the element.
|
||||
- Hover preview for elements with a link only worked when hovering over the element link. Now, you can hover anywhere. If there are multiple elements with links, the top-level element will take precedence.
|
||||
- Link navigation within drawing when the "Focus on Existing Tab" feature is enabled under "Links, transclusion and TODOs" in settings works again.
|
||||
- If a link points to a back-of-the-card section or block the drawing will automatically switch to markdown view mode and navigate to the block or section.
|
||||
- DynamicSytle, dark mode when canvas background is set to transparent.
|
||||
- Scale to maintain the aspect ratio of a markdown notes embedded as images.
|
||||
- You can now borrow interactive markdown embeds to tables, blockquotes, list elements and callouts - not just paragraphs.
|
||||
- Back of the drawing cards:
|
||||
- Leaving the Section Name empty when creating the first back of the card note resulted in an error.
|
||||
- If you add the markdown comment (${String.fromCharCode(96)}%%${String.fromCharCode(96)}) directly before ${String.fromCharCode(96)}# Text Elements${String.fromCharCode(96)}, a trailing ${String.fromCharCode(96)}#${String.fromCharCode(96)} will be added to your document, when adding a back of the card note. This is to hide the markdown comment from the card. The trailing (empty) ${String.fromCharCode(96)}#${String.fromCharCode(96)} will not be visible in reading mode, pdf exports, and when publishing with Obsidian Publish.
|
||||
Here's a sample markdown structure of your document:
|
||||
|
||||
${String.fromCharCode(96,96,96)}markdown
|
||||
---
|
||||
excalidraw-plugin: parsed
|
||||
---
|
||||
# Your back of the card section
|
||||
bla bla bla
|
||||
|
||||
#
|
||||
%%
|
||||
# Text Elements
|
||||
... the rest of the Excalidraw file
|
||||
${String.fromCharCode(96,96,96)}
|
||||
`,
|
||||
"2.1.4":`
|
||||
## Fixed
|
||||
- Fixed the **aspect ratio** of an Excalidraw embedded within another Excalidraw **not updating**. [#1707](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1707)
|
||||
- Some plugins automatically add document properties to all files in the Vault. Users with this configuration were **unable to run Excalidraw scripts**. Excalidraw now removes document properties from the script before execution.
|
||||
- The very last markdown edit sometimes **wasn't saved when immediately switching from Markdown to Excalidraw View**. I now force a save before switching views.
|
||||
- The setting to disable/enable ${String.fromCharCode(96)}CTRL/CMD + CLICK on text with [[links]] or [](links) to open them${String.fromCharCode(96)} works again. [#1704](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1704)
|
||||
- **Annotation and cropping** of images in Markdown notes now also work **with Markdown links that have encoded characters** e.g.: ${String.fromCharCode(96)}${String.fromCharCode(96)}.
|
||||
- Solved compatibility issue of **Taskbone OCR on Android**.
|
||||
|
||||
## New
|
||||
- New settings:
|
||||
- Under "Appearance and Behavior": Option to **render Excalidraw file as an image in Markdown reading mode**. This setting is disabled by default. [#1706](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1706), [#1705](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1705)
|
||||
- Under "Embedding Excalidraw ... and Exporting"/"Export Settings": Option to **render Excalidraw file as an image when exporting to PDF** in Markdown mode. This option is disabled by default. When enabled, exporting an Excalidraw drawing in markdown view mode to PDF will render the image on the page.
|
||||
- **Enhanced annotation and cropping** of images in Markdown documents:
|
||||
- Newly embedded **links will now follow the style of the original link**. If the original format was a ${String.fromCharCode(96)}${String.fromCharCode(96)}, the annotated file will follow this format. For ${String.fromCharCode(96)}[[wiki links]]${String.fromCharCode(96)}, it will follow that style. Additionally, if an alias was specified like ${String.fromCharCode(96)}[[link|alias]]${String.fromCharCode(96)}, the annotated or cropped image will retain the alias.
|
||||
- Introduced a new setting under "Saving" titled **"Preserve image size when annotating"**. This setting is disabled by default. When enabled, the embed link replacing the annotated image will maintain the size of the original image.
|
||||
- Option to **automatically embed the scene in exported PNG and SVG image files**. Including the scene will allow users to open the picture on Excalidraw.com or in another Obsidian Vault as an editable Excalidraw file.New setting is under the Export category. The new frontmatter tag is: ${String.fromCharCode(96)}excalidraw-export-embed-scene: true/false${String.fromCharCode(96)}.
|
||||
`,
|
||||
"2.1.3":`
|
||||
This is a republish of 2.1.2 with a minor change. Sorry about the frequent releases. I will hold back for a few weeks now.
|
||||
`,
|
||||
"2.1.2":`
|
||||
## Quality of Life Improvements
|
||||
- The "Insert Any File" option that disappeared from the Command Palette is now restored. [#1690](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1690)
|
||||
- Improved two-finger pan speed.
|
||||
- Fixed text wrapping issue that caused text to jump around when editing text in a sticky note when the Obsidian zoom level was not set to 100%.
|
||||
- Mask Generation in [ExcaliAI](https://youtu.be/3G8hsV-V-gQ) Edit Image now works properly again. [#1684](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1684)
|
||||
- Fixed aspect ratio change for .jpg, .png, .bmp, .webp, .SVG (non-Excalidraw) images. Previously, if the image was distorted (i.e. you held SHIFT while resizing it), it would revert to the original aspect ratio upon saving the drawing. Resetting the aspect ratio is the desired behavior for nested Excalidraw drawings since you might have changed the source image and want it to still display with the correct aspect ratio, however for other image files, the behavior is not desired. [#1698](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1698)
|
||||
- The command palette action "Set selected image element size to 100% of original" now works even on freshly pasted images, not just after saving the drawing. ([#1695](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1698))
|
||||
- If a text element has an element link (CTRL/CMD+K), but the link was not reflected in the Element Text, then CTRL/CMD+clicking the text element did not navigate to the link, only clicking the link indicator did. Now you can also CTRL/CMD click anywhere on the text element and it will navigate. Note, however, that links in the text element text take precedence over element links.
|
||||
`,
|
||||
"2.1.1":`
|
||||
## Fixed
|
||||
- Printing a markdown page that has an Excalidraw drawing on the back side, resulted in an empty PDF. This is now resolved.
|
||||
|
||||
## New
|
||||
- Reduce the visual clutter by fading out the Excalidraw markup in markdown view mode. This feature needs to be enabled in plugin settings. You'll find the setting under ${String.fromCharCode(96)}Miscellaneous features${String.fromCharCode(96)}. Look for ${String.fromCharCode(96)}Fade out Excalidraw markup${String.fromCharCode(96)}. Depending on the location of the markdown comment ${String.fromCharCode(96)}%%${String.fromCharCode(96)}, if the comment starts before ${String.fromCharCode(96)}# Text Elements${String.fromCharCode(96)} then the fading will start from ${String.fromCharCode(96)}# Text Elements${String.fromCharCode(96)}, if the comment is before ${String.fromCharCode(96)}# Drawing${String.fromCharCode(96)} then the fading will only start with "drawing". If you delete the opening ${String.fromCharCode(96)}%%${String.fromCharCode(96)} the markup will be visible. Note, that if you place the comment before ${String.fromCharCode(96)}#Text Elements${String.fromCharCode(96)}, you will not be able to reference blocks in the ${String.fromCharCode(96)}# Text Elements${String.fromCharCode(96)} section, because Obsidian does not index blocks within comment blocks. Image references are not effective, they will work.
|
||||
|
||||
<img src="https://github.com/zsviczian/obsidian-excalidraw-plugin/assets/14358394/bb96cdb4-8c5f-4dc5-ad39-7fccee6d5cac" referrerpolicy="no-referrer" style="width: 150px; margin: 0 auto;">
|
||||
<img src="https://github.com/zsviczian/obsidian-excalidraw-plugin/assets/14358394/e627fdb7-6820-4d7d-97f9-a030016be9aa" referrerpolicy="no-referrer" style="width: 100%; margin: 0 auto;">
|
||||
`,
|
||||
"2.1.0":`
|
||||
Bumping the version to 2.1.0 due to minor file format changes that aren't backward compatible. Essentially, 2.0.26 is already not backward compatible, but I forgot to update the version number.
|
||||
|
||||
If you haven't watched the [walkthrough video](https://youtu.be/tHUcD4rWIuY) for 2.0.26, I recommend you do so.
|
||||
|
||||
## New
|
||||
- Settings under ${String.fromCharCode(96)}Excalidraw Appearance and Behavior${String.fromCharCode(96)}
|
||||
- Configure visibility of the crosshair cursor when using the pen tool. [#1673](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1673)
|
||||
- Set the time delay for long press to open drawings from markdown under "Link Click and Modifier Keys".
|
||||
|
||||
##
|
||||
`,
|
||||
"2.0.26":`
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/tHUcD4rWIuY" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## New
|
||||
- Minor updates from [Excalidraw.com](https://excalidraw.com). The key change is text measurements that should result in consistent text sizing between desktop and mobile devices.
|
||||
- Now you can embed the markdown section of an Excalidraw note to your drawing. Simply select ${String.fromCharCode(96)}Insert ANY file${String.fromCharCode(96)}, choose the drawing, and select the relevant heading section to embed.
|
||||
- This also works with "back-of-the-drawing" markdown sections. Use the context menu ${String.fromCharCode(96)}Add back-of-note Card${String.fromCharCode(96)}. The action is also available on the Command Palette and in the Excalidraw-Obsidian Tools Panel.
|
||||
- Editing an embedded markdown note is now easier. Just press Enter when the element is selected. [#1650](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1650)
|
||||
- The crosshair cursor is now hidden when the freedraw tool is active and using a pen. [#1659](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1659)
|
||||
- ${String.fromCharCode(96)}Convert markdown note to Excalidraw Drawing${String.fromCharCode(96)} now converts an existing markdown note (not just empty notes) into a drawing. The original markdown note will be on the "back side of the drawing".
|
||||
- Introducing ${String.fromCharCode(96)}Annotate image in Excalidraw${String.fromCharCode(96)}, which works very similarly to ${String.fromCharCode(96)}Crop and mask image${String.fromCharCode(96)}. You can replace an image in a markdown note or on the Obsidian Canvas with an Excalidraw drawing containing that image. You will be able to annotate the image in Excalidraw.
|
||||
- Now you can reference frames in images embedded in markdown and canvas with frame names e.g. ${String.fromCharCode(96)}![[drawing#^frame=Frame 01]]${String.fromCharCode(96)}
|
||||
- Excalidraw file format change:
|
||||
- New frontmatter switch ${String.fromCharCode(96)}excalidraw-open-md${String.fromCharCode(96)}: If set to true, the file by default will open as a markdown file. You can switch to Excalidraw View Mode via the command palette action or by right-clicking the tab.
|
||||
- Easter Egg: If you add a comment in front of ${String.fromCharCode(96)}# Text Elements${String.fromCharCode(96)}, then the entire Excalidraw data: markdown and JSON will be commented out, thus invisible when exporting to the web. If you remove the comment from before ${String.fromCharCode(96)}# Text Elements${String.fromCharCode(96)}, then only the JSON will be commented out.
|
||||
|
||||
Before:
|
||||
${String.fromCharCode(96,96,96)}markdown
|
||||
#1657
|
||||
%%
|
||||
# Text Elements
|
||||
...
|
||||
# Drawing
|
||||
${String.fromCharCode(96,96,96)}
|
||||
|
||||
After:
|
||||
${String.fromCharCode(96,96,96)}markdown
|
||||
# Text Elements
|
||||
....
|
||||
%%
|
||||
# Drawing
|
||||
${String.fromCharCode(96,96,96)}
|
||||
`,
|
||||
"2.0.25":`
|
||||
# New - a small change that opens big opportunities
|
||||
- You can now set a folder as the Excalidraw Template in settings (See under Basic). If a folder is provided, Excalidraw will treat drawings in that folder as templates and will prompt you to select the template to use for new drawings.
|
||||
- I updated the <a href="https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Deconstruct%20selected%20elements%20into%20new%20drawing.md">Deconstruct Selected Elements into new Drawing</a> script to accommodate the new template setting.
|
||||
`,
|
||||
"2.0.24":`
|
||||
Quality of Life Fixes!
|
||||
|
||||
## Fixed
|
||||
- Text editing issue on mobile devices with an on-screen keyboard is now fixed 🥳. Previously, Excalidraw's UI fell apart when the keyboard was activated, and often even after you stopped editing, the canvas positioning was off. I hope to have solved the issue (we'll see after your testing and feedback!). This is one of those cases that seems insignificant but took enormous effort. It took me 2.5 full days of net time to figure out the root cause and the solution (this is not an exaggeration).
|
||||
- Tool buttons did not get selected on the first click.
|
||||
- Images flicker on Forced Save.
|
||||
- Hover preview fixes:
|
||||
- ${String.fromCharCode(96)}area=${String.fromCharCode(96)}, ${String.fromCharCode(96)}group=${String.fromCharCode(96)}, ${String.fromCharCode(96)}frame=${String.fromCharCode(96)} references now display the part of the image as expected in hover preview (showed an empty preview until now).
|
||||
- Block and section references to notes on the "back side of the drawing" now correctly show up in hover preview (showed an empty preview until now).
|
||||
|
||||
## New
|
||||
- Default height setting in Plugin Settings. Thanks @leoccyao! [#1612](https://github.com/zsviczian/obsidian-excalidraw-plugin/pull/1612)
|
||||
`,
|
||||
"2.0.23":`
|
||||
## New
|
||||
- Additional arrowheads (Circle, Circle Outline, Diamond, Diamond Outline, Triangle Outline) are now available via element properties.
|
||||
- Setting under "Links and Transclusions" to show/hide second-order links
|
||||
|
||||
## Fixed
|
||||
- some styling issues with dynamic styles (e.g.: text color of context menu)
|
||||
|
||||
## New in ExcalidrawAutomate
|
||||
- Excalidraw Publish Support: New hook to modify the link in the exported SVGs. This is useful when you want to export SVGs to your website. If set, this callback is triggered whenever a drawing is exported to SVG. The string returned by the hook will replace the link in the exported SVG. The hook is only executed if the link is to a file internal to Obsidian. [1605](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1605)
|
||||
${String.fromCharCode(96,96,96)}js
|
||||
onUpdateElementLinkForExportHook: (data: {
|
||||
* originalLink: string,
|
||||
* obsidianLink: string,
|
||||
* linkedFile: TFile | null,
|
||||
* hostFile: TFile,
|
||||
* }) => string = null;
|
||||
${String.fromCharCode(96,96,96)}
|
||||
`,
|
||||
"2.0.22":`
|
||||
## Fixed
|
||||
- BUG: Unable to load obsidian excalidraw plugin on ipad 15.x or older [#1525](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1525)
|
||||
- BUG: ea.help does not display help if only function signature is available [#1601](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1601)
|
||||
`,
|
||||
"2.0.21":`
|
||||
## New/changed
|
||||
**"Focus on Existing Tab"**
|
||||
- New Setting: Disabled by default.
|
||||
- Prevents multiple instances of the same drawing from opening when clicking on links within Excalidraw.
|
||||
- Overrides the "Reuse Adjacent Pane" option when the file is already open.
|
||||
- Accessible under "Links, Transclusions, and TODOs" in plugin settings.
|
||||
|
||||
**Enhanced Context Menu Functions for Text Containers**
|
||||
- Two new context menu functions added for containers with a text element:
|
||||
- Right-click to select the text element only, allowing independent color changes from the container.
|
||||
- Remove orphaned element links when the text element has a link but no longer includes a link in the text.
|
||||
|
||||
**Improved Laser Pointer Activation**
|
||||
- Laser pointer activation on double tap in view mode removed due to interference with link navigation and other features.
|
||||
- When the drawing is in "view" mode, laser pointer activation now available via long-press/right-click context menu.
|
||||
- Alternatively, activate the laser pointer with "k" if you have a keyboard.
|
||||
|
||||
## Fixed
|
||||
- **Older iOS and Android webview support**: Rebuilt all packages and dependencies with Node 18, hoping to address (sorry I can't reproduce/test these issues myself) compatibility issues with older iPad OS versions, up to 15.7. [#1525](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1525), and Android [1598](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1598)
|
||||
- **Double-click navigation**: Fixed the issue where double-clicking an embedded image did not navigate to the link in view mode.
|
||||
- **ExcaliBrain new file creation**: Resolved the issue with new file creation from ExcaliBrain. [#201](https://github.com/zsviczian/excalibrain/issues/201)
|
||||
- **Canvas immersive style**: Removed Canvas immersive embedding style support from the Excalidraw stylesheet to address performance issues experienced by some users with various Obsidian themes. If you require this feature, you can add a CSS snippet with the provided code.
|
||||
|
||||
${String.fromCharCode(96,96,96)}css
|
||||
.canvas-node:not(.is-editing):has(.excalidraw-canvas-immersive) {
|
||||
::-webkit-scrollbar,
|
||||
::-webkit-scrollbar-horizontal {
|
||||
display: none;
|
||||
}
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.canvas-node:not(.is-editing) .canvas-node-container:has(.excalidraw-canvas-immersive) {
|
||||
border: unset;
|
||||
box-shadow: unset;
|
||||
}
|
||||
${String.fromCharCode(96,96,96)}
|
||||
`,
|
||||
"2.0.20":`
|
||||
## Fixed in ExcalidrawAutomate
|
||||
- Regression: ${String.fromCharCode(96)}ea.getMaximumGroups(elements)${String.fromCharCode(96)} stopped working after the 2.0.19 update. [#1576](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1576)
|
||||
`,
|
||||
"2.0.19":`
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/4wp6vLiIdGM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
@@ -852,7 +1412,7 @@ ${String.fromCharCode(96,96,96)}`,
|
||||
## New
|
||||
- New scripts by @threethan:
|
||||
- [Auto Draw for Pen](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Auto%20Draw%20for%20Pen.md): Automatically switches between the select and draw tools, based on whether a pen is being used. Supports most pens including Apple Pencil.
|
||||
- [Hardware Eraser Support](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Hardware%20Eraser%20Support.md): Adds support for pen inversion, a.k.a. the hardware eraser on the back of your pen. Supports Windows based styluses. Does not suppoprt Apple Pencil or S-Pen.
|
||||
- [Hardware Eraser Support](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Hardware%20Eraser%20Support.md): Adds support for pen inversion, a.k.a. the hardware eraser on the back of your pen. Supports Windows based styluses. Does not support Apple Pencil or S-Pen.
|
||||
- Added separate buttons to support copying link, area or group references to objects on the drawing. [#1063](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1063). See [this video](https://youtu.be/yZQoJg2RCKI) for more details on how this works.
|
||||
- Hover preview will no longer trigger for image files (.png, .svg, .jpg, .gif, .webp, .bmp, .ico, .excalidraw)
|
||||
- Minor updates to the [Slideshow](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Slideshow.md) script. You can download the updated script from the Excalidraw script library. The slideshow will now correctly run also when initiated in a popout window. When the drawing is in a popout window, the slideshow will not be full screen, but will only occupy the popout window. If you run the slideshow from the main Obsidian workspace, it will be displayed in full-screen mode.
|
||||
|
||||
@@ -9,11 +9,17 @@ export enum openDialogAction {
|
||||
}
|
||||
|
||||
export class OpenFileDialog extends FuzzySuggestModal<TFile> {
|
||||
public app: App;
|
||||
private plugin: ExcalidrawPlugin;
|
||||
private action: openDialogAction;
|
||||
private onNewPane: boolean;
|
||||
|
||||
destroy() {
|
||||
this.app = null;
|
||||
this.plugin = null;
|
||||
this.action = null;
|
||||
this.inputEl.onkeyup = null;
|
||||
}
|
||||
|
||||
constructor(app: App, plugin: ExcalidrawPlugin) {
|
||||
super(app);
|
||||
this.app = app;
|
||||
|
||||
@@ -393,7 +393,7 @@ export class PenSettingsModal extends Modal {
|
||||
let spSetting: Setting;
|
||||
|
||||
new Setting(ce)
|
||||
.setName("Pressure sensitve pen?")
|
||||
.setName("Pressure sensitive pen?")
|
||||
.setDesc(fragWithHTML(`<b>toggle on</b>: pressure sensitive<br><b>toggle off</b>: constant pressure`))
|
||||
.addToggle(toggle =>
|
||||
toggle
|
||||
|
||||
@@ -8,20 +8,21 @@ import {
|
||||
TFile,
|
||||
Notice,
|
||||
TextAreaComponent,
|
||||
TFolder,
|
||||
} from "obsidian";
|
||||
import ExcalidrawView from "../ExcalidrawView";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { escapeRegExp, getLinkParts, sleep } from "../utils/Utils";
|
||||
import { getLeaf } from "../utils/ObsidianUtils";
|
||||
import { getLeaf, openLeaf } from "../utils/ObsidianUtils";
|
||||
import { checkAndCreateFolder, splitFolderAndFilename } from "src/utils/FileUtils";
|
||||
import { KeyEvent, isWinCTRLorMacCMD } from "src/utils/ModifierkeyHelper";
|
||||
import { t } from "src/lang/helpers";
|
||||
import { ExcalidrawElement, getEA } from "src";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { MAX_IMAGE_SIZE, REG_LINKINDEX_INVALIDCHARS } from "src/constants/constants";
|
||||
import { REGEX_LINK } from "src/ExcalidrawData";
|
||||
import { REGEX_LINK, REGEX_TAGS } from "src/ExcalidrawData";
|
||||
import { ScriptEngine } from "src/Scripts";
|
||||
import { openExternalLink, openTagSearch } from "src/utils/ExcalidrawViewUtils";
|
||||
import { openExternalLink, openTagSearch, parseObsidianLink } from "src/utils/ExcalidrawViewUtils";
|
||||
|
||||
export type ButtonDefinition = { caption: string; tooltip?:string; action: Function };
|
||||
|
||||
@@ -210,15 +211,15 @@ export class GenericInputPrompt extends Modal {
|
||||
}, 30);
|
||||
}
|
||||
|
||||
textComponent.inputEl.addEventListener("keydown", this.keyDownCallback);
|
||||
textComponent.inputEl.addEventListener('keyup', checkcaret); // Every character written
|
||||
textComponent.inputEl.addEventListener('pointerup', checkcaret); // Click down
|
||||
textComponent.inputEl.addEventListener('touchend', checkcaret); // Click down
|
||||
textComponent.inputEl.addEventListener('input', checkcaret); // Other input events
|
||||
textComponent.inputEl.addEventListener('paste', checkcaret); // Clipboard actions
|
||||
textComponent.inputEl.addEventListener('cut', checkcaret);
|
||||
textComponent.inputEl.addEventListener('select', checkcaret); // Some browsers support this event
|
||||
textComponent.inputEl.addEventListener('selectionchange', checkcaret);// Some browsers support this event
|
||||
textComponent.inputEl.addEventListener("keydown", this.keyDownCallback.bind(this));
|
||||
textComponent.inputEl.addEventListener('keyup', checkcaret.bind(this)); // Every character written
|
||||
textComponent.inputEl.addEventListener('pointerup', checkcaret.bind(this)); // Click down
|
||||
textComponent.inputEl.addEventListener('touchend', checkcaret.bind(this)); // Click down
|
||||
textComponent.inputEl.addEventListener('input', checkcaret.bind(this)); // Other input events
|
||||
textComponent.inputEl.addEventListener('paste', checkcaret.bind(this)); // Clipboard actions
|
||||
textComponent.inputEl.addEventListener('cut', checkcaret.bind(this));
|
||||
textComponent.inputEl.addEventListener('select', checkcaret.bind(this)); // Some browsers support this event
|
||||
textComponent.inputEl.addEventListener('selectionchange', checkcaret.bind(this));// Some browsers support this event
|
||||
|
||||
return textComponent;
|
||||
}
|
||||
@@ -271,18 +272,18 @@ export class GenericInputPrompt extends Modal {
|
||||
this.createButton(
|
||||
actionButtonContainer,
|
||||
"✅",
|
||||
this.submitClickCallback,
|
||||
this.submitClickCallback.bind(this),
|
||||
).setCta().buttonEl.style.marginRight = "0";
|
||||
}
|
||||
this.createButton(actionButtonContainer, "❌", this.cancelClickCallback, t("PROMPT_BUTTON_CANCEL"));
|
||||
this.createButton(actionButtonContainer, "❌", this.cancelClickCallback.bind(this), t("PROMPT_BUTTON_CANCEL"));
|
||||
if(this.displayEditorButtons) {
|
||||
this.createButton(editorButtonContainer, "⏎", ()=>this.insertStringBtnClickCallback("\n"), t("PROMPT_BUTTON_INSERT_LINE"), "0");
|
||||
this.createButton(editorButtonContainer, "⌫", this.delBtnClickCallback, "Delete");
|
||||
this.createButton(editorButtonContainer, "⌫", this.delBtnClickCallback.bind(this), "Delete");
|
||||
this.createButton(editorButtonContainer, "⎵", ()=>this.insertStringBtnClickCallback(" "), t("PROMPT_BUTTON_INSERT_SPACE"));
|
||||
if(this.view) {
|
||||
this.createButton(editorButtonContainer, "🔗", this.linkBtnClickCallback, t("PROMPT_BUTTON_INSERT_LINK"));
|
||||
this.createButton(editorButtonContainer, "🔗", this.linkBtnClickCallback.bind(this), t("PROMPT_BUTTON_INSERT_LINK"));
|
||||
}
|
||||
this.createButton(editorButtonContainer, "🔠", this.uppercaseBtnClickCallback, t("PROMPT_BUTTON_UPPERCASE"));
|
||||
this.createButton(editorButtonContainer, "🔠", this.uppercaseBtnClickCallback.bind(this), t("PROMPT_BUTTON_UPPERCASE"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,8 +342,13 @@ export class GenericInputPrompt extends Modal {
|
||||
this.inputComponent.inputEl.setSelectionRange(this.selectionStart, this.selectionEnd);
|
||||
}
|
||||
|
||||
private submitClickCallback = () => this.submit();
|
||||
private cancelClickCallback = () => this.cancel();
|
||||
private submitClickCallback () {
|
||||
this.submit();
|
||||
}
|
||||
|
||||
private cancelClickCallback () {
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
private keyDownCallback = (evt: KeyboardEvent) => {
|
||||
if ((evt.key === "Enter" && this.lines === 1) || (isWinCTRLorMacCMD(evt) && evt.key === "Enter")) {
|
||||
@@ -499,7 +505,7 @@ export class NewFileActions extends Modal {
|
||||
this.view = view;
|
||||
this.openNewFile = openNewFile;
|
||||
this.sourceElement = sourceElement;
|
||||
if(!parentFile) this.parentFile = view.file;
|
||||
this.parentFile = parentFile ?? view.file;
|
||||
this.waitForClose = new Promise<TFile|null>((resolve, reject) => {
|
||||
this.resolvePromise = resolve;
|
||||
this.rejectPromise = reject;
|
||||
@@ -515,13 +521,22 @@ export class NewFileActions extends Modal {
|
||||
if (!file || !this.openNewFile) {
|
||||
return;
|
||||
}
|
||||
const leaf = getLeaf(this.plugin,this.view.leaf,this.keys)
|
||||
leaf.openFile(file, {active:true});
|
||||
openLeaf({
|
||||
plugin: this.plugin,
|
||||
fnGetLeaf: () => getLeaf(this.plugin,this.view.leaf,this.keys),
|
||||
file,
|
||||
openState: { active: true },
|
||||
});
|
||||
}
|
||||
|
||||
onClose() {
|
||||
super.onClose();
|
||||
this.resolvePromise(this.newFile);
|
||||
this.app = null;
|
||||
this.plugin = null;
|
||||
this.view = null;
|
||||
this.parentFile = null;
|
||||
this.sourceElement = null;
|
||||
}
|
||||
|
||||
createForm(): void {
|
||||
@@ -583,7 +598,8 @@ export class NewFileActions extends Modal {
|
||||
ea.copyViewElementsToEAforEditing([this.sourceElement]);
|
||||
ea.getElement(this.sourceElement.id).isDeleted = true;
|
||||
ea.addEmbeddable(this.sourceElement.x, this.sourceElement.y,MAX_IMAGE_SIZE, MAX_IMAGE_SIZE, undefined,f);
|
||||
ea.addElementsToView();
|
||||
await ea.addElementsToView();
|
||||
ea.destroy();
|
||||
}
|
||||
this.close();
|
||||
};
|
||||
@@ -657,10 +673,10 @@ export class ConfirmationPrompt extends Modal {
|
||||
buttonContainer.style.display = "flex";
|
||||
buttonContainer.style.justifyContent = "flex-end";
|
||||
|
||||
const cancelButton = this.createButton(buttonContainer, t("PROMPT_BUTTON_CANCEL"), this.cancelClickCallback);
|
||||
const cancelButton = this.createButton(buttonContainer, t("PROMPT_BUTTON_CANCEL"), this.cancelClickCallback.bind(this));
|
||||
cancelButton.buttonEl.style.marginRight = "0.5rem";
|
||||
|
||||
const confirmButton = this.createButton(buttonContainer, t("PROMPT_BUTTON_OK"), this.confirmClickCallback);
|
||||
const confirmButton = this.createButton(buttonContainer, t("PROMPT_BUTTON_OK"), this.confirmClickCallback.bind(this));
|
||||
confirmButton.buttonEl.style.marginRight = "0";
|
||||
|
||||
cancelButton.buttonEl.focus();
|
||||
@@ -672,12 +688,12 @@ export class ConfirmationPrompt extends Modal {
|
||||
return button;
|
||||
}
|
||||
|
||||
private cancelClickCallback = () => {
|
||||
private cancelClickCallback() {
|
||||
this.didConfirm = false;
|
||||
this.close();
|
||||
};
|
||||
|
||||
private confirmClickCallback = () => {
|
||||
private confirmClickCallback() {
|
||||
this.didConfirm = true;
|
||||
this.close();
|
||||
};
|
||||
@@ -697,32 +713,53 @@ export class ConfirmationPrompt extends Modal {
|
||||
}
|
||||
}
|
||||
|
||||
export const linkPrompt = async (linkText:string, app: App, view?: ExcalidrawView):Promise<[file:TFile, linkText:string, subpath: string]> => {
|
||||
const partsArray = REGEX_LINK.getResList(linkText);
|
||||
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);
|
||||
let subpath: string = null;
|
||||
let file: TFile = null;
|
||||
let parts = partsArray[0];
|
||||
if (partsArray.length > 1) {
|
||||
parts = await ScriptEngine.suggester(
|
||||
app,
|
||||
partsArray.filter(p=>Boolean(p.value)).map(p => {
|
||||
const alias = REGEX_LINK.getAliasOrLink(p);
|
||||
return alias === "100%" ? REGEX_LINK.getLink(p) : alias;
|
||||
}),
|
||||
partsArray.filter(p=>Boolean(p.value)),
|
||||
"Select link to open"
|
||||
);
|
||||
if(!parts) return;
|
||||
}
|
||||
if(!parts) return;
|
||||
let parts = linksArray[0] ?? tagsArray[0];
|
||||
const itemsDisplay = [
|
||||
...linksArray.filter(p=> Boolean(p.value)).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)),
|
||||
];
|
||||
const items = [
|
||||
...linksArray.filter(p=>Boolean(p.value)),
|
||||
...tagsArray.filter(x=> Boolean(x.value)),
|
||||
];
|
||||
|
||||
if (items.length>1) {
|
||||
parts = await ScriptEngine.suggester(
|
||||
app,
|
||||
itemsDisplay,
|
||||
items,
|
||||
message,
|
||||
);
|
||||
if(!parts) return;
|
||||
}
|
||||
|
||||
if(!parts) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!parts.value) {
|
||||
openTagSearch(linkText, app);
|
||||
if (REGEX_TAGS.isTag(parts)) {
|
||||
openTagSearch(REGEX_TAGS.getTag(parts), app);
|
||||
return;
|
||||
}
|
||||
|
||||
linkText = REGEX_LINK.getLink(parts);
|
||||
if(openExternalLink(linkText, app)) return;
|
||||
const maybeObsidianLink = parseObsidianLink(linkText, app, false);
|
||||
if (typeof maybeObsidianLink === "boolean" && maybeObsidianLink) return;
|
||||
if (typeof maybeObsidianLink === "string") linkText = maybeObsidianLink;
|
||||
|
||||
if (linkText.search("#") > -1) {
|
||||
const linkParts = getLinkParts(linkText, view ? view.file : undefined);
|
||||
@@ -738,4 +775,14 @@ export const linkPrompt = async (linkText:string, app: App, view?: ExcalidrawVie
|
||||
view ? view.file.path : "",
|
||||
);
|
||||
return [file, linkText, subpath];
|
||||
}
|
||||
|
||||
export const templatePromt = async (files: TFile[], app: App): Promise<TFile> => {
|
||||
if(files.length === 1) return files[0];
|
||||
return ((await linkPrompt(
|
||||
files.map(f=>`[[${f.path}|${f.name}]]`).join(" "),
|
||||
app,
|
||||
undefined,
|
||||
t("PROMPT_SELECT_TEMPLATE")
|
||||
))??[null, null, null])[0];
|
||||
}
|
||||
44
src/dialogs/RankMessage.ts
Normal file
@@ -1,6 +1,7 @@
|
||||
import { MarkdownRenderer, Modal, Notice, request } from "obsidian";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { errorlog, escapeRegExp, log } from "../utils/Utils";
|
||||
import { errorlog, escapeRegExp } from "../utils/Utils";
|
||||
import { log } from "src/utils/DebugHelper";
|
||||
|
||||
const URL =
|
||||
"https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/index-new.md";
|
||||
@@ -21,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 = "→";
|
||||
|
||||
67
src/dialogs/SelectCard.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { App, FuzzySuggestModal, Notice, TFile } from "obsidian";
|
||||
import { t } from "../lang/helpers";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import { getEA } from "src";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { MD_EX_SECTIONS } from "src/constants/constants";
|
||||
import { addBackOfTheNoteCard } from "src/utils/ExcalidrawViewUtils";
|
||||
|
||||
export class SelectCard extends FuzzySuggestModal<string> {
|
||||
|
||||
constructor(
|
||||
public app: App,
|
||||
private view: ExcalidrawView,
|
||||
private sections: string[]
|
||||
) {
|
||||
super(app);
|
||||
this.limit = 20;
|
||||
this.setInstructions([
|
||||
{
|
||||
command: t("TYPE_SECTION"),
|
||||
purpose: "",
|
||||
},
|
||||
]);
|
||||
|
||||
this.inputEl.onkeyup = (e) => {
|
||||
if (e.key == "Enter") {
|
||||
if (this.containerEl.innerText.includes(t("EMPTY_SECTION_MESSAGE"))) {
|
||||
const item = this.inputEl.value;
|
||||
if(item === "" || MD_EX_SECTIONS.includes(item)) {
|
||||
new Notice(t("INVALID_SECTION_NAME"));
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
addBackOfTheNoteCard(this.view, item);
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
getItems(): string[] {
|
||||
return this.sections;
|
||||
}
|
||||
|
||||
getItemText(item: string): string {
|
||||
return item;
|
||||
}
|
||||
|
||||
onChooseItem(item: string): void {
|
||||
const ea = getEA(this.view) as ExcalidrawAutomate;
|
||||
const id = ea.addEmbeddable(
|
||||
0,0,400,500,
|
||||
`[[${this.view.file.path}#${item}]]`
|
||||
);
|
||||
(async () => {
|
||||
await ea.addElementsToView(true, false, true);
|
||||
ea.selectElementsInView([id]);
|
||||
ea.destroy();
|
||||
})();
|
||||
}
|
||||
|
||||
public start(): void {
|
||||
this.emptyStateText = t("EMPTY_SECTION_MESSAGE");
|
||||
this.setPlaceholder(t("SELECT_SECTION_OR_TYPE_NEW"));
|
||||
this.open();
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,9 @@ const hyperlink = (url: string, text: string) => {
|
||||
return `<a onclick='window.open("${url}")'>${text}</a>`;
|
||||
}
|
||||
|
||||
const EMBEDDABLE_MDCUSTOMPROPS = `type EmbeddableMDCustomProps = {<br>useObsidianDefaults: boolean;<br>backgroundMatchCanvas: boolean;<br>backgroundMatchElement: boolean;<br>backgroundColor: string;<br>backgroundOpacity: number;<br>borderMatchElement: boolean;<br>borderColor: string;<br>borderOpacity: number;<br>filenameVisible: boolean;<br>};<br>`;
|
||||
|
||||
|
||||
export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
{
|
||||
field: "help",
|
||||
@@ -97,7 +100,7 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
{
|
||||
field: "style.fontFamily",
|
||||
code: "[number]",
|
||||
desc: "1: Virgil, 2:Helvetica, 3:Cascadia, 4:LocalFont",
|
||||
desc: "1: Virgil, 2:Helvetica, 3:Cascadia, 4:Local Font, 5: Excalifont, 6: Nunito, 7: Lilita One, 8: Comic Shanns, 9: Liberation Sans",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
@@ -197,6 +200,7 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
' "excalidraw-export-dark"?: boolean;\n' +
|
||||
' "excalidraw-export-padding"?: number;\n' +
|
||||
' "excalidraw-export-pngscale"?: number;\n' +
|
||||
' "excalidraw-export-embed-scene"?: boolean;\n' +
|
||||
' "excalidraw-default-mode"?: "view" | "zen";\n' +
|
||||
' "excalidraw-onload-script"?: string;\n' +
|
||||
' "excalidraw-linkbutton-opacity"?: number;\n' +
|
||||
@@ -229,6 +233,18 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
desc: null,
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "addElementsToFrame",
|
||||
code: "addElementsToFrame(frameId: string, elementIDs: string[]):void;",
|
||||
desc: null,
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "addFrame",
|
||||
code: "addFrame(topX: number, topY: number, width: number, height: number, name?: string): string;",
|
||||
desc: null,
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "addRect",
|
||||
code: "addRect(topX: number, topY: number, width: number, height: number, id?:string): string;",
|
||||
@@ -261,8 +277,10 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
},
|
||||
{
|
||||
field: "addText",
|
||||
code: 'addText(topX: number, topY: number, text: string, formatting?: {wrapAt?: number; width?: number; height?: number; textAlign?: "left" | "center" | "right"; textVerticalAlign: "top" | "middle" | "bottom"; box?: boolean | "box" | "blob" | "ellipse" | "diamond"; boxPadding?: number; boxStrokeColor?: string;}, id?: string,): string;',
|
||||
desc: "If box is !null, then text will be boxed\nThe function returns the id of the TextElement. If the text element is boxed i.e. it is a sticky note, then the id of the container object",
|
||||
code: 'addText(topX: number, topY: number, text: string, formatting?: {autoResize?: boolean; wrapAt?: number; width?: number; height?: number; textAlign?: "left" | "center" | "right"; textVerticalAlign: "top" | "middle" | "bottom"; box?: boolean | "box" | "blob" | "ellipse" | "diamond"; boxPadding?: number; boxStrokeColor?: string;}, id?: string,): string;',
|
||||
desc: "If box is !null, then text will be boxed\nThe function returns the id of the TextElement. If the text element is boxed i.e. it is a sticky note, then the id of the container object.\n"+
|
||||
"Default value for autoResize is true. Setting autoResize to false will wrap the text in the text element without the need for the container. If set to false, you must provide a width value as well.\n" +
|
||||
"wrapAt will be ignored if autoResize is set to false (and a width is also provided)",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
@@ -285,8 +303,9 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
},
|
||||
{
|
||||
field: "addEmbeddable",
|
||||
code: "addEmbeddable(topX: number, topY: number, width: number, height: number, url?: string, file?: TFile): string;",
|
||||
desc: "Adds an iframe/webview (depending on content and platform) to the drawing. If url is not null then the iframe/webview will be loaded from the url. The url maybe a markdown link to an note in the Vault or a weblink. If url is null then the iframe/webview will be loaded from the file. Both the url and the file may not be null.",
|
||||
code: "addEmbeddable(topX: number, topY: number, width: number, height: number, url?: string, file?: TFile, embeddableCustomData?: EmbeddableMDCustomProps): string;",
|
||||
desc: "Adds an iframe/webview (depending on content and platform) to the drawing. If url is not null then the iframe/webview will be loaded from the url. The url maybe a markdown link to an note in the Vault or a weblink. " +
|
||||
"If url is null then the iframe/webview will be loaded from the file. Both the url and the file may not be null.<br>" + EMBEDDABLE_MDCUSTOMPROPS,
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
@@ -304,6 +323,12 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
desc: "This is an async function, you need to avait the results. Adds a LaTex element to the drawing. The tex string is the LaTex code. The function returns the id of the created element.",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "tex2dataURL",
|
||||
code: "async tex2dataURL(tex: string, scale: number = 4): Promise<{mimeType: MimeType;fileId: FileId;dataURL: DataURL;created: number;size: { height: number; width: number };}> ",
|
||||
desc: "returns the base64 dataURL of the LaTeX equation rendered as an SVG. tex is the LaTeX equation string",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "connectObjects",
|
||||
code: "connectObjects(objectA: string, connectionA: ConnectionPoint, objectB: string, connectionB: ConnectionPoint, formatting?: {numberOfPoints?: number; startArrowHead?: string; endArrowHead?: string; padding?: number;},): string;",
|
||||
@@ -364,6 +389,14 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
desc: null,
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "addBackOfTheCardNoteToView",
|
||||
code: "async addBackOfTheCardNoteToView(sectionTitle: string, activate: boolean = false, sectionBody?: string, embeddableCustomData?: EmbeddableMDCustomProps): Promise<string>",
|
||||
desc: "Adds a back of the note card to the current active view. If <b>body</b> is provided the note will be created with the body text, otherwise the note will be created with the title only.<br>Returns the id of the created element.<br>" +
|
||||
"If <b>activate</b> is true, the embedded note will be activated for editing.<br>" +
|
||||
"This is an async function, if you need the element ID of the created element, the function should be awaited.<br>" + EMBEDDABLE_MDCUSTOMPROPS,
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "getViewSelectedElement",
|
||||
code: "getViewSelectedElement(): ExcalidrawElement;",
|
||||
@@ -372,8 +405,8 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
},
|
||||
{
|
||||
field: "getViewSelectedElements",
|
||||
code: "getViewSelectedElements(): ExcalidrawElement[];",
|
||||
desc: null,
|
||||
code: "getViewSelectedElements(includeFrameChildren: boolean = true): ExcalidrawElement[];",
|
||||
desc: "If a frame is selected this function will return the frame and all its elements unless includeFrameChildren is set to false",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
@@ -427,7 +460,19 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
{
|
||||
field: "getExportSettings",
|
||||
code: "getExportSettings(withBackground: boolean, withTheme: boolean,): ExportSettings;",
|
||||
desc: "Utility function to generate ExportSettings object",
|
||||
desc: "Utility function to generate ExportSettings object\n" +
|
||||
"export interface ExportSettings {\n" +
|
||||
" withBackground: boolean;\n" +
|
||||
" withTheme: boolean;\n" +
|
||||
" isMask: boolean; //if true elements will be processed as mask, clipping, etc.\n" +
|
||||
" frameRendering?: { //optional, overrides relevant appState settings for rendering the frame\n" +
|
||||
" enabled: boolean;\n" +
|
||||
" name: boolean;\n" +
|
||||
" outline: boolean;\n" +
|
||||
" clip: boolean;\n" +
|
||||
" };\n" +
|
||||
" skipInliningFonts?: boolean;\n" +
|
||||
"}",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
@@ -462,10 +507,17 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
},
|
||||
{
|
||||
field: "getElementsInTheSameGroupWithElement",
|
||||
code: "getElementsInTheSameGroupWithElement(element: ExcalidrawElement, elements: ExcalidrawElement[]): ExcalidrawElement[];",
|
||||
desc: "Gets all the elements from elements[] that share one or more groupIds with element.",
|
||||
code: "getElementsInTheSameGroupWithElement(element: ExcalidrawElement, elements: ExcalidrawElement[], includeFrameElements: boolean = false): ExcalidrawElement[];",
|
||||
desc: "Gets all the elements from elements[] that share one or more groupIds with element.<br>" +
|
||||
"If includeFrameElements is true, then if the frame is part of the group all the elements that are in the frame will also be included in the result set",
|
||||
after: ""
|
||||
},
|
||||
{
|
||||
field: "getElementsInFrame",
|
||||
code: " getElementsInFrame(frameElement: ExcalidrawElement,elements: ExcalidrawElement[],shouldIncludeFrame: boolean = false,): ExcalidrawElement[];",
|
||||
desc: "Gets all the elements from elements[] that are inside the frameElement. If shouldIncludeFrame is true, the frameElement will also be included in the result.",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "activeScript",
|
||||
code: "activeScript: string;",
|
||||
@@ -496,6 +548,23 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
desc: "Measures text size based on current style settings",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "getOriginalImageSize",
|
||||
code: "async getOriginalImageSize(imageElement: ExcalidrawImageElement, shouldWaitForImage: boolean=false): Promise<{width: number; height: number}>",
|
||||
desc: "Returns the size of the image element at 100% (i.e. the original size) or undefined if the data URL is not available.\n"+
|
||||
"If shouldWaitForImage is true, the function will wait for the view to load the image before returning the size.\n"+
|
||||
"This is an async function, you need to await the result.",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "resetImageAspectRatio",
|
||||
code: "async resetImageAspectRatio(imgEl: ExcalidrawImageElement): Promise<boolean>",
|
||||
desc: "Resets the image to its original aspect ratio.\n" +
|
||||
"If the image is resized then the function returns true.\n" +
|
||||
"If the image element is not in EA (only in the view), then if the image is resized, the element is copied to EA for Editing using copyViewElementsToEAforEditing([imgEl]).\n" +
|
||||
"Note you need to run await ea.addElementsToView(false); to add the modified image to the view.",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "verifyMinimumPluginVersion",
|
||||
code: "verifyMinimumPluginVersion(requiredVersion: string): boolean;",
|
||||
@@ -556,12 +625,41 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
desc: "Converts a CSS color name to its HEX color equivalent. 'White' to #FFFFFF",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "getCM",
|
||||
code: "getCM(color:TInput): ColorMaster;",
|
||||
desc: `Returns a ${hyperlink("https://github.com/lbragile/ColorMaster", "ColorMaster")} object. ` +
|
||||
"The function also accepts css color names. Under the hood, before calling ColorMaster it uses " +
|
||||
"colorNameToHex to convert the color name to a HEX color.",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "obsidian",
|
||||
code: "obsidian",
|
||||
desc: `Access functions and objects available on the ${hyperlink("https://github.com/obsidianmd/obsidian-api/blob/master/obsidian.d.ts","Obsidian Module")}`,
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "getListOfTemplateFiles",
|
||||
code: "getListOfTemplateFiles(): TFile[] | null",
|
||||
desc: "Returns a list of files in the template folder. " +
|
||||
"If the Excalidraw Template is set as a single file, it returns a single element in the list. " +
|
||||
"If no template is set, it returns null.",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "getEmbeddedImagesFiletree",
|
||||
code: "getEmbeddedImagesFiletree(excalidrawFile?: TFile): TFile[]",
|
||||
desc: "Retruns the embedded images in the scene recursively. If excalidrawFile is not provided, " +
|
||||
"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>",
|
||||
@@ -595,7 +693,7 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
{
|
||||
field: "getleaf",
|
||||
code: "getLeaf(origo: WorkspaceLeaf, targetPane?: PaneTarget): WorkspaceLeaf;",
|
||||
desc: "Generates a new Obsidian Leaf following Excalidraw plugin settings such as open in Main Workspace or not, open in adjacent pane if avaialble, etc.<br>" +
|
||||
desc: "Generates a new Obsidian Leaf following Excalidraw plugin settings such as open in Main Workspace or not, open in adjacent pane if available, etc.<br>" +
|
||||
"@param origo: the currently active leaf, the origin of the new leaf<br>" +
|
||||
'@param targetPane: <code>type PaneTarget = "active-pane"|"new-pane"|"popout-window"|"new-tab"|"md-properties";',
|
||||
after: "",
|
||||
@@ -643,7 +741,7 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
},
|
||||
{
|
||||
field: "postOpenAI",
|
||||
code: "async postOpenAI(requst: AIRequest): Promise<RequestUrlResponse>",
|
||||
code: "async postOpenAI(request: AIRequest): Promise<RequestUrlResponse>",
|
||||
desc:
|
||||
"This asynchronous function should be awaited. It posts the supplied request to the OpenAI API and returns the response.<br>" +
|
||||
"The response is a dictionary with the following keys:<br><code>{image, text, instruction, systemPrompt, responseType}</code><br>"+
|
||||
@@ -670,8 +768,9 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
},
|
||||
{
|
||||
field: "viewUpdateScene",
|
||||
code: "viewUpdateScene(scene:{elements?:ExcalidrawElement[],appState?: AppState,files?: BinaryFileData,commitToHistory?: boolean,},restore:boolean=false):void",
|
||||
desc: "Calls the ExcalidrawAPI updateScene function for the targetView. When restore=true, excalidraw will try to correct errors in the scene such as setting default values to missing element properties.",
|
||||
code: "viewUpdateScene(scene:{elements?:ExcalidrawElement[],appState?: AppState,files?: BinaryFileData,commitToHistory?: boolean,storeAction?: 'capture' | 'none' | 'update'},restore:boolean=false):void",
|
||||
desc: "Calls the ExcalidrawAPI updateScene function for the targetView. When restore=true, excalidraw will try to correct errors in the scene such as setting default values to missing element properties. " +
|
||||
`Note that commitToHistory has been deprecated in Excalidraw and is no longer used. You should use storeAction instead. See ${hyperlink("https://github.com/excalidraw/excalidraw/pull/7898", "ExcalidrawAPI")} documentation for more information.`,
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
@@ -813,6 +912,18 @@ export const FRONTMATTER_KEYS_INFO: SuggesterInfo[] = [
|
||||
desc: "If this key is present it will override the default excalidraw embed and export setting. 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.",
|
||||
after: ": 1",
|
||||
},
|
||||
{
|
||||
field: "export-embed-scene",
|
||||
code: null,
|
||||
desc: "If this key is present it will override the default excalidraw embed and export setting.",
|
||||
after: ": false",
|
||||
},
|
||||
{
|
||||
field: "open-md",
|
||||
code: null,
|
||||
desc: "If this key is present the file will be opened as a markdown file in the editor",
|
||||
after: ": true",
|
||||
},
|
||||
{
|
||||
field: "autoexport",
|
||||
code: null,
|
||||
@@ -820,9 +931,9 @@ export const FRONTMATTER_KEYS_INFO: SuggesterInfo[] = [
|
||||
after: ": png",
|
||||
},
|
||||
{
|
||||
field: "iframe-theme",
|
||||
field: "embeddable-theme",
|
||||
code: null,
|
||||
desc: "Override iFrame theme plugin-settings for this file. 'match' will match the Excalidraw theme, 'default' will match the obsidian theme. Valid values are\ndark\nlight\nauto\ndefault",
|
||||
desc: "Override embeddable's theme plugin-settings for this file. 'auto' will match the Excalidraw theme, 'default' will match the Obsidian theme. Valid values are\ndark\nlight\nauto\ndefault",
|
||||
after: ": auto",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@ import ExcalidrawView from "../ExcalidrawView";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { Modal, Setting, TextComponent } from "obsidian";
|
||||
import { FileSuggestionModal } from "./FolderSuggester";
|
||||
import { IMAGE_TYPES, sceneCoordsToViewportCoords, viewportCoordsToSceneCoords, MAX_IMAGE_SIZE, ANIMATED_IMAGE_TYPES } from "src/constants/constants";
|
||||
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";
|
||||
import { InsertPDFModal } from "./InsertPDFModal";
|
||||
@@ -19,7 +19,7 @@ export class UniversalInsertFileModal extends Modal {
|
||||
private plugin: ExcalidrawPlugin,
|
||||
private view: ExcalidrawView,
|
||||
) {
|
||||
super(app);
|
||||
super(plugin.app);
|
||||
const appState = (view.excalidrawAPI as ExcalidrawImperativeAPI).getAppState();
|
||||
const containerRect = view.containerEl.getBoundingClientRect();
|
||||
const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
|
||||
@@ -78,6 +78,7 @@ export class UniversalInsertFileModal extends Modal {
|
||||
|
||||
const updateForm = async () => {
|
||||
const ea = this.plugin.ea;
|
||||
const isSelf = file === this.view.file;
|
||||
const isMarkdown = file && file.extension === "md" && !ea.isExcalidrawFile(file);
|
||||
const isImage = file && (IMAGE_TYPES.contains(file.extension) || ea.isExcalidrawFile(file));
|
||||
const isAnimatedImage = file && ANIMATED_IMAGE_TYPES.contains(file.extension);
|
||||
@@ -85,39 +86,43 @@ export class UniversalInsertFileModal extends Modal {
|
||||
const isPDF = file && file.extension === "pdf";
|
||||
const isExcalidraw = file && ea.isExcalidrawFile(file);
|
||||
|
||||
if (isMarkdown) {
|
||||
const sections = (file && file.extension === "md")
|
||||
? (await this.plugin.app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "heading")
|
||||
.filter((b: any) => !isExcalidraw || !MD_EX_SECTIONS.includes(b.display))
|
||||
: null;
|
||||
|
||||
if (isMarkdown || (isExcalidraw && sections?.length > 0)) {
|
||||
sectionPickerSetting.settingEl.style.display = "";
|
||||
sectionPicker.selectEl.style.display = "block";
|
||||
while(sectionPicker.selectEl.options.length > 0) {
|
||||
sectionPicker.selectEl.remove(0);
|
||||
}
|
||||
sectionPicker.addOption("","");
|
||||
(await app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "heading")
|
||||
.forEach((b: any) => {
|
||||
sectionPicker.addOption(
|
||||
`#${cleanSectionHeading(b.display)}`,
|
||||
b.display)
|
||||
});
|
||||
if(!isExcalidraw) sectionPicker.addOption("","");
|
||||
sections.forEach((b: any) => {
|
||||
sectionPicker.addOption(
|
||||
`#${cleanSectionHeading(b.display)}`,
|
||||
b.display)
|
||||
});
|
||||
} else {
|
||||
sectionPickerSetting.settingEl.style.display = "none";
|
||||
sectionPicker.selectEl.style.display = "none";
|
||||
}
|
||||
|
||||
if (isExcalidraw) {
|
||||
if (isExcalidraw && !isSelf) {
|
||||
sizeToggleSetting.settingEl.style.display = "";
|
||||
} else {
|
||||
sizeToggleSetting.settingEl.style.display = "none";
|
||||
}
|
||||
|
||||
if (isImage || (file?.extension === "md")) {
|
||||
if (!isSelf && (isImage || (file?.extension === "md"))) {
|
||||
actionImage.buttonEl.style.display = "block";
|
||||
} else {
|
||||
actionImage.buttonEl.style.display = "none";
|
||||
}
|
||||
|
||||
if (isIFrame || isAnimatedImage) {
|
||||
if (isIFrame || isAnimatedImage || (isExcalidraw && sections?.length > 0)) {
|
||||
actionIFrame.buttonEl.style.display = "block";
|
||||
} else {
|
||||
actionIFrame.buttonEl.style.display = "none";
|
||||
@@ -131,9 +136,17 @@ export class UniversalInsertFileModal extends Modal {
|
||||
|
||||
}
|
||||
|
||||
const sections = (await this.plugin.app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },this.view.file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "heading")
|
||||
.filter((b: any) => !MD_EX_SECTIONS.includes(b.display));
|
||||
|
||||
const search = new TextComponent(ce);
|
||||
search.inputEl.style.width = "100%";
|
||||
const suggester = new FileSuggestionModal(this.app, search,app.vault.getFiles().filter((f: TFile) => f!==this.view.file));
|
||||
const suggester = new FileSuggestionModal(
|
||||
this.app,
|
||||
search,
|
||||
this.app.vault.getFiles().filter((f: TFile) => sections?.length > 0 || f!==this.view.file));
|
||||
search.onChange(() => {
|
||||
file = suggester.getSelectedItem();
|
||||
updateForm();
|
||||
@@ -176,6 +189,7 @@ export class UniversalInsertFileModal extends Modal {
|
||||
`[[${path}${sectionPicker.selectEl.value}]]`,
|
||||
)]
|
||||
);
|
||||
ea.destroy();
|
||||
this.close();
|
||||
})
|
||||
actionIFrame = button;
|
||||
@@ -207,6 +221,7 @@ export class UniversalInsertFileModal extends Modal {
|
||||
ea.isExcalidrawFile(file) ? !anchorTo100 : undefined,
|
||||
)]
|
||||
);
|
||||
ea.destroy();
|
||||
this.close();
|
||||
})
|
||||
actionImage = button;
|
||||
@@ -258,5 +273,8 @@ export class UniversalInsertFileModal extends Modal {
|
||||
|
||||
onClose(): void {
|
||||
this.view.ownerWindow.removeEventListener("keydown", this.onKeyDown);
|
||||
this.view = null;
|
||||
this.file = null;
|
||||
this.plugin = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ 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";
|
||||
|
||||
// English
|
||||
@@ -9,6 +10,7 @@ export default {
|
||||
// main.ts
|
||||
CONVERT_URL_TO_FILE: "Save image from URL to local file",
|
||||
UNZIP_CURRENT_FILE: "Decompress current Excalidraw file",
|
||||
ZIP_CURRENT_FILE: "Compress current Excalidraw file",
|
||||
PUBLISH_SVG_CHECK: "Obsidian Publish: Find SVG and PNG exports that are out of date",
|
||||
EMBEDDABLE_PROPERTIES: "Embeddable Properties",
|
||||
EMBEDDABLE_RELATIVE_ZOOM: "Scale selected embeddable elements to 100% relative to the current canvas zoom",
|
||||
@@ -23,7 +25,7 @@ export default {
|
||||
"Script is up to date - Click to reinstall",
|
||||
OPEN_AS_EXCALIDRAW: "Open as Excalidraw Drawing",
|
||||
TOGGLE_MODE: "Toggle between Excalidraw and Markdown mode",
|
||||
CONVERT_NOTE_TO_EXCALIDRAW: "Convert empty note to Excalidraw Drawing",
|
||||
CONVERT_NOTE_TO_EXCALIDRAW: "Convert markdown note to Excalidraw Drawing",
|
||||
CONVERT_EXCALIDRAW: "Convert *.excalidraw to *.md files",
|
||||
CREATE_NEW: "Create new drawing",
|
||||
CONVERT_FILE_KEEP_EXT: "*.excalidraw => *.excalidraw.md",
|
||||
@@ -35,6 +37,8 @@ export default {
|
||||
TRANSCLUDE: "Embed a drawing",
|
||||
TRANSCLUDE_MOST_RECENT: "Embed the most recently edited drawing",
|
||||
TOGGLE_LEFTHANDED_MODE: "Toggle left-handed mode",
|
||||
TOGGLE_SPLASHSCREEN: "Show splash screen in new drawings",
|
||||
FLIP_IMAGE: "Open the back-of-the-note of the selected excalidraw image",
|
||||
NEW_IN_NEW_PANE: "Create new drawing - IN AN ADJACENT WINDOW",
|
||||
NEW_IN_NEW_TAB: "Create new drawing - IN A NEW TAB",
|
||||
NEW_IN_ACTIVE_PANE: "Create new drawing - IN THE CURRENT ACTIVE WINDOW",
|
||||
@@ -48,16 +52,20 @@ export default {
|
||||
NEW_IN_POPOUT_WINDOW_EMBED: "Create new drawing - IN A POPOUT WINDOW - and embed into active document",
|
||||
TOGGLE_LOCK: "Toggle Text Element between edit RAW and PREVIEW",
|
||||
DELETE_FILE: "Delete selected image or Markdown file from Obsidian Vault",
|
||||
COPY_ELEMENT_LINK: "Copy [[link]] for selected element(s)",
|
||||
COPY_DRAWING_LINK: "Copy ![[embed link]] for this drawing",
|
||||
INSERT_LINK_TO_ELEMENT:
|
||||
`Copy markdown link for selected element to clipboard. ${labelCTRL()}+CLICK to copy 'group=' link. ${labelSHIFT()}+CLICK to copy an 'area=' link. ${labelALT()}+CLICK to watch a help video.`,
|
||||
`Copy [[link]] for selected element to clipboard. ${labelCTRL()}+CLICK to copy 'group=' link. ${labelSHIFT()}+CLICK to copy an 'area=' link.`,
|
||||
INSERT_LINK_TO_ELEMENT_GROUP:
|
||||
"Copy 'group=' markdown link for selected element to clipboard.",
|
||||
"Copy 'group=' ![[link]] for selected element to clipboard.",
|
||||
INSERT_LINK_TO_ELEMENT_AREA:
|
||||
"Copy 'area=' markdown link for selected element to clipboard.",
|
||||
"Copy 'area=' ![[link]] for selected element to clipboard.",
|
||||
INSERT_LINK_TO_ELEMENT_FRAME:
|
||||
"Copy 'frame=' markdown link for selected element to clipboard.",
|
||||
"Copy 'frame=' ![[link]] for selected element to clipboard.",
|
||||
INSERT_LINK_TO_ELEMENT_FRAME_CLIPPED:
|
||||
"Copy 'clippedframe=' ![[link]] for selected element to clipboard.",
|
||||
INSERT_LINK_TO_ELEMENT_NORMAL:
|
||||
"Copy markdown link for selected element to clipboard.",
|
||||
"Copy [[link]] for selected element to clipboard.",
|
||||
INSERT_LINK_TO_ELEMENT_ERROR: "Select a single element in the scene",
|
||||
INSERT_LINK_TO_ELEMENT_READY: "Link is READY and available on the clipboard",
|
||||
INSERT_LINK: "Insert link to file",
|
||||
@@ -68,20 +76,51 @@ export default {
|
||||
INSERT_MD: "Insert markdown file from vault",
|
||||
INSERT_PDF: "Insert PDF file from vault",
|
||||
UNIVERSAL_ADD_FILE: "Insert ANY file",
|
||||
INSERT_CARD: "Add back-of-note card",
|
||||
CONVERT_CARD_TO_FILE: "Move back-of-note card to File",
|
||||
ERROR_TRY_AGAIN: "Please try again.",
|
||||
PASTE_CODEBLOCK: "Paste code block",
|
||||
INSERT_LATEX:
|
||||
`Insert LaTeX formula (e.g. \\binom{n}{k} = \\frac{n!}{k!(n-k)!}). ${labelALT()}+CLICK to watch a help video.`,
|
||||
`Insert LaTeX formula (e.g. \\binom{n}{k} = \\frac{n!}{k!(n-k)!}).`,
|
||||
ENTER_LATEX: "Enter a valid LaTeX expression",
|
||||
READ_RELEASE_NOTES: "Read latest release notes",
|
||||
RUN_OCR: "OCR: Grab text from freedraw scribble and pictures to clipboard",
|
||||
RUN_OCR: "OCR full drawing: Grab text from freedraw + images to clipboard and doc.props",
|
||||
RERUN_OCR: "OCR full drawing re-run: Grab text from freedraw + images to clipboard and doc.props",
|
||||
RUN_OCR_ELEMENTS: "OCR selected elements: Grab text from freedraw + images to clipboard",
|
||||
TRAY_MODE: "Toggle property-panel tray-mode",
|
||||
SEARCH: "Search for text in drawing",
|
||||
CROP_PAGE: "Crop and mask selected page",
|
||||
CROP_IMAGE: "Crop and mask image",
|
||||
ANNOTATE_IMAGE : "Annotate image in Excalidraw",
|
||||
INSERT_ACTIVE_PDF_PAGE_AS_IMAGE: "Insert active PDF page as image",
|
||||
RESET_IMG_TO_100: "Set selected image element size to 100% of original",
|
||||
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",
|
||||
|
||||
//ExcalidrawView.ts
|
||||
NO_SEARCH_RESULT: "Didn't find a matching element in the drawing",
|
||||
FORCE_SAVE_ABORTED: "Force Save aborted because saving is in progress",
|
||||
LINKLIST_SECOND_ORDER_LINK: "Second Order Link",
|
||||
MARKDOWN_EMBED_CUSTOMIZE_LINK_PROMPT_TITLE: "Customize the Embedded File link",
|
||||
MARKDOWN_EMBED_CUSTOMIZE_LINK_PROMPT: "Do not add [[square brackets]] around the filename!<br>" +
|
||||
"For markdown-page images follow this format when editing your link: <mark>filename#^blockref|WIDTHxMAXHEIGHT</mark><br>" +
|
||||
"You can anchor Excalidraw images to 100% of their size by adding <code>|100%</code> to the end of the link.<br>" +
|
||||
"You can change the PDF page by changing <code>#page=1</code> to <code>#page=2</code> etc.<br>" +
|
||||
"PDF rect crop values are: <code>left, bottom, right, top</code>. Eg.: <code>#rect=0,0,500,500</code><br>",
|
||||
FRAME_CLIPPING_ENABLED: "Frame Rendering: Enabled",
|
||||
FRAME_CLIPPING_DISABLED: "Frame Rendering: Disabled",
|
||||
ARROW_BINDING_INVERSE_MODE: "Inverted Mode: Default arrow binding is now disabled. Use CTRL/CMD to temporarily enable binding when needed.",
|
||||
ARROW_BINDING_NORMAL_MODE: "Normal Mode: Arrow binding is now enabled. Use CTRL/CMD to temporarily disable binding when needed.",
|
||||
EXPORT_FILENAME_PROMPT: "Please provide filename",
|
||||
EXPORT_FILENAME_PROMPT_PLACEHOLDER: "filename, leave blank to cancel action",
|
||||
WARNING_SERIOUS_ERROR: "WARNING: Excalidraw ran into an unknown problem!\n\n" +
|
||||
"There is a risk that your most recent changes cannot be saved.\n\n" +
|
||||
"To be on the safe side...\n" +
|
||||
"1) Please select your drawing using CTRL/CMD+A and make a copy with CTRL/CMD+C.\n" +
|
||||
"2) Then create an empty drawing in a new pane by CTRL/CMD+clicking the Excalidraw ribbon button,\n" +
|
||||
"3) and paste your work to the new document with CTRL/CMD+V.",
|
||||
ARIA_LABEL_TRAY_MODE: "Tray-mode offers an alternative, more spacious canvas",
|
||||
MASK_FILE_NOTICE: "This is a mask file. It is used to crop images and mask out parts of the image. Press and hold notice to open the help video.",
|
||||
INSTALL_SCRIPT_BUTTON: "Install or update Excalidraw Scripts",
|
||||
OPEN_AS_MD: "Open as Markdown",
|
||||
@@ -105,10 +144,33 @@ export default {
|
||||
BACKUP_RESTORED: "Backup restored",
|
||||
CACHE_NOT_READY: "I apologize for the inconvenience, but an error occurred while loading your file.<br><br><mark>Having a little patience can save you a lot of time...</mark><br><br>The plugin has a backup cache, but it appears that you have just started Obsidian. Initializing the Backup Cache may take some time, usually up to a minute or more depending on your device's performance. You will receive a notification in the top right corner when the cache initialization is complete.<br><br>Please press OK to attempt loading the file again and check if the cache has finished initializing. If you see a completely empty file behind this message, I recommend waiting until the backup cache is ready before proceeding. Alternatively, you can choose Cancel to manually correct your file.<br>",
|
||||
OBSIDIAN_TOOLS_PANEL: "Obsidian Tools Panel",
|
||||
ERROR_SAVING_IMAGE: "Unknown error occured while fetching the image. It could be that for some reason the image is not available or rejected the fetch request from Obsidian",
|
||||
ERROR_SAVING_IMAGE: "Unknown error occurred while fetching the image. It could be that for some reason the image is not available or rejected the fetch request from Obsidian",
|
||||
WARNING_PASTING_ELEMENT_AS_TEXT: "PASTING EXCALIDRAW ELEMENTS AS A TEXT ELEMENT IS NOT ALLOWED",
|
||||
USE_INSERT_FILE_MODAL: "Use 'Insert Any File' to embed a markdown note",
|
||||
RECURSIVE_INSERT_ERROR: "You may not recursively insert part of an image into the same image as it would create an infinite loop",
|
||||
CONVERT_TO_MARKDOWN: "Convert to file...",
|
||||
SELECT_TEXTELEMENT_ONLY: "Select text element only (not container)",
|
||||
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",
|
||||
@@ -129,22 +191,35 @@ export default {
|
||||
CROP_PREFIX_DESC:
|
||||
"The first part of the filename for new drawings created when cropping an image. " +
|
||||
"If empty the default 'cropped_' will be used.",
|
||||
ANNOTATE_PREFIX_NAME: "Annotation file prefix",
|
||||
ANNOTATE_PREFIX_DESC:
|
||||
"The first part of the filename for new drawings created when annotating an image. " +
|
||||
"If empty the default 'annotated_' will be used.",
|
||||
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_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_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:
|
||||
"Use Excalidraw folder when embedding a drawing into the active document",
|
||||
FOLDER_EMBED_DESC:
|
||||
"Define which folder to place the newly inserted drawing into " +
|
||||
"when using the command palette action: 'Create a new drawing and embed into active document'.<br>" +
|
||||
"<b><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",
|
||||
TEMPLATE_NAME: "Excalidraw template file or folder",
|
||||
TEMPLATE_DESC:
|
||||
"Full filepath to the Excalidraw template. " +
|
||||
"E.g.: If your template is in the default Excalidraw folder and its name is " +
|
||||
"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 " +
|
||||
"Template.md, the setting would be: Excalidraw/Template.md (or just Excalidraw/Template - you may omit the .md file extension). " +
|
||||
"If you are using Excalidraw in compatibility mode, then your template must be a legacy Excalidraw file as well " +
|
||||
"such as Excalidraw/Template.excalidraw.",
|
||||
"such as Excalidraw/Template.excalidraw. <br><b>Template Folder:</b> You can also set a folder as your template. " +
|
||||
"In this case you will be prompted which tempalte to use when creating a new drawing.<br>" +
|
||||
"<b>Pro Tip:</b> If you are using the Obsidian Templater plugin, you can add Templater code to your different Excalidraw " +
|
||||
"templates to automate configuration of your drawings.",
|
||||
SCRIPT_FOLDER_NAME: "Excalidraw Automate script folder (CASE SeNSitiVE!)",
|
||||
SCRIPT_FOLDER_DESC:
|
||||
"The files you place in this folder will be treated as Excalidraw Automate scripts. " +
|
||||
@@ -181,7 +256,7 @@ export default {
|
||||
"The default OpenAI API URL. This is a freetext field, so you can enter any valid OpenAI API compatible URL. " +
|
||||
"Excalidraw will use this URL when posting API requests to OpenAI. I am not doing any error handling on this field, so make sure you enter a valid URL and only change this if you know what you are doing. ",
|
||||
AI_OPENAI_DEFAULT_IMAGE_API_URL_NAME: "OpenAI Image Generation API URL",
|
||||
AI_OPENAI_DEFAULT_VISION_MODEL_PLACEHOLDER: "Enter your default AI vision model here. e.g.: gpt-4-vision-preview",
|
||||
AI_OPENAI_DEFAULT_VISION_MODEL_PLACEHOLDER: "Enter your default AI vision model here. e.g.: gpt-4o",
|
||||
SAVING_HEAD: "Saving",
|
||||
SAVING_DESC: "In the 'Saving' section of Excalidraw Settings, you can configure how your drawings are saved. This includes options for compressing Excalidraw JSON in Markdown, setting autosave intervals for both desktop and mobile, defining filename formats, and choosing whether to use the .excalidraw.md or .md file extension. ",
|
||||
COMPRESS_NAME: "Compress Excalidraw JSON in Markdown",
|
||||
@@ -195,6 +270,14 @@ export default {
|
||||
"once you switch back to Excalidraw view. " +
|
||||
"The setting only has effect 'point forward', meaning, existing drawings will not be affected by the setting " +
|
||||
"until you open them and save them.<br><b><u>Toggle ON:</u></b> Compress drawing JSON<br><b><u>Toggle OFF:</u></b> Leave drawing JSON uncompressed",
|
||||
DECOMPRESS_FOR_MD_NAME: "Decompress Excalidraw JSON in Markdown View",
|
||||
DECOMPRESS_FOR_MD_DESC:
|
||||
"By enabling this feature Excalidraw will automatically decompress the drawing JSON when you switch to Markdown view. " +
|
||||
"This will allow you to easily read and edit the JSON string. The drawing will be compressed again " +
|
||||
"once you switch back to Excalidraw view and save the drawing (CTRL+S).<br>" +
|
||||
"I recommend switching this feature off as it will result in smaller file sizes and avoiding unnecessary results in Obsidian search. " +
|
||||
"You can always use the 'Excalidraw: Decompress current Excalidraw file' command from the command palette "+
|
||||
"to manually decompress the drawing JSON when you need to read or edit it.",
|
||||
AUTOSAVE_INTERVAL_DESKTOP_NAME: "Interval for autosave on Desktop",
|
||||
AUTOSAVE_INTERVAL_DESKTOP_DESC:
|
||||
"The time interval between saves. Autosave will skip if there are no changes in the drawing. " +
|
||||
@@ -265,6 +348,33 @@ FILENAME_HEAD: "Filename",
|
||||
DEFAULT_PEN_MODE_NAME: "Pen mode",
|
||||
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",
|
||||
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>"+
|
||||
"The effect depends on the device. Crosshair is typically visible on drawing tablets, MS Surface, but not on iOS.",
|
||||
SHOW_DRAWING_OR_MD_IN_HOVER_PREVIEW_NAME: "Render Excalidraw file as an image in hover preview...",
|
||||
SHOW_DRAWING_OR_MD_IN_HOVER_PREVIEW_DESC:
|
||||
"...even if the file has the <b>excalidraw-open-md: true</b> frontmatter key.<br>" +
|
||||
"When this setting is off and the file is set to open in md by default, the hover preview will show the " +
|
||||
"markdown side of the document.",
|
||||
SHOW_DRAWING_OR_MD_IN_READING_MODE_NAME: "Render as image when in markdown reading mode of an Excalidraw file",
|
||||
SHOW_DRAWING_OR_MD_IN_READING_MODE_DESC:
|
||||
"When you are in markdown reading mode (aka. reading the back side of the drawing) should the Excalidraw drawing be rendered as an image? " +
|
||||
"This setting will not affect the display of the drawing when you are in Excalidraw mode or when you embed the drawing into a markdown document or when rendering hover preview.<br><ul>" +
|
||||
"<li>See other related setting for <a href='#"+TAG_PDFEXPORT+"'>PDF Export</a> under 'Embedding and Exporting' further below.</li></ul><br>" +
|
||||
"You must close the active excalidraw/markdown file and reopen it for this change to take effect.",
|
||||
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_NAME: "Render the file as an image when exporting an Excalidraw file to PDF",
|
||||
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_DESC:
|
||||
"This setting controls the behavior of Excalidraw when exporting an Excalidraw file to PDF in markdown view mode using Obsidian's <b>Export to PDF</b> feature.<br>" +
|
||||
"<ul><li>When <b>enabled</b> the PDF will show the Excalidraw drawing only;</li>" +
|
||||
"<li>When <b>disabled</b> the PDF will show the markdown side of the document.</li></ul>" +
|
||||
"See the other related setting for <a href='#"+TAG_MDREADINGMODE+"'>Markdown Reading Mode</a> under 'Appearnace and Behavior' further above.<br>" +
|
||||
"⚠️ Note, you must close the active excalidraw/markdown file and reopen for this change to take effect. ⚠️",
|
||||
HOTKEY_OVERRIDE_HEAD: "Hotkey overrides",
|
||||
HOTKEY_OVERRIDE_DESC: `Some of the Excalidraw hotkeys such as <code>${labelCTRL()}+Enter</code> to edit text or <code>${labelCTRL()}+K</code> to create an element link ` +
|
||||
"conflict with Obsidian hotkey settings. The hotkey combinations you add below will override Obsidian's hotkey settings while useing Excalidraw, thus " +
|
||||
`you can add <code>${labelCTRL()}+G</code> if you want to default to Group Object in Excalidraw instead of opening Graph View.`,
|
||||
THEME_HEAD: "Theme and styling",
|
||||
ZOOM_HEAD: "Zoom",
|
||||
DEFAULT_PINCHZOOM_NAME: "Allow pinch zoom in pen mode",
|
||||
@@ -306,6 +416,18 @@ FILENAME_HEAD: "Filename",
|
||||
"These settings are different for Apple and non-Apple. If you use Obsidian on multiple platforms, you'll need to make the settings separately. "+
|
||||
"The toggles follow the order of " +
|
||||
(DEVICE.isIOS || DEVICE.isMacOS ? "SHIFT, CMD, OPT, CONTROL." : "SHIFT, CTRL, ALT, META (Windows key)."),
|
||||
LONG_PRESS_DESKTOP_NAME: "Long press to open desktop",
|
||||
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. ",
|
||||
|
||||
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. " +
|
||||
"Enabling this setting overrides 'Reuse Adjacent Pane' when the file is already open.",
|
||||
SECOND_ORDER_LINKS_NAME: "Show second-order links",
|
||||
SECOND_ORDER_LINKS_DESC: "Show links when clicking on a link in Excalidraw. Second-order link are backlinks pointing to the link being clicked. " +
|
||||
"When using image icons to connect similar notes, second order links allow you to get to related notes in one click instead of two. " +
|
||||
"See <a href='https://youtube.com/shorts/O_1ls9c6wBY?feature=share'>YT Short</a> to understand.",
|
||||
ADJACENT_PANE_NAME: "Reuse adjacent pane",
|
||||
ADJACENT_PANE_DESC:
|
||||
`When ${labelCTRL()}+${labelALT()} clicking a link in Excalidraw, by default the plugin will open the link in a new pane. ` +
|
||||
@@ -349,7 +471,7 @@ FILENAME_HEAD: "Filename",
|
||||
`${labelCTRL()}+CLICK on text with [[links]] or [](links) to open them`,
|
||||
LINK_CTRL_CLICK_DESC:
|
||||
"You can turn this feature off if it interferes with default Excalidraw features you want to use. If " +
|
||||
"this is turned off, only the link button in the title bar of the drawing pane will open links.",
|
||||
`this is turned off, you can either use ${labelCTRL()} + ${labelMETA()} or the link indicator in the top right of the element to open links.`,
|
||||
TRANSCLUSION_WRAP_NAME: "Overflow wrap behavior of transcluded text",
|
||||
TRANSCLUSION_WRAP_DESC:
|
||||
"Number specifies the character count where the text should be wrapped. " +
|
||||
@@ -378,7 +500,11 @@ FILENAME_HEAD: "Filename",
|
||||
EMBED_TOEXCALIDRAW_DESC: "In the Embed Files section of Excalidraw Settings, you can configure how various files are embedded into Excalidraw. This includes options for embedding interactive markdown files, PDFs, and markdown files as images.",
|
||||
MD_HEAD: "Embed markdown into Excalidraw as image",
|
||||
MD_EMBED_CUSTOMDATA_HEAD_NAME: "Interactive Markdown Files",
|
||||
MD_EMBED_CUSTOMDATA_HEAD_DESC: `These settings will only effect future embeds. Current embeds remain unchanged. The theme setting of embedded frames is under the "Excalidraw appearance and behavior" section.`,
|
||||
MD_EMBED_CUSTOMDATA_HEAD_DESC: `The below settings will only effect future embeds. Current embeds remain unchanged. The theme setting of embedded frames is under the "Excalidraw appearance and behavior" section.`,
|
||||
MD_EMBED_SINGLECLICK_EDIT_NAME: "Single click to edit embedded markdown",
|
||||
MD_EMBED_SINGLECLICK_EDIT_DESC:
|
||||
"Single click on an embedded markdown file to edit it. " +
|
||||
"When turned off, the markdown file will first open in preview mode, then switch to edit mode when you click on it again.",
|
||||
MD_TRANSCLUDE_WIDTH_NAME: "Default width of a transcluded markdown document",
|
||||
MD_TRANSCLUDE_WIDTH_DESC:
|
||||
"The width of the markdown page. This affects the word wrapping when transcluding longer paragraphs, and the width of " +
|
||||
@@ -429,13 +555,17 @@ FILENAME_HEAD: "Filename",
|
||||
EMBED_IMAGE_CACHE_NAME: "Cache images for embedding in markdown",
|
||||
EMBED_IMAGE_CACHE_DESC: "Cache images for embedding in markdown. This will speed up the embedding process, but in case you compose images of several sub-component drawings, " +
|
||||
"the embedded image in Markdown won't update until you open the drawing and save it to trigger an update of the cache.",
|
||||
SCENE_IMAGE_CACHE_NAME: "Cache nested Excalidraws in Scene",
|
||||
SCENE_IMAGE_CACHE_DESC: "Cache nested Excalidraws in the Scene for faster scene rendering. This will speed up the rendering process, especially if you have deeply nested Excalidraws in your scene. " +
|
||||
"Excalidraw will try to intelligently identify if any children of a nested Excalidraw have changed and will update the cache accordingly. " +
|
||||
"You may want to turn this off, in case you are suspecting that the cache is not updating properly.",
|
||||
EMBED_IMAGE_CACHE_CLEAR: "Purge Cache",
|
||||
BACKUP_CACHE_CLEAR: "Purge Backups",
|
||||
BACKUP_CACHE_CLEAR_CONFIRMATION: "This action will delete all Excalidraw drawing backups. Backups are used as a safety measure in case your drawing file gets damaged. Each time you open Obsidian the plugin automatically deletes backups for files that no longer exist in your Vault. Are you sure you want to clear all backups?",
|
||||
EMBED_REUSE_EXPORTED_IMAGE_NAME:
|
||||
"If found, use the already exported image for preview",
|
||||
EMBED_REUSE_EXPORTED_IMAGE_DESC:
|
||||
"This setting works in conjunction with the Auto-export SVG/PNG setting. If an exported image that matches the file name of the drawing " +
|
||||
"This setting works in conjunction with the <a href='#"+TAG_AUTOEXPORT+"'>Auto-export SVG/PNG</a> setting. If an exported image that matches the file name of the drawing " +
|
||||
"is available, use that image instead of generating a preview image on the fly. This will result in faster previews especially when you have many embedded objects in the drawing, however, " +
|
||||
"it may happen that your latest changes are not displayed and that the image will not automatically match your Obsidian theme in case you have changed the " +
|
||||
"Obsidian theme since the export was created. This setting only applies to embedding images into markdown documents. " +
|
||||
@@ -458,10 +588,15 @@ FILENAME_HEAD: "Filename",
|
||||
"The default width of an embedded drawing. This applies to live preview edit and reading mode, as well as to hover previews. You can specify a custom " +
|
||||
"width when embedding an image using the <code>![[drawing.excalidraw|100]]</code> or " +
|
||||
"<code>[[drawing.excalidraw|100x100]]</code> format.",
|
||||
EMBED_HEIGHT_NAME: "Default height of embedded (transcluded) image",
|
||||
EMBED_HEIGHT_DESC:
|
||||
"The default height of an embedded drawing. This applies to live preview edit and reading mode, as well as to hover previews. You can specify a custom " +
|
||||
"height when embedding an image using the <code>![[drawing.excalidraw|100]]</code> or " +
|
||||
"<code>[[drawing.excalidraw|100x100]]</code> format.",
|
||||
EMBED_TYPE_NAME: "Type of file to insert into the document",
|
||||
EMBED_TYPE_DESC:
|
||||
"When you embed an image into a document using the command palette this setting will specify if Excalidraw should embed the original Excalidraw file " +
|
||||
"or a PNG or an SVG copy. You need to enable auto-export PNG / SVG (see below under Export Settings) for those image types to be available in the dropdown. For drawings that do not have a " +
|
||||
"or a PNG or an SVG copy. You need to enable <a href='#"+TAG_AUTOEXPORT+"'>auto-export PNG / SVG</a> (see below under Export Settings) for those image types to be available in the dropdown. For drawings that do not have a " +
|
||||
"a corresponding PNG or SVG readily available the command palette action will insert a broken link. You need to open the original drawing and initiate export manually. " +
|
||||
"This option will not autogenerate PNG/SVG files, but will simply reference the already existing files.",
|
||||
EMBED_MARKDOWN_COMMENT_NAME: "Embed link to drawing as comment",
|
||||
@@ -479,13 +614,17 @@ FILENAME_HEAD: "Filename",
|
||||
"If turned off, the exported image will be transparent.",
|
||||
EXPORT_PADDING_NAME: "Image Padding",
|
||||
EXPORT_PADDING_DESC:
|
||||
"The padding (in pixels) around the exported SVG or PNG image. " +
|
||||
"The padding (in pixels) around the exported SVG or PNG image. Padding is set to 0 for clippedFrame references." +
|
||||
"If you have curved lines close to the edge of the image they might get cropped during image export. You can increase this value to avoid cropping. " +
|
||||
"You can also override this setting at a file level by adding the <code>excalidraw-export-padding: 5<code> frontmatter key.",
|
||||
EXPORT_THEME_NAME: "Export image with theme",
|
||||
EXPORT_THEME_DESC:
|
||||
"Export the image matching the dark/light theme of your drawing. If turned off, " +
|
||||
"drawings created in dark mode will appear as they would in light mode.",
|
||||
EXPORT_EMBED_SCENE_NAME: "Embed scene in exported image",
|
||||
EXPORT_EMBED_SCENE_DESC:
|
||||
"Embed Excalidraw scene in exported image. Can be overridden at a file level by adding the <code>excalidraw-export-embed-scene: true/false<code> frontmatter key. " +
|
||||
"The setting only takes effect the next time you (re)open drawings.",
|
||||
EXPORT_HEAD: "Auto-export Settings",
|
||||
EXPORT_SYNC_NAME:
|
||||
"Keep the .SVG and/or .PNG filenames in sync with the drawing file",
|
||||
@@ -507,6 +646,18 @@ FILENAME_HEAD: "Filename",
|
||||
"Double files will be exported both if auto-export SVG or PNG (or both) are enabled, as well as when clicking export on a single image.",
|
||||
COMPATIBILITY_HEAD: "Compatibility features",
|
||||
COMPATIBILITY_DESC: "You should only enable these features if you have a strong reason for wanting to work with excalidraw.com files instead of markdown files. Many of the plugin features are not supported on legacy files. Typical usecase would be if you use set your vault up on top of a Visual Studio Code project folder and you have .excalidraw drawings you want to access from Visual Studio Code as well. Another usecase might be using Excalidraw in Logseq and Obsidian in parallel.",
|
||||
DUMMY_TEXT_ELEMENT_LINT_SUPPORT_NAME: "Linter compatibility",
|
||||
DUMMY_TEXT_ELEMENT_LINT_SUPPORT_DESC: "Excalidraw is sensitive to the file structure below <code># Excalidraw Data</code>. Automatic linting of documents can create errors in Excalidraw Data. " +
|
||||
"While I've made some effort to make the data loading resilient to " +
|
||||
"lint changes, this solution is not foolproof.<br><mark>The best is to avoid liniting or otherwise automatically changing Excalidraw documents using different plugins.</mark><br>" +
|
||||
"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 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. " +
|
||||
"If you are experiencing problems with the plugin, please enable this setting, reproduce the issue, and include the console log in the issue you raise on <a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/issues'>GitHub</a>",
|
||||
SLIDING_PANES_NAME: "Sliding panes plugin support",
|
||||
SLIDING_PANES_DESC:
|
||||
"Need to restart Obsidian for this change to take effect.<br>" +
|
||||
@@ -535,7 +686,7 @@ FILENAME_HEAD: "Filename",
|
||||
LATEX_DEFAULT_DESC: "Leave empty if you don't want a default formula. You can add default formatting here such as <code>\\color{white}</code>.",
|
||||
NONSTANDARD_HEAD: "Non-Excalidraw.com supported features",
|
||||
NONSTANDARD_DESC: `These settings in the "Non-Excalidraw.com Supported Features" section provide customization options beyond the default Excalidraw.com features. These features are not available on excalidraw.com. When exporting the drawing to Excalidraw.com these features will appear different.
|
||||
You can configure the number of custom pens displayed next to the Obsidian Menu on the canvas, allowing you to choose from a range of options. Additionally, you can enable a fourth font option, which adds a fourth font button to the properties panel for text elements. `,
|
||||
You can configure the number of custom pens displayed next to the Obsidian Menu on the canvas, allowing you to choose from a range of options. Additionally, you can enable a local font option, which adds a local font to the list of fonts on the element properties panel for text elements. `,
|
||||
RENDER_TWEAK_HEAD: "Rendering tweaks",
|
||||
MAX_IMAGE_ZOOM_IN_NAME: "Maximum image zoom in resolution",
|
||||
MAX_IMAGE_ZOOM_IN_DESC: "To save on memory and because Apple Safari (Obsidian on iOS) has some hard-coded limitations, Excalidraw.com limits the max resolution of images and large objects when zooming in. You can override this limitation using a multiplicator. " +
|
||||
@@ -575,17 +726,29 @@ FILENAME_HEAD: "Filename",
|
||||
"Turn this on to support image embedding styles such as ![[drawing|width|style]] in live preview editing mode. " +
|
||||
"The setting will not affect the currently open documents. You need close the open documents and re-open them for the change " +
|
||||
"to take effect.",
|
||||
CUSTOM_FONT_HEAD: "Fourth font",
|
||||
ENABLE_FOURTH_FONT_NAME: "Enable fourth font option",
|
||||
FADE_OUT_EXCALIDRAW_MARKUP_NAME: "Fade out Excalidraw markup",
|
||||
FADE_OUT_EXCALIDRAW_MARKUP_DESC: "In Markdown view mode, the section after the markdown comment %% " +
|
||||
"fades out. The text is still there, but the visual clutter is reduced. Note, you can place the %% in the line right above # Text Elements, " +
|
||||
"in this case the entire drawing markdown will fade out including # Text Elements. The side effect is you won't be able to block reference text in other markdown notes, that is after the %% comment section. This is seldom an issue. " +
|
||||
"Should you want to edit the Excalidraw markdown script, simply switch to markdown view mode and temporarily remove the %% comment.",
|
||||
EXCALIDRAW_PROPERTIES_NAME: "Load Excalidraw Properties into Obsidian Suggester",
|
||||
EXCALIDRAW_PROPERTIES_DESC: "Toggle this setting to load Excalidraw document properties into Obsidian's property suggester at plugin startup. "+
|
||||
"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.",
|
||||
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 fourth font button on the properties panel for text elements. " +
|
||||
"Files that use this fourth 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.",
|
||||
FOURTH_FONT_NAME: "Fourth font file",
|
||||
"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 fourth 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.",
|
||||
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",
|
||||
@@ -601,6 +764,12 @@ FILENAME_HEAD: "Filename",
|
||||
"the developer of Taskbone (as you can imagine, there is no such thing as 'free', providing this awesome OCR service costs some money to the developer of Taskbone), you can " +
|
||||
"purchase a paid API key from <a href='https://www.taskbone.com/' target='_blank'>taskbone.com</a>. In case you have purchased a key, simply overwrite this auto generated free-tier API-key with your paid key.",
|
||||
|
||||
//HotkeyEditor
|
||||
HOTKEY_PRESS_COMBO_NANE: "Press your hotkey combination",
|
||||
HOTKEY_PRESS_COMBO_DESC: "Please press the desired key combination",
|
||||
HOTKEY_BUTTON_ADD_OVERRIDE: "Add New Override",
|
||||
HOTKEY_BUTTON_REMOVE: "Remove",
|
||||
|
||||
//openDrawings.ts
|
||||
SELECT_FILE: "Select a file then press enter.",
|
||||
SELECT_COMMAND: "Select a command then press enter.",
|
||||
@@ -619,6 +788,13 @@ FILENAME_HEAD: "Filename",
|
||||
PDF_PAGES_HEADER: "Pages to load?",
|
||||
PDF_PAGES_DESC: "Format: 1, 3-5, 7, 9-11",
|
||||
|
||||
//SelectCard.ts
|
||||
TYPE_SECTION: "Type section name to select.",
|
||||
SELECT_SECTION_OR_TYPE_NEW:
|
||||
"Select existing section or type name of a new section then press Enter.",
|
||||
INVALID_SECTION_NAME: "Invalid section name.",
|
||||
EMPTY_SECTION_MESSAGE: "Type the Section Name and hit enter to create a new Section",
|
||||
|
||||
//EmbeddedFileLoader.ts
|
||||
INFINITE_LOOP_WARNING:
|
||||
"EXCALIDRAW WARNING\nAborted loading embedded images due to infinite loop in file:\n",
|
||||
@@ -638,7 +814,7 @@ FILENAME_HEAD: "Filename",
|
||||
TOGGLE_FRAME_RENDERING: "Toggle frame rendering",
|
||||
TOGGLE_FRAME_CLIPPING: "Toggle frame clipping",
|
||||
OPEN_LINK_CLICK: "Open Link",
|
||||
OPEN_LINK_PROPS: "Open markdown-embed properties or open link in new window",
|
||||
OPEN_LINK_PROPS: "Open the image-link or LaTeX-formula editor",
|
||||
|
||||
//IFrameActionsMenu.tsx
|
||||
NARROW_TO_HEADING: "Narrow to heading...",
|
||||
@@ -691,6 +867,7 @@ FILENAME_HEAD: "Filename",
|
||||
PROMPT_BUTTON_INSERT_SPACE: "Insert space",
|
||||
PROMPT_BUTTON_INSERT_LINK: "Insert markdown link to file",
|
||||
PROMPT_BUTTON_UPPERCASE: "Uppercase",
|
||||
PROMPT_SELECT_TEMPLATE: "Select a template",
|
||||
|
||||
//ModifierKeySettings
|
||||
WEB_BROWSER_DRAG_ACTION: "Web Browser Drag Action",
|
||||
@@ -698,4 +875,23 @@ FILENAME_HEAD: "Filename",
|
||||
INTERNAL_DRAG_ACTION: "Obsidian Internal Drag Action",
|
||||
PANE_TARGET: "Link click behavior",
|
||||
DEFAULT_ACTION_DESC: "In case none of the combinations apply the default action for this group is: ",
|
||||
|
||||
//FrameSettings.ts
|
||||
FRAME_SETTINGS_TITLE: "Frame Settings",
|
||||
FRAME_SETTINGS_ENABLE: "Enable Frames",
|
||||
FRAME_SETTIGNS_NAME: "Display Frame Name",
|
||||
FRAME_SETTINGS_OUTLINE: "Display Frame Outline",
|
||||
FRAME_SETTINGS_CLIP: "Enable Frame Clipping",
|
||||
|
||||
//InsertPDFModal.ts
|
||||
IPM_PAGES_TO_IMPORT_NAME: "Pages to import",
|
||||
IPM_SELECT_PAGES_TO_IMPORT: "Please select pages to import",
|
||||
IPM_ADD_BORDER_BOX_NAME: "Add border box",
|
||||
IPM_ADD_FRAME_NAME: "Add page to frame",
|
||||
IPM_ADD_FRAME_DESC: "For easier handling I recommend to lock the page inside the frame. " +
|
||||
"If, however, you do lock the page inside the frame then the only way to unlock it is to right-click the frame, select remove elements from frame, then unlock the page.",
|
||||
IPM_GROUP_PAGES_NAME: "Group pages",
|
||||
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",
|
||||
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ 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";
|
||||
|
||||
// 简体中文
|
||||
@@ -9,7 +10,8 @@ export default {
|
||||
// main.ts
|
||||
CONVERT_URL_TO_FILE: "从 URL 下载图像到本地",
|
||||
UNZIP_CURRENT_FILE: "解压当前 Excalidraw 文件",
|
||||
PUBLISH_SVG_CHECK: "Obsidian Publish: 搜索过期的 SVG 和 PNG 导出文件",
|
||||
ZIP_CURRENT_FILE: "压缩当前 Excalidraw 文件",
|
||||
PUBLISH_SVG_CHECK: "Obsidian Publish:搜索过期的 SVG 和 PNG 导出文件",
|
||||
EMBEDDABLE_PROPERTIES: "Embeddable 元素设置",
|
||||
EMBEDDABLE_RELATIVE_ZOOM: "使元素的缩放等级等于当前画布的缩放等级",
|
||||
OPEN_IMAGE_SOURCE: "打开 Excalidraw 绘图文件",
|
||||
@@ -35,6 +37,8 @@ export default {
|
||||
TRANSCLUDE: "嵌入绘图(形如 ![[drawing]])到当前 Markdown 文档中",
|
||||
TRANSCLUDE_MOST_RECENT: "嵌入最近编辑过的绘图(形如 ![[drawing]])到当前 Markdown 文档中",
|
||||
TOGGLE_LEFTHANDED_MODE: "切换为左手模式",
|
||||
TOGGLE_SPLASHSCREEN: "在新绘图中显示启动画面",
|
||||
FLIP_IMAGE: "打开当前所选 excalidraw 图像的“背景笔记”",
|
||||
NEW_IN_NEW_PANE: "新建绘图 - 于新面板",
|
||||
NEW_IN_NEW_TAB: "新建绘图 - 于新页签",
|
||||
NEW_IN_ACTIVE_PANE: "新建绘图 - 于当前面板",
|
||||
@@ -48,14 +52,18 @@ export default {
|
||||
NEW_IN_POPOUT_WINDOW_EMBED: "新建绘图 - 于新窗口 - 并将其嵌入(形如 ![[drawing]])到当前 Markdown 文档中",
|
||||
TOGGLE_LOCK: "文本元素:原文模式(RAW)⟺ 预览模式(PREVIEW)",
|
||||
DELETE_FILE: "从库中删除所选图像(或以图像形式嵌入绘图中的 Markdown)的源文件",
|
||||
COPY_ELEMENT_LINK: "复制所选元素的链接(形如 [[file#^id]]])",
|
||||
COPY_DRAWING_LINK: "复制绘图的嵌入链接(形如 ![[darwing]])",
|
||||
INSERT_LINK_TO_ELEMENT:
|
||||
`复制所选元素为内部链接(形如 [[file#^id]] )。\n按住 ${labelCTRL()} 可复制元素所在分组为内部链接(形如 [[file#^group=id]] )。\n按住 ${labelSHIFT()} 可复制所选元素所在区域为内部链接(形如 [[file#^area=id]] )。\n按住 ${labelALT()} 可观看视频演示。`,
|
||||
`复制所选元素为内部链接(形如 [[file#^id]] )。\n按住 ${labelCTRL()} 可复制元素所在分组为内部链接(形如 [[file#^group=id]] )。\n按住 ${labelSHIFT()} 可复制所选元素所在区域为内部链接(形如 [[file#^area=id]] )。`,
|
||||
INSERT_LINK_TO_ELEMENT_GROUP:
|
||||
"复制所选元素所在分组为内部链接(形如 [[file#^group=id]] )",
|
||||
"复制所选元素所在分组为嵌入链接(形如 ![[file#^group=id]] )",
|
||||
INSERT_LINK_TO_ELEMENT_AREA:
|
||||
"复制所选元素所在区域为内部链接(形如 [[file#^area=id]] )",
|
||||
"复制所选元素所在区域为嵌入链接(形如 ![[file#^area=id]] )",
|
||||
INSERT_LINK_TO_ELEMENT_FRAME:
|
||||
"复制所选框架为内部链接(形如 [[file#^frame=id]] )",
|
||||
"复制所选框架为嵌入链接(形如 ![[file#^frame=id]] )",
|
||||
INSERT_LINK_TO_ELEMENT_FRAME_CLIPPED:
|
||||
"复制所选框架(内容)为嵌入链接(形如 ![[file#^clippedframe=id]] )",
|
||||
INSERT_LINK_TO_ELEMENT_NORMAL:
|
||||
"复制所选元素为内部链接(形如 [[file#^id]] )",
|
||||
INSERT_LINK_TO_ELEMENT_ERROR: "未选择画布里的单个元素",
|
||||
@@ -64,27 +72,60 @@ export default {
|
||||
INSERT_COMMAND: "插入 Obsidian 命令(以内部链接形式嵌入)到当前绘图中",
|
||||
INSERT_IMAGE: "插入图像或 Excalidraw 绘图(以图像形式嵌入)到当前绘图中",
|
||||
IMPORT_SVG: "从 SVG 文件导入图形元素到当前绘图中(暂不支持文本元素)",
|
||||
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 公式到当前绘图`,
|
||||
`插入 LaTeX 公式(例如:\\binom{n}{k} = \\frac{n!}{k!(n-k)!})。`,
|
||||
ENTER_LATEX: "输入 LaTeX 表达式",
|
||||
READ_RELEASE_NOTES: "阅读本插件的更新说明",
|
||||
RUN_OCR: "OCR:识别涂鸦和图片里的文本并复制到剪贴板",
|
||||
RUN_OCR: "OCR 完整画布:识别涂鸦和图片里的文本并复制到剪贴板和文档属性中",
|
||||
RERUN_OCR: "重新运行 OCR 完整画笔:识别涂鸦和图片里的文本并复制到剪贴板和文档属性中",
|
||||
RUN_OCR_ELEMENTS: "OCR 选中的元素:识别涂鸦和图片里的文本并复制到剪贴板",
|
||||
TRAY_MODE: "绘图工具属性页:面板模式 ⟺ 托盘模式",
|
||||
SEARCH: "搜索文本",
|
||||
CROP_IMAGE: "裁剪与蒙版",
|
||||
RESET_IMG_TO_100: "重设图像元素的尺寸为 100%",
|
||||
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>文件名#^块引用|宽度x最大高度</mark><br>" +
|
||||
"您可以通过在链接末尾添加 <code>|100%</code> 来将 Excalidraw 图像锚定为 100% 的大小。<br>" +
|
||||
"您可以通过将 <code>#page=1</code> 更改为 <code>#page=2</code> 等来更改 PDF 页码。<br>" +
|
||||
"PDF 矩形裁剪值为:<code>左, 下, 右, 上</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: "这是一个蒙版图像。长按本提示来观看视频讲解。",
|
||||
INSTALL_SCRIPT_BUTTON: "安装或更新 Excalidraw 脚本",
|
||||
OPEN_AS_MD: "打开为 Markdown 文档",
|
||||
EXPORT_IMAGE: `导出为图像`,
|
||||
OPEN_LINK: "打开所选元素里的链接 \n(按住 SHIFT 在新面板打开)",
|
||||
OPEN_LINK: "打开所选元素里的链接 \n(按住 Shift 在新面板打开)",
|
||||
EXPORT_EXCALIDRAW: "导出为 .excalidraw 文件(旧版绘图文件格式)",
|
||||
LINK_BUTTON_CLICK_NO_TEXT:
|
||||
"请选择一个含有链接的图形或文本元素。",
|
||||
@@ -103,10 +144,33 @@ export default {
|
||||
BACKUP_RESTORED: "已恢复备份",
|
||||
CACHE_NOT_READY: "抱歉,加载绘图文件时出错。<br><br><mark>现在有耐心,将来更省心。</mark><br><br>该插件有备份机制,但您似乎刚刚打开 Obsidian,需要等待一分钟或更长的时间来读取缓存。缓存读取完毕时,您将会在右上角收到提示。<br><br>请点击 OK 并耐心等待缓存,或者选择点击取消后手动修复你的文件。<br>",
|
||||
OBSIDIAN_TOOLS_PANEL: "Obsidian 工具面板",
|
||||
ERROR_SAVING_IMAGE: "获取图像时发生未知错误",
|
||||
ERROR_SAVING_IMAGE: "获取图像时发生未知错误。可能是由于某种原因,图像不可用或拒绝了 Obsidian 的获取请求。",
|
||||
WARNING_PASTING_ELEMENT_AS_TEXT: "你不能将 Excalidraw 元素粘贴为文本元素!",
|
||||
USE_INSERT_FILE_MODAL: "使用“插入任意文件”功能来嵌入 Markdown 文档",
|
||||
RECURSIVE_INSERT_ERROR: "你不能将图像的一部分嵌入到此图像中,因为这可能导致无限循环。",
|
||||
CONVERT_TO_MARKDOWN: "转存为 Markdown 文档(并嵌入为 MD-Embeddable)",
|
||||
SELECT_TEXTELEMENT_ONLY: "只选择文本元素(非容器)",
|
||||
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: "显示更新说明",
|
||||
@@ -123,6 +187,23 @@ export default {
|
||||
FOLDER_NAME: "Excalidraw 文件夹",
|
||||
FOLDER_DESC:
|
||||
"新绘图的默认存储路径。若为空,将在库的根目录中创建新绘图。",
|
||||
CROP_PREFIX_NAME: "剪贴文件的前缀",
|
||||
CROP_PREFIX_DESC:
|
||||
"当剪贴图片进来时保存的文件名的前缀。 " +
|
||||
"留空则使用 'cropped_'",
|
||||
ANNOTATE_PREFIX_NAME: "标注文件的前缀",
|
||||
ANNOTATE_PREFIX_DESC:
|
||||
"在标注图像时创建新绘图的文件名的第一部分。" +
|
||||
"留空则使用'annotated_'",
|
||||
ANNOTATE_PRESERVE_SIZE_NAME: "在标注时保留图像尺寸",
|
||||
ANNOTATE_PRESERVE_SIZE_DESC:
|
||||
"当在 Markdown 中标注图像时,替换后的图像链接将包含原始图像的宽度。",
|
||||
CROP_FOLDER_NAME: "剪贴文件文件夹",
|
||||
CROP_FOLDER_DESC:
|
||||
"剪贴图像时创建新绘图的默认存储路径。如果留空,将按照 Vault 附件设置创建。",
|
||||
ANNOTATE_FOLDER_NAME: "图片标注文件文件夹",
|
||||
ANNOTATE_FOLDER_DESC:
|
||||
"创建图片标注是的默认存储路径。如果留空,将按照 Vault 附件设置创建。",
|
||||
FOLDER_EMBED_NAME:
|
||||
"将 Excalidraw 文件夹用于“新建绘图”系列命令",
|
||||
FOLDER_EMBED_DESC:
|
||||
@@ -131,11 +212,14 @@ export default {
|
||||
"<b>开启:</b>使用上面的 Excalidraw 文件夹。 <br><b>关闭:</b>使用 Obsidian 设置的新附件默认位置。",
|
||||
TEMPLATE_NAME: "Excalidraw 模板文件",
|
||||
TEMPLATE_DESC:
|
||||
"Excalidraw 模板文件的存储路径。<br>" +
|
||||
"如果您的模板在默认的 Excalidraw 文件夹中且文件名是 " +
|
||||
"Template.md,则此项应设为 Excalidraw/Template.md(也可省略 .md 扩展名,即 Excalidraw/Template)。<br>" +
|
||||
"Excalidraw 模板文件(文件夹)的存储路径。<br>" +
|
||||
"<b>模板文件:</b>比如:如果您的模板在默认的 Excalidraw 文件夹中且文件名是 " +
|
||||
"Template.md,则此项应设为 Excalidraw/Template.md(也可省略 .md 扩展名,即 Excalidraw/Template)。" +
|
||||
"如果您在兼容模式下使用 Excalidraw,那么您的模板文件也必须是旧的 *.excalidraw 格式," +
|
||||
"例如 Excalidraw/Template.excalidraw。",
|
||||
"例如 Excalidraw/Template.excalidraw。<br><b>模板文件夹:</b> 你还可以将文件夹设置为模板。 " +
|
||||
"在这种情况下,创建新绘图时将提示您选择使用哪个模板。<br>" +
|
||||
"<b>专业提示:</b> 如果您正在使用 Obsidian Templater 插件,您可以将 Templater 代码添加到不同的" +
|
||||
"Excalidraw 模板中,以自动配置您的绘图",
|
||||
SCRIPT_FOLDER_NAME: "Excalidraw 自动化脚本的文件夹(大小写敏感!)",
|
||||
SCRIPT_FOLDER_DESC:
|
||||
"此文件夹用于存放 Excalidraw 自动化脚本。" +
|
||||
@@ -171,8 +255,8 @@ export default {
|
||||
AI_OPENAI_DEFAULT_API_URL_DESC:
|
||||
"默认的 OpenAI API URL。请填写有效的 OpenAI API URL。" +
|
||||
"Excalidraw 会通过该 URL 发送 API 请求给 OpenAI。我没有对此选项做任何错误处理,请谨慎修改。",
|
||||
AI_OPENAI_DEFAULT_IMAGE_API_URL_NAME: "OpenAI Image Generation API URL",
|
||||
AI_OPENAI_DEFAULT_VISION_MODEL_PLACEHOLDER: "gpt-4-vision-preview",
|
||||
AI_OPENAI_DEFAULT_IMAGE_API_URL_NAME: "OpenAI 图像生成 API URL",
|
||||
AI_OPENAI_DEFAULT_VISION_MODEL_PLACEHOLDER: "输入你的默认 AI 模型名称,例如:gpt-4o",
|
||||
SAVING_HEAD: "保存",
|
||||
SAVING_DESC: "包括:压缩,自动保存的时间间隔,文件的命名格式和扩展名等的设置。",
|
||||
COMPRESS_NAME: "压缩 Excalidraw JSON",
|
||||
@@ -186,6 +270,14 @@ export default {
|
||||
"而当您切换回 Excalidraw 模式时,数据就会被再次编码。<br>" +
|
||||
"开启此项后,对于之前已存在但未压缩的绘图文件," +
|
||||
"需要重新打开并保存才能生效。",
|
||||
DECOMPRESS_FOR_MD_NAME: "在 Markdown 视图中解压缩 Excalidraw JSON",
|
||||
DECOMPRESS_FOR_MD_DESC:
|
||||
"通过启用此功能,Excalidraw 将在切换到 Markdown 视图时自动解压缩绘图 JSON。" +
|
||||
"这将使您能够轻松阅读和编辑 JSON 字符串。" +
|
||||
"一旦您切换回 Excalidraw 视图并保存绘图(Ctrl+S),绘图将再次被压缩。<br>" +
|
||||
"我建议关闭此功能,因为这可以获得更小的文件尺寸,并避免在 Obsidian 搜索中出现不必要的结果。 " +
|
||||
"您始终可以使用命令面板中的“Excalidraw: 解压缩当前 Excalidraw 文件”命令"+
|
||||
"在需要阅读或编辑时手动解压缩绘图 JSON。",
|
||||
AUTOSAVE_INTERVAL_DESKTOP_NAME: "桌面端自动保存时间间隔",
|
||||
AUTOSAVE_INTERVAL_DESKTOP_DESC:
|
||||
"每隔多长时间自动保存一次(如果绘图文件没有发生改变,将不会保存)。" +
|
||||
@@ -235,7 +327,7 @@ FILENAME_HEAD: "文件名",
|
||||
IFRAME_MATCH_THEME_DESC:
|
||||
"<b>开启:</b>当 Obsidian 和 Excalidraw 一个使用黑暗主题、一个使用明亮主题时," +
|
||||
"开启此项后,以交互形式嵌入到绘图中的元素(Embeddable) 将会匹配 Excalidraw 主题。<br>" +
|
||||
"<b>关闭:</b>如果您想要 Embeddable 匹配 Obsidian 主题,请关闭此项。",
|
||||
"<b>关闭:</b>如果您想要 Embeddable 匹配 Obsidian 主题,请关闭此项。",
|
||||
MATCH_THEME_NAME: "使新建的绘图匹配 Obsidian 主题",
|
||||
MATCH_THEME_DESC:
|
||||
"如果 Obsidian 使用黑暗主题,新建的绘图文件也将使用黑暗主题。<br>" +
|
||||
@@ -256,12 +348,39 @@ FILENAME_HEAD: "文件名",
|
||||
DEFAULT_PEN_MODE_NAME: "触控笔模式(Pen mode)",
|
||||
DEFAULT_PEN_MODE_DESC:
|
||||
"打开绘图时,是否自动开启触控笔模式?",
|
||||
DISABLE_DOUBLE_TAP_ERASER_NAME: "启用手写模式下的双击橡皮擦功能",
|
||||
SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_NAME: "在触控笔模式下显示十字准星(+)",
|
||||
SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_DESC:
|
||||
"在触控笔模式下使用涂鸦功能会显示十字准星 <b><u>打开:</u></b> 显示 <b><u>关闭:</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 格式打开时,悬停预览将显示文档的 Markdown 部分(背景笔记)。" +
|
||||
"",
|
||||
SHOW_DRAWING_OR_MD_IN_READING_MODE_NAME: "Excalidraw 文件在 Markdown 阅读模式下渲染为图片",
|
||||
SHOW_DRAWING_OR_MD_IN_READING_MODE_DESC:
|
||||
"当您处于 Markdown 阅读模式(即查看绘图的背景笔记)时,Excalidraw 绘图是否应该渲染为图像?" +
|
||||
"此设置不会影响您在 Excalidraw 模式下的绘图显示,或者在将绘图嵌入 Markdown 文档时,或在渲染悬停预览时。<br><ul>" +
|
||||
"<li>请参阅下面‘嵌入和导出’部分的 <a href='#"+TAG_PDFEXPORT+"'>PDF 导出</a> 相关设置。</li></ul><br>" +
|
||||
"您必须关闭当前的 Excalidraw/Markdown 文件并重新打开,以使此更改生效。",
|
||||
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_NAME: "在将 Excalidraw 文件导出为 PDF 时将文件渲染为图像",
|
||||
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_DESC:
|
||||
"处于 Markdown 视图模式时,此设置控制 Excalidraw 在使用 Obsidian 的 <b>导出为 PDF</b> 功能时,将 Excalidraw 文件导出为 PDF 的行为。<br>" +
|
||||
"<ul><li>当 <b>启用</b> 时,PDF 将仅显示 Excalidraw 绘图;</li>" +
|
||||
"<li>当 <b>禁用</b> 时,PDF 将显示文档的 Markdown 部分(背景笔记)。</li></ul>" +
|
||||
"请参阅上面‘外观和行为’部分的 <<a href='#"+TAG_MDREADINGMODE+"'>>Markdown 阅读模式</a> 相关设置。" +
|
||||
"⚠️ 注意,您必须关闭当前的 Excalidraw/Markdown 文件并重新打开,以使此更改生效。⚠️",
|
||||
HOTKEY_OVERRIDE_HEAD: "热键覆盖",
|
||||
HOTKEY_OVERRIDE_DESC: `一些 Excalidraw 的热键,例如 ${labelCTRL()}+Enter 用于编辑文本,或 ${labelCTRL()}+K 用于创建元素链接。` +
|
||||
"与 Obsidian 的热键设置发生冲突。您在下面添加的热键组合将在使用 Excalidraw 时覆盖 Obsidian 的热键设置," +
|
||||
`因此如果您希望在 Excalidraw 中默认选择“组合对象”,而不是打开“图形视图”,您可以添加 ${labelCTRL()}+G。`,
|
||||
THEME_HEAD: "主题和样式",
|
||||
ZOOM_HEAD: "缩放",
|
||||
DEFAULT_PINCHZOOM_NAME: "允许在触控笔模式下进行双指缩放",
|
||||
DEFAULT_PINCHZOOM_DESC:
|
||||
"在触控笔模式下使用自由画笔工具时,双指缩放可能造成干扰。<br>" +
|
||||
"<b>开启: </b>允许在触控笔模式下进行双指缩放<br><b>关闭: </b>禁止在触控笔模式下进行双指缩放",
|
||||
"<b>开启::</b>允许在触控笔模式下进行双指缩放<br><b>关闭: </b>禁止在触控笔模式下进行双指缩放",
|
||||
|
||||
DEFAULT_WHEELZOOM_NAME: "鼠标滚轮缩放页面",
|
||||
DEFAULT_WHEELZOOM_DESC:
|
||||
@@ -287,7 +406,7 @@ FILENAME_HEAD: "文件名",
|
||||
LINKS_DESC:
|
||||
`按住 ${labelCTRL()} 并点击包含 <code>[[链接]]</code> 的文本元素可以打开其中的链接。` +
|
||||
"如果所选文本元素包含多个 <code>[[有效的内部链接]]</code> ,只会打开第一个链接;" +
|
||||
"如果所选文本元素包含有效的 URL 链接 (如 <code>https://</code> 或 <code>http://</code>)," +
|
||||
"如果所选文本元素包含有效的 URL 链接(如 <code>https://</code> 或 <code>http://</code>)," +
|
||||
"插件会在浏览器中打开链接。<br>" +
|
||||
"链接的源文件被重命名时,绘图中相应的 <code>[[内部链接]]</code> 也会同步更新。" +
|
||||
"若您不愿绘图中的链接外观因此而变化,可使用 <code>[[内部链接|别名]]</code>。",
|
||||
@@ -296,7 +415,19 @@ FILENAME_HEAD: "文件名",
|
||||
"Excalidraw 不会检查您的设置是否合理,因此请谨慎设置,避免冲突。" +
|
||||
"以下选项在苹果和非苹果设备上区别很大,如果您在多个硬件平台上使用 Obsidian,需要分别进行设置。"+
|
||||
"选项里的 4 个开关依次代表 " +
|
||||
(DEVICE.isIOS || DEVICE.isMacOS ? "SHIFT, CMD, OPT, CONTROL." : "SHIFT, CTRL, ALT, META (Win 键)。"),
|
||||
(DEVICE.isIOS || DEVICE.isMacOS ? "Shift, CMD, OPT, CONTROL." : "Shift, Ctrl, Alt, META (Win 键)。"),
|
||||
LONG_PRESS_DESKTOP_NAME: "长按打开(电脑端)",
|
||||
LONG_PRESS_DESKTOP_DESC: "长按(以毫秒为单位)打开在 Markdown 文件中嵌入的 Excalidraw 绘图。",
|
||||
LONG_PRESS_MOBILE_NAME: "长按打开(移动端)",
|
||||
LONG_PRESS_MOBILE_DESC: "长按(以毫秒为单位)打开在 Markdown 文件中嵌入的 Excalidraw 绘图。",
|
||||
|
||||
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'>这个 YouTube Shorts 视频</a> 以了解更多信息。",
|
||||
ADJACENT_PANE_NAME: "在相邻面板中打开",
|
||||
ADJACENT_PANE_DESC:
|
||||
`按住 ${labelCTRL()}+${labelSHIFT()} 并点击绘图里的内部链接时,插件默认会在新面板中打开该链接。<br>` +
|
||||
@@ -306,7 +437,7 @@ FILENAME_HEAD: "文件名",
|
||||
MAINWORKSPACE_PANE_NAME: "在主工作区中打开",
|
||||
MAINWORKSPACE_PANE_DESC:
|
||||
`按住 ${labelCTRL()}+${labelSHIFT()} 并点击绘图里的内部链接时,插件默认会在当前窗口的新面板中打开该链接。<br>` +
|
||||
"若开启此项,Excalidraw 会在主工作区的面板中打开该链接。",
|
||||
"若开启此项,Excalidraw 会在主工作区的面板中打开该链接。",
|
||||
LINK_BRACKETS_NAME: "在链接的两侧显示 <code>[[中括号]]</code>",
|
||||
LINK_BRACKETS_DESC: `${
|
||||
"文本元素处于预览(PREVIEW)模式时,在内部链接的两侧显示中括号。<br>" +
|
||||
@@ -331,7 +462,7 @@ FILENAME_HEAD: "文件名",
|
||||
HOVERPREVIEW_NAME: "鼠标悬停预览内部链接",
|
||||
HOVERPREVIEW_DESC:
|
||||
`<b>开启:</b>在 Excalidraw <u>阅读模式(View)</u>下,鼠标悬停在 <code>[[内部链接]]</code> 上即可预览;` +
|
||||
"而在<u>普通模式(Normal)</u>下, 鼠标悬停在内部链接右上角的蓝色标识上即可预览。<br> " +
|
||||
"而在<u>普通模式(Normal)</u>下,鼠标悬停在内部链接右上角的蓝色标识上即可预览。<br> " +
|
||||
`<b>关闭:</b>鼠标悬停在 <code>[[内部链接]]</code> 上,并且按住 ${labelCTRL()} 才能预览。`,
|
||||
LINKOPACITY_NAME: "链接标识的透明度",
|
||||
LINKOPACITY_DESC:
|
||||
@@ -340,7 +471,7 @@ FILENAME_HEAD: "文件名",
|
||||
`按住 ${labelCTRL()} 并点击含有 [[链接]] 或 [别名](链接) 的文本来打开链接`,
|
||||
LINK_CTRL_CLICK_DESC:
|
||||
"如果此功能影响到您使用某些原版 Excalidraw 功能,可将其关闭。" +
|
||||
"关闭后,您只能通过绘图面板标题栏中的链接按钮来打开链接。",
|
||||
"关闭后,您可以使用 ${labelCTRL()} + ${labelMETA()} 或者元素右上角的链接指示器来打开链接。",
|
||||
TRANSCLUSION_WRAP_NAME: "MD-Transclusion 的折行方式",
|
||||
TRANSCLUSION_WRAP_DESC:
|
||||
"中的 number 表示嵌入的文本溢出时,在第几个字符处进行折行。<br>" +
|
||||
@@ -369,17 +500,21 @@ FILENAME_HEAD: "文件名",
|
||||
EMBED_TOEXCALIDRAW_DESC: "包括:以图像形式嵌入到绘图中的 PDF 文档、以交互形式嵌入到绘图中的 Markdown 文档(MD-Embeddable)、以图像形式嵌入的 Markdown 文档(MD-Embed)等。",
|
||||
MD_HEAD: "以图像形式嵌入到绘图中的 Markdown 文档(MD-Embed)",
|
||||
MD_EMBED_CUSTOMDATA_HEAD_NAME: "以交互形式嵌入到绘图中的 Markdown 文档(MD-Embeddable)",
|
||||
MD_EMBED_CUSTOMDATA_HEAD_DESC: `这些选项不会影响到已存在的 MD-Embeddable。MD-Embeddable 的主题风格在“显示 & 行为”小节设置。`,
|
||||
MD_EMBED_CUSTOMDATA_HEAD_DESC: `以下设置只会影响以后的嵌入。已存在的嵌入保持不变。嵌入框的主题设置位于 “Excalidraw 外观和行为” 部分。`,
|
||||
MD_EMBED_SINGLECLICK_EDIT_NAME: "单击以编辑嵌入的 markdown。",
|
||||
MD_EMBED_SINGLECLICK_EDIT_DESC:
|
||||
"单击嵌入的 markdown 文件以进行编辑。 " +
|
||||
"当此功能关闭时,markdown 文件将首先以预览模式打开,然后在您再次单击时切换到编辑模式。",
|
||||
MD_TRANSCLUDE_WIDTH_NAME: "MD-Embed 的默认宽度",
|
||||
MD_TRANSCLUDE_WIDTH_DESC:
|
||||
"MD-Embed 的宽度。该选项会影响到折行,以及图像元素的宽度。<br>" +
|
||||
"您可为绘图中的某个 MD-Embed 单独设置此项,方法是将绘图切换至 Markdown 模式," +
|
||||
"并修改相应的 <code>[[Embed文件名#标题|宽度x最大高度]]</code>。",
|
||||
"并修改相应的 <code>[[Embed 文件名#标题|宽度x最大高度]]</code>。",
|
||||
MD_TRANSCLUDE_HEIGHT_NAME:
|
||||
"MD-Embed 的默认最大高度",
|
||||
MD_TRANSCLUDE_HEIGHT_DESC:
|
||||
"MD-Embed 的高度取决于 Markdown 文档内容的多少,但最大不会超过该值。<br>" +
|
||||
"您可为绘图中的某个 MD-Embed 单独设置此项,方法是将绘图切换至 Markdown 模式,并修改相应的 <code>[[Embed文件名#^块引ID|宽度x最大高度]]</code>。",
|
||||
"您可为绘图中的某个 MD-Embed 单独设置此项,方法是将绘图切换至 Markdown 模式,并修改相应的 <code>[[Embed 文件名#^块引ID|宽度x最大高度]]</code>。",
|
||||
MD_DEFAULT_FONT_NAME:
|
||||
"MD-Embed 的默认字体",
|
||||
MD_DEFAULT_FONT_DESC:
|
||||
@@ -400,11 +535,11 @@ FILENAME_HEAD: "文件名",
|
||||
MD_CSS_DESC:
|
||||
"MD-Embed 图像所采用的 CSS 样式表文件名。需包含扩展名,例如 md-embed.css。" +
|
||||
"允许使用 Markdown 文件(如 md-embed-css.md),但其内容应符合 CSS 语法。<br>" +
|
||||
"如果您要查询 CSS 所作用的 HTML 节点,请在 Obsidian 开发者控制台(CTRL+SHIFT+i)中键入命令:" +
|
||||
"如果您要查询 CSS 所作用的 HTML 节点,请在 Obsidian 开发者控制台(Ctrl+Shift+I)中键入命令:" +
|
||||
"<code>ExcalidrawAutomate.mostRecentMarkdownSVG</code> —— 这将显示 Excalidraw 最近生成的 SVG。<br>" +
|
||||
"此外,在 CSS 中不能任意地设置字体,您一般只能使用系统默认的标准字体(详见 README)," +
|
||||
"但可以通过上面的设置来额外添加一个自定义字体。<br>" +
|
||||
"您可为某个 MD-Embed 单独设置此项,方法是在其源文件的 frontmatter 中添加形如 <code>excalidraw-css: 库中的CSS文件或CSS片段</code> 的键值对。",
|
||||
"您可为某个 MD-Embed 单独设置此项,方法是在其源文件的 frontmatter 中添加形如 <code>excalidraw-css: 库中的 CSS 文件或 CSS 片段</code> 的键值对。",
|
||||
EMBED_HEAD: "嵌入到 Markdown 文档中的绘图",
|
||||
EMBED_DESC: `包括:嵌入到 Markdown 文档中的绘图的预览图类型(SVG、PNG)、源文件类型(Excalidraw 绘图文件、SVG、PNG)、缓存、图像大小、图像主题,以及嵌入的语法等。
|
||||
此外,还有自动导出 SVG 或 PNG 文件并保持与绘图文件状态同步的设置。`,
|
||||
@@ -420,13 +555,17 @@ FILENAME_HEAD: "文件名",
|
||||
EMBED_IMAGE_CACHE_NAME: "为嵌入到 Markdown 文档中的绘图创建预览图缓存",
|
||||
EMBED_IMAGE_CACHE_DESC: "可提高下次嵌入的速度。" +
|
||||
"但如果绘图中又嵌入了子绘图,当子绘图改变时,您需要打开子绘图并手动保存,才能够更新父绘图的预览图。",
|
||||
SCENE_IMAGE_CACHE_NAME: "缓存场景中嵌套的 Excalidraw",
|
||||
SCENE_IMAGE_CACHE_DESC: "缓存场景中嵌套的 Excalidraw 以加快场景渲染速度。这将加快渲染过程,特别是在您的场景中有深度嵌套的 Excalidraw 时。" +
|
||||
"Excalidraw 将智能地尝试识别嵌套 Excalidraw 的子元素是否发生变化,并更新缓存。 " +
|
||||
"如果您怀疑缓存未能正确更新,您可能需要关闭此功能。",
|
||||
EMBED_IMAGE_CACHE_CLEAR: "清除缓存",
|
||||
BACKUP_CACHE_CLEAR: "清除备份",
|
||||
BACKUP_CACHE_CLEAR_CONFIRMATION: "该操作将删除所有绘图文件的备份。备份是绘图文件损坏时的一种补救手段。每次您打开 Obsidian 时,本插件会自动清理无用的备份。您确定要现在删除所有备份吗?",
|
||||
EMBED_REUSE_EXPORTED_IMAGE_NAME:
|
||||
"将之前已导出的图像作为预览图",
|
||||
EMBED_REUSE_EXPORTED_IMAGE_DESC:
|
||||
"该选项与“自动导出 SVG/PNG 副本”选项配合使用。如果嵌入到 Markdown 文档中的绘图文件存在同名的 SVG/PNG 副本,则将其作为预览图,而不再重新生成。<br>" +
|
||||
"该选项与<a href='#"+TAG_AUTOEXPORT+"'>自动导出 SVG/PNG 副本</a>选项配合使用。如果嵌入到 Markdown 文档中的绘图文件存在同名的 SVG/PNG 副本,则将其作为预览图,而不再重新生成。<br>" +
|
||||
"该选项能够提高 Markdown 文档的打开速度,尤其是当嵌入到 Markdown 文档中的绘图文件中含有大量图像或 MD-Embed 时。" +
|
||||
"但是,该选项也可能导致预览图无法立即响应你对绘图文件或者 Obsidian 主题风格的修改。<br>" +
|
||||
"该选项仅作用于嵌入到 Markdown 文档中的绘图。" +
|
||||
@@ -439,7 +578,7 @@ FILENAME_HEAD: "文件名",
|
||||
EMBED_PREVIEW_IMAGETYPE_DESC:
|
||||
"<b>Native SVG:</b>高品质、可交互。<br>" +
|
||||
"<b>SVG:</b>高品质、不可交互。<br>" +
|
||||
"<b>PNG:</b>高性能、<a href='https://www.youtube.com/watch?v=yZQoJg2RCKI&t=633s' target='_blank'>不可交互</a>。",
|
||||
"<b>PNG:</b>高性能、<a href='https://www.youtube.com/watch?v=yZQoJg2RCKI&t=633s' target='_blank'>不可交互</a>。",
|
||||
PREVIEW_MATCH_OBSIDIAN_NAME: "预览图匹配 Obsidian 主题",
|
||||
PREVIEW_MATCH_OBSIDIAN_DESC:
|
||||
"开启此项,则当 Obsidian 处于黑暗模式时,嵌入到 Markdown 文档中的绘图的预览图也会以黑暗模式渲染;当 Obsidian 处于明亮模式时,预览图也会以明亮模式渲染。<br>" +
|
||||
@@ -449,17 +588,22 @@ FILENAME_HEAD: "文件名",
|
||||
"嵌入到 Markdown 文档中的绘图的预览图的默认宽度。该选项也适用于鼠标悬停时浮现的预览图。<br>" +
|
||||
"您可为某个要嵌入到 Markdown 文档中的绘图文件单独设置此项," +
|
||||
"方法是修改相应的内部链接格式为形如 <code>![[drawing.excalidraw|100]]</code> 或 <code>[[drawing.excalidraw|100x100]]</code>。",
|
||||
EMBED_HEIGHT_NAME: "预览图的默认高度",
|
||||
EMBED_HEIGHT_DESC:
|
||||
"嵌入到 Markdown 文档中的绘图的预览图得默认高度。该选项也适用于实时预览编辑和阅读模式,以及悬停预览。" +
|
||||
"您可以在使用 <code>![[drawing.excalidraw|100]]</code> 或者 <code>[[drawing.excalidraw|100x100]]</code>" +
|
||||
"格式在嵌入图像时指定自定义高度。",
|
||||
EMBED_TYPE_NAME: "“嵌入绘图到当前 Markdown 文档中”系列命令的源文件类型",
|
||||
EMBED_TYPE_DESC:
|
||||
"在命令面板中执行“嵌入绘图到当前 Markdown 文档中”系列命令时,要嵌入绘图文件本身,还是嵌入其 PNG 或 SVG 副本。<br>" +
|
||||
"如果您想选择 PNG 或 SVG 副本,需要先开启下方的“自动导出 PNG 副本”或“自动导出 SVG 副本”。<br>" +
|
||||
"如果您想选择 PNG 或 SVG 副本,需要先开启下方的<a href='#"+TAG_AUTOEXPORT+"'>自动导出 PNG / SVG 副本</a>。<br>" +
|
||||
"如果您选择了 PNG 或 SVG 副本,当副本不存在时,该命令将会插入一条损坏的链接,您需要打开绘图文件并手动导出副本才能修复 —— " +
|
||||
"也就是说,该选项不会自动帮您生成 PNG/SVG 副本,而只会引用已有的 PNG/SVG 副本。",
|
||||
EMBED_MARKDOWN_COMMENT_NAME: "Embed link to drawing as comment",
|
||||
EMBED_MARKDOWN_COMMENT_NAME: "将链接作为注释嵌入",
|
||||
EMBED_MARKDOWN_COMMENT_DESC:
|
||||
"Embed the link to the original Excalidraw file as a markdown link under the image, e.g.:<code>%%[[drawing.excalidraw]]%%</code>.<br>" +
|
||||
"Instead of adding a markdown comment you may also select the embedded SVG or PNG line and use the command palette action: " +
|
||||
"'<code>Excalidraw: Open Excalidraw drawing</code>' to open the drawing.",
|
||||
"在图像下方以 Markdown 链接的形式嵌入原始 Excalidraw 文件的链接,例如:<code>%%[[drawing.excalidraw]]%%</code>。<br>" +
|
||||
"除了添加 Markdown 注释之外,您还可以选择嵌入的 SVG 或 PNG,并使用命令面板:" +
|
||||
"'<code>Excalidraw: 打开 Excalidraw 绘图</code>'来打开该绘图",
|
||||
EMBED_WIKILINK_NAME: "“嵌入绘图到当前 Markdown 文档中”系列命令产生的内部链接类型",
|
||||
EMBED_WIKILINK_DESC:
|
||||
"<b>开启:</b>将产生 <code>![[Wiki 链接]]</code>。<b>关闭:</b>将产生 <code></code>。",
|
||||
@@ -470,13 +614,17 @@ FILENAME_HEAD: "文件名",
|
||||
"如果关闭,将导出透明背景的图像。",
|
||||
EXPORT_PADDING_NAME: "导出的图像的空白边距",
|
||||
EXPORT_PADDING_DESC:
|
||||
"导出的 SVG/PNG 图像四周的空白边距(单位:像素)。<br>" +
|
||||
"导出的 SVG/PNG 图像四周的空白边距(单位:像素)。对于裁剪框架引用,间距被设置为 0。<br>" +
|
||||
"增加该值,可以避免在导出图像时,靠近图像边缘的图形被裁掉。<br>" +
|
||||
"您可为某个绘图单独设置此项,方法是在其 frontmatter 中添加形如 <code>excalidraw-export-padding: 5<code> 的键值对。",
|
||||
EXPORT_THEME_NAME: "导出的图像匹配主题",
|
||||
EXPORT_THEME_DESC:
|
||||
"导出与绘图的黑暗/明亮主题匹配的图像。" +
|
||||
"如果关闭,在黑暗主题下导出的图像将和明亮主题一样。",
|
||||
EXPORT_EMBED_SCENE_NAME: "在导出的图片中嵌入场景",
|
||||
EXPORT_EMBED_SCENE_DESC:
|
||||
"在导出的图像中嵌入 Excalidraw 场景。可以通过在文件级别添加 <code>excalidraw-export-embed-scene: true/false</code> frontmatter 元数据键来覆盖此设置。" +
|
||||
"此设置仅在您下次(重新)打开绘图时生效。",
|
||||
EXPORT_HEAD: "导出设置",
|
||||
EXPORT_SYNC_NAME:
|
||||
"保持 SVG/PNG 文件名与绘图文件同步",
|
||||
@@ -497,7 +645,19 @@ FILENAME_HEAD: "文件名",
|
||||
EXPORT_BOTH_DARK_AND_LIGHT_DESC: "若开启,Excalidraw 将导出两个文件:filename.dark.png(或 filename.dark.svg)和 filename.light.png(或 filename.light.svg)。<br>"+
|
||||
"该选项可作用于“自动导出 SVG 副本”、“自动导出 PNG 副本”,以及其他的手动的导出命令。",
|
||||
COMPATIBILITY_HEAD: "兼容性设置",
|
||||
COMPATIBILITY_DESC: "如果没有特殊原因(例如您想同时在 VSCode / Logseq 和 Obsidian 中使用 Excalidraw),建议您使用 markdown 格式的绘图文件,而不是旧的 excalidraw.com 格式,因为本插件的很多功能在旧格式中无法使用。",
|
||||
COMPATIBILITY_DESC: "如果没有特殊原因(例如您想同时在 VSCode / Logseq 和 Obsidian 中使用 Excalidraw),建议您使用 Markdown 格式的绘图文件,而不是旧的 excalidraw.com 格式,因为本插件的很多功能在旧格式中无法使用。",
|
||||
DUMMY_TEXT_ELEMENT_LINT_SUPPORT_NAME: "代码格式化(Linting)兼容性",
|
||||
DUMMY_TEXT_ELEMENT_LINT_SUPPORT_DESC: "Excalidraw 对 <code># Excalidraw Data</code> 下的文件结构非常敏感。文档的自动代码格式化(linting)可能会在 Excalidraw 数据中造成错误。" +
|
||||
"虽然我已经努力使数据加载对自动代码格式化(linting)变更具有一定的抗性,但这种解决方案并非万无一失。<br>"+
|
||||
"<mark>最好的方法是避免使用不同的插件对 Excalidraw 文档进行自动更改。</mark><br>" +
|
||||
"如果出于某些合理的原因,您决定忽略我的建议并配置了 Excalidraw 文件的自动代码格式化,那么可以使用这个设置<br> " +
|
||||
"<code>## Text Elements</code> 部分对空行很敏感。一种常见的代码格式化是在章节标题后添加一个空行。但对于 Excalidraw 来说,这将破坏/改变您绘图中的第一个文本元素。" +
|
||||
"为了解决这个问题,您可以启用这个设置。启用后 Excalidraw 将在 <code>## Text Elements</code> 的开头添加一个虚拟元素,供自动代码格式化工具修改。" ,
|
||||
PRESERVE_TEXT_AFTER_DRAWING_NAME: "Zotero 和脚注(footnotes)的兼容性",
|
||||
PRESERVE_TEXT_AFTER_DRAWING_DESC: "保留 Markdown 文件中 <code>## Drawing</code> 部分之后的文本内容。保存非常大的绘图时,这可能会造成微小的性能影响。",
|
||||
DEBUGMODE_NAME: "开启 debug 信息",
|
||||
DEBUGMODE_DESC: "我建议在启用/禁用此设置后重新启动 Obsidian。这将在控制台中启用调试消息。这对于排查问题很有帮助。" +
|
||||
"如果您在使用插件时遇到问题,请启用此设置,重现问题,并在 <a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/issues'>GitHub</a> 上提出的问题中包含控制台日志。",
|
||||
SLIDING_PANES_NAME: "Sliding panes 插件支持",
|
||||
SLIDING_PANES_DESC:
|
||||
"设置此项后需要重启 Obsidian 才能生效。<br>" +
|
||||
@@ -525,11 +685,16 @@ FILENAME_HEAD: "文件名",
|
||||
LATEX_DEFAULT_NAME: "插入 LaTeX 时的默认表达式",
|
||||
LATEX_DEFAULT_DESC: "允许留空。允许使用类似 <code>\\color{white}</code> 的格式化表达式。",
|
||||
NONSTANDARD_HEAD: "非 Excalidraw.com 官方支持的特性",
|
||||
NONSTANDARD_DESC: `这些特性不受 Excalidraw.com 官方支持。如果以 Excalidraw.com 格式导出绘图,这些特性将会发生不可预知的变化。
|
||||
NONSTANDARD_DESC: `这些特性不受 Excalidraw.com 官方支持。如果在 Excalidraw.com 导入绘图,这些特性将会发生不可预知的变化。
|
||||
包括:自定义画笔工具的数量,自定义字体等。`,
|
||||
RENDER_TWEAK_HEAD: "渲染优化",
|
||||
MAX_IMAGE_ZOOM_IN_NAME: "最大图像放大倍数",
|
||||
MAX_IMAGE_ZOOM_IN_DESC: "为了节省内存,并且因为 Apple Safari (Obsidian on iOS) 有一些硬编码的限制,Excalidraw.com 在放大时会限制图像和大型对象的最大分辨率。您可以使用乘数来覆盖这个限制。" +
|
||||
"这意味着将乘以 Excalidraw 默认设置的限制,乘数越大,图像放大分辨率就越高,但也会消耗更多内存。" +
|
||||
"我建议尝试多个值来设置这个参数。当您放大一个较大的 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 自动化",
|
||||
@@ -561,17 +726,29 @@ FILENAME_HEAD: "文件名",
|
||||
"开启此项,则可在 Obsidian 实时预览模式的编辑视图下,用形如 <code>![[绘图|宽度|样式]]</code> 的语法来嵌入绘图。<br>" +
|
||||
"该选项不会在已打开的文档中立刻生效 —— " +
|
||||
"你需要重新打开此文档来使其生效。",
|
||||
CUSTOM_FONT_HEAD: "自定义字体",
|
||||
FADE_OUT_EXCALIDRAW_MARKUP_NAME: "淡化 Excalidraw 标记",
|
||||
FADE_OUT_EXCALIDRAW_MARKUP_DESC: "在 Markdown 视图模式下,在 Markdown 注释 %% " +
|
||||
"之后的部分会淡化。文本仍然存在,但视觉杂乱感会减少。请注意,您可以将 %% 放在 # Text Elements 行的上一行," +
|
||||
"这样,整个 Excalidraw Markdown 都会淡化,包括 # Text Elements。 副作用是您将无法在其他 Markdown 笔记中引用文本块,即 %% 注释部分之后的内容。这应该不是大问题。" +
|
||||
"如果您想编辑 Excalidraw Markdown 脚本,只需切换到 Markdown 视图模式并暂时删除 %% 注释。",
|
||||
EXCALIDRAW_PROPERTIES_NAME: "将 Excalidraw 属性加载到 Obsidian 的自动提示中",
|
||||
EXCALIDRAW_PROPERTIES_DESC: "切换此设置以在插件启动时将 Excalidraw 文档属性加载到 Obsidian 的属性自动提示中。"+
|
||||
"启用此功能简化了 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>获取免费商用中文手写字体。",
|
||||
SCRIPT_SETTINGS_HEAD: "已安装脚本的设置",
|
||||
SCRIPT_SETTINGS_DESC: "有些 Excalidraw 自动化脚本包含设置项,当执行这些脚本时,它们会在该列表下添加设置项。",
|
||||
TASKBONE_HEAD: "Taskbone OCR(光学符号识别)",
|
||||
@@ -581,16 +758,22 @@ FILENAME_HEAD: "文件名",
|
||||
"注意,识别的过程不是在本地进行的,而是通过在线 API,图像会被上传到 taskbone 的服务器(仅用于识别目的)。如果您介意,请不要使用这个功能。",
|
||||
TASKBONE_ENABLE_NAME: "启用 Taskbone",
|
||||
TASKBONE_ENABLE_DESC: "启用意味着您同意 Taskbone <a href='https://www.taskbone.com/legal/terms/' target='_blank'>条款及细则</a> 以及 " +
|
||||
"<a href='https://www.taskbone.com/legal/privacy/' target='_blank'>隐私政策</a>.",
|
||||
"<a href='https://www.taskbone.com/legal/privacy/' target='_blank'>隐私政策</a>。",
|
||||
TASKBONE_APIKEY_NAME: "Taskbone API Key",
|
||||
TASKBONE_APIKEY_DESC: "Taskbone 的免费 API key 提供了一定数量的每月识别次数。如果您非常频繁地使用此功能,或者想要支持 " +
|
||||
"Taskbone 的开发者(您懂的,没有人能用爱发电,Taskbone 开发者也需要投入资金来维持这项 OCR 服务)您可以" +
|
||||
"到 <a href='https://www.taskbone.com/' target='_blank'>taskbone.com</a> 购买一个商用 API key。购买后请将它填写到旁边这个文本框里,替换掉原本自动生成的免费 API key。",
|
||||
|
||||
//HotkeyEditor
|
||||
HOTKEY_PRESS_COMBO_NANE: "按下您的组合键",
|
||||
HOTKEY_PRESS_COMBO_DESC: "请按下所需的组合键",
|
||||
HOTKEY_BUTTON_ADD_OVERRIDE: "添加新的(热键)覆写",
|
||||
HOTKEY_BUTTON_REMOVE: "移除",
|
||||
|
||||
//openDrawings.ts
|
||||
SELECT_FILE: "选择一个文件后按回车。",
|
||||
SELECT_COMMAND: "选择一个命令后按回车。",
|
||||
SELECT_FILE_WITH_OPTION_TO_SCALE: `选择一个文件后按回车,或者 ${labelSHIFT()}+${labelMETA()}+ENTER 以 100% 尺寸插入。`,
|
||||
SELECT_FILE_WITH_OPTION_TO_SCALE: `选择一个文件后按回车,或者 ${labelSHIFT()}+${labelMETA()}+Enter 以 100% 尺寸插入。`,
|
||||
NO_MATCH: "查询不到匹配的文件。",
|
||||
NO_MATCHING_COMMAND: "查询不到匹配的命令。",
|
||||
SELECT_FILE_TO_LINK: "选择要插入(以内部链接形式嵌入)到当前绘图中的文件。",
|
||||
@@ -605,6 +788,13 @@ FILENAME_HEAD: "文件名",
|
||||
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: "输入章节(标题)名称以创建",
|
||||
|
||||
//EmbeddedFileLoader.ts
|
||||
INFINITE_LOOP_WARNING:
|
||||
"EXCALIDRAW 警告\n停止加载嵌入的图像,因为此文件中存在死循环:\n",
|
||||
@@ -624,7 +814,7 @@ FILENAME_HEAD: "文件名",
|
||||
TOGGLE_FRAME_RENDERING: "开启或关闭框架渲染",
|
||||
TOGGLE_FRAME_CLIPPING: "开启或关闭框架裁剪",
|
||||
OPEN_LINK_CLICK: "打开所选的图形或文本元素里的链接",
|
||||
OPEN_LINK_PROPS: "编辑所选 MD-Embed 的内部链接,或者打开所选的图形或文本元素里的链接",
|
||||
OPEN_LINK_PROPS: "打开图像链接或 LaTeX 公式编辑器",
|
||||
|
||||
//IFrameActionsMenu.tsx
|
||||
NARROW_TO_HEADING: "缩放至标题",
|
||||
@@ -677,11 +867,31 @@ FILENAME_HEAD: "文件名",
|
||||
PROMPT_BUTTON_INSERT_SPACE: "插入空格",
|
||||
PROMPT_BUTTON_INSERT_LINK: "插入内部链接",
|
||||
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 文件",
|
||||
|
||||
};
|
||||
1480
src/main.ts
@@ -1,12 +1,11 @@
|
||||
import * as React from "react";
|
||||
import ExcalidrawView from "../ExcalidrawView";
|
||||
import { Notice } from "obsidian";
|
||||
|
||||
type ButtonProps = {
|
||||
title: string;
|
||||
action: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||
longpress?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||
icon: JSX.Element;
|
||||
view: ExcalidrawView;
|
||||
};
|
||||
|
||||
type ButtonState = {
|
||||
@@ -24,6 +23,10 @@ export class ActionButton extends React.Component<ButtonProps, ButtonState> {
|
||||
};
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
this.render = () => null;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<button
|
||||
@@ -48,7 +51,8 @@ export class ActionButton extends React.Component<ButtonProps, ButtonState> {
|
||||
onPointerDown={(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
this.toastMessageTimeout = window.setTimeout(
|
||||
() => {
|
||||
this.props.view.excalidrawAPI?.setToast({message:this.props.title, duration: 3000, closable: true});
|
||||
new Notice(this.props.title, 3000);
|
||||
//this.props.view.excalidrawAPI?.setToast({message:this.props.title, duration: 3000, closable: true});
|
||||
this.toastMessageTimeout = 0;
|
||||
},
|
||||
400,
|
||||
@@ -58,7 +62,8 @@ export class ActionButton extends React.Component<ButtonProps, ButtonState> {
|
||||
if(this.props.longpress) {
|
||||
this.props.longpress(event);
|
||||
} else {
|
||||
this.props.view.excalidrawAPI?.setToast({message:"Cannot pin this action", duration: 3000, closable: true});
|
||||
new Notice("Cannot pin this action", 3000);
|
||||
//this.props.view.excalidrawAPI?.setToast({message:"Cannot pin this action", duration: 3000, closable: true});
|
||||
}
|
||||
this.longpressTimeout = 0;
|
||||
},
|
||||
|
||||
@@ -7,14 +7,18 @@ import { ActionButton } from "./ActionButton";
|
||||
import { ICONS } from "./ActionIcons";
|
||||
import { t } from "src/lang/helpers";
|
||||
import { ScriptEngine } from "src/Scripts";
|
||||
import { ROOTELEMENTSIZE, mutateElement, nanoid, sceneCoordsToViewportCoords } from "src/constants/constants";
|
||||
import { MD_EX_SECTIONS, ROOTELEMENTSIZE, mutateElement, nanoid, sceneCoordsToViewportCoords } from "src/constants/constants";
|
||||
import { REGEX_LINK, REG_LINKINDEX_HYPERLINK } from "src/ExcalidrawData";
|
||||
import { processLinkText, useDefaultExcalidrawFrame } from "src/utils/CustomEmbeddableUtils";
|
||||
import { cleanSectionHeading } from "src/utils/ObsidianUtils";
|
||||
import { EmbeddableSettings } from "src/dialogs/EmbeddableSettings";
|
||||
import { openExternalLink } from "src/utils/ExcalidrawViewUtils";
|
||||
import { getEA } from "src";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
|
||||
export class EmbeddableMenu {
|
||||
private menuFadeTimeout: number = 0;
|
||||
private menuElementId: string = null;
|
||||
|
||||
constructor(
|
||||
private view:ExcalidrawView,
|
||||
@@ -22,6 +26,19 @@ export class EmbeddableMenu {
|
||||
) {
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
if(this.menuFadeTimeout) {
|
||||
clearTimeout(this.menuFadeTimeout);
|
||||
this.menuFadeTimeout = null;
|
||||
}
|
||||
this.view = null;
|
||||
this.containerRef = null;
|
||||
this.updateElement = null;
|
||||
this.handleMouseEnter = null;
|
||||
this.handleMouseLeave = null;
|
||||
this.renderButtons = null;
|
||||
}
|
||||
|
||||
private updateElement = (subpath: string, element: ExcalidrawEmbeddableElement, file: TFile) => {
|
||||
if(!element) return;
|
||||
const view = this.view;
|
||||
@@ -34,26 +51,122 @@ export class EmbeddableMenu {
|
||||
file.extension === "md",
|
||||
)
|
||||
const link = `[[${path}${subpath}]]`;
|
||||
mutateElement (element,{link});
|
||||
const ea = getEA(view) as ExcalidrawAutomate;
|
||||
ea.copyViewElementsToEAforEditing([element]);
|
||||
ea.getElement(element.id).link = link;
|
||||
view.excalidrawData.elementLinks.set(element.id, link);
|
||||
view.setDirty(99);
|
||||
view.updateScene({appState: {activeEmbeddable: null}});
|
||||
ea.addElementsToView(false, true, true).then(() => ea.destroy());
|
||||
}
|
||||
|
||||
private menuFadeTimeout: number = 0;
|
||||
private menuElementId: string = null;
|
||||
private handleMouseEnter () {
|
||||
clearTimeout(this.menuFadeTimeout);
|
||||
this.containerRef.current?.style.setProperty("opacity", "1");
|
||||
};
|
||||
|
||||
private handleMouseLeave () {
|
||||
const self = this;
|
||||
this.menuFadeTimeout = window.setTimeout(() => {
|
||||
self.containerRef.current?.style.setProperty("opacity", "0.2");
|
||||
this.containerRef.current?.style.setProperty("opacity", "0.2");
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
private async actionMarkdownSelection (file: TFile, isExcalidrawFile: boolean, subpath: string, element: ExcalidrawEmbeddableElement) {
|
||||
this.view.updateScene({appState: {activeEmbeddable: null}, storeAction: "update"});
|
||||
const sections = (await app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "heading")
|
||||
.filter((b: any) => !isExcalidrawFile || !MD_EX_SECTIONS.includes(b.display));
|
||||
let values, display;
|
||||
if(isExcalidrawFile) {
|
||||
values = sections.map((b: any) => `#${cleanSectionHeading(b.display)}`);
|
||||
display = sections.map((b: any) => b.display);
|
||||
} else {
|
||||
values = [""].concat(
|
||||
sections.map((b: any) => `#${cleanSectionHeading(b.display)}`)
|
||||
);
|
||||
display = [t("SHOW_ENTIRE_FILE")].concat(
|
||||
sections.map((b: any) => b.display)
|
||||
);
|
||||
}
|
||||
const newSubpath = await ScriptEngine.suggester(
|
||||
app, display, values, "Select section from document"
|
||||
);
|
||||
if(!newSubpath && newSubpath!=="") return;
|
||||
if (newSubpath !== subpath) {
|
||||
this.updateElement(newSubpath, element, file);
|
||||
}
|
||||
}
|
||||
|
||||
private async actionMarkdownBlock (file: TFile, subpath: string, element: ExcalidrawEmbeddableElement) {
|
||||
if(!file) return;
|
||||
this.view.updateScene({appState: {activeEmbeddable: null}, storeAction: "update"});
|
||||
const paragraphs = (await app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },file))
|
||||
.blocks.filter((b: any) => b.display && b.node &&
|
||||
(b.node.type === "paragraph" || b.node.type === "blockquote" || b.node.type === "listItem" || b.node.type === "table" || b.node.type === "callout")
|
||||
);
|
||||
const values = ["entire-file"].concat(paragraphs);
|
||||
const display = [t("SHOW_ENTIRE_FILE")].concat(
|
||||
paragraphs.map((b: any) => `${b.node?.id ? `#^${b.node.id}: ` : ``}${b.display.trim()}`));
|
||||
|
||||
const selectedBlock = await ScriptEngine.suggester(
|
||||
app, display, values, "Select section from document"
|
||||
);
|
||||
if(!selectedBlock) return;
|
||||
|
||||
if(selectedBlock==="entire-file") {
|
||||
if(subpath==="") return;
|
||||
this.updateElement("", element, file);
|
||||
return;
|
||||
}
|
||||
|
||||
let blockID = selectedBlock.node.id;
|
||||
if(blockID && (`#^${blockID}` === subpath)) return;
|
||||
if (!blockID) {
|
||||
const offset = selectedBlock.node?.position?.end?.offset;
|
||||
if(!offset) return;
|
||||
blockID = nanoid();
|
||||
const fileContents = await app.vault.cachedRead(file);
|
||||
if(!fileContents) return;
|
||||
await app.vault.modify(file, fileContents.slice(0, offset) + ` ^${blockID}` + fileContents.slice(offset));
|
||||
await sleep(200); //wait for cache to update
|
||||
}
|
||||
this.updateElement(`#^${blockID}`, element, file);
|
||||
}
|
||||
|
||||
private actionZoomToElement (element: ExcalidrawEmbeddableElement, maxLevel?: number) {
|
||||
if(!element) return;
|
||||
const api = this.view.excalidrawAPI as ExcalidrawImperativeAPI;
|
||||
api.zoomToFit([element], maxLevel ?? this.view.plugin.settings.zoomToFitMaxLevel, 0.1);
|
||||
}
|
||||
|
||||
private actionProperties (element: ExcalidrawEmbeddableElement, file: TFile) {
|
||||
if(!element) return;
|
||||
new EmbeddableSettings(this.view.plugin,this.view,file,element).open();
|
||||
}
|
||||
|
||||
private actionCrop (element: ExcalidrawEmbeddableElement) {
|
||||
if(!element) return;
|
||||
//@ts-ignore
|
||||
this.view.app.commands.executeCommandById("obsidian-excalidraw-plugin:crop-image");
|
||||
}
|
||||
|
||||
private actionReload (iframe: HTMLIFrameElement, link: string) {
|
||||
iframe.src = link;
|
||||
}
|
||||
|
||||
private actionOpen (iframe: HTMLIFrameElement, element: ExcalidrawEmbeddableElement) {
|
||||
openExternalLink(
|
||||
!iframe.src.startsWith("https://www.youtube.com") && !iframe.src.startsWith("https://player.vimeo.com")
|
||||
? iframe.src
|
||||
: element.link,
|
||||
this.view.app
|
||||
);
|
||||
}
|
||||
|
||||
private actionCopyCode (element: ExcalidrawEmbeddableElement, link: string) {
|
||||
if(!element) return;
|
||||
navigator.clipboard.writeText(atob(link.split(",")[1]));
|
||||
}
|
||||
|
||||
renderButtons(appState: AppState) {
|
||||
const view = this.view;
|
||||
@@ -99,6 +212,8 @@ export class EmbeddableMenu {
|
||||
const { subpath, file } = processLinkText(link, view);
|
||||
if(!file) return;
|
||||
const isMD = file.extension==="md";
|
||||
const isExcalidrawFile = view.plugin.isExcalidrawFile(file);
|
||||
const isPDF = file.extension==="pdf";
|
||||
const { x, y } = sceneCoordsToViewportCoords( { sceneX: element.x, sceneY: element.y }, appState);
|
||||
const top = `${y-2.5*ROOTELEMENTSIZE-appState.offsetTop}px`;
|
||||
const left = `${x-appState.offsetLeft}px`;
|
||||
@@ -127,89 +242,38 @@ export class EmbeddableMenu {
|
||||
<ActionButton
|
||||
key={"MarkdownSection"}
|
||||
title={t("NARROW_TO_HEADING")}
|
||||
action={async () => {
|
||||
const sections = (await app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "heading");
|
||||
const values = [""].concat(
|
||||
sections.map((b: any) => `#${cleanSectionHeading(b.display)}`)
|
||||
);
|
||||
const display = [t("SHOW_ENTIRE_FILE")].concat(
|
||||
sections.map((b: any) => b.display)
|
||||
);
|
||||
const newSubpath = await ScriptEngine.suggester(
|
||||
app, display, values, "Select section from document"
|
||||
);
|
||||
if(!newSubpath && newSubpath!=="") return;
|
||||
if (newSubpath !== subpath) {
|
||||
this.updateElement(newSubpath, element, file);
|
||||
}
|
||||
}}
|
||||
action={async () => this.actionMarkdownSelection(file, isExcalidrawFile, subpath, element)}
|
||||
icon={ICONS.ZoomToSection}
|
||||
view={view}
|
||||
/>
|
||||
)}
|
||||
{isMD && (
|
||||
{isMD && !isExcalidrawFile && (
|
||||
<ActionButton
|
||||
key={"MarkdownBlock"}
|
||||
title={t("NARROW_TO_BLOCK")}
|
||||
action={async () => {
|
||||
if(!file) return;
|
||||
const paragrphs = (await app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "paragraph");
|
||||
const values = ["entire-file"].concat(paragrphs);
|
||||
const display = [t("SHOW_ENTIRE_FILE")].concat(
|
||||
paragrphs.map((b: any) => `${b.node?.id ? `#^${b.node.id}: ` : ``}${b.display.trim()}`));
|
||||
|
||||
const selectedBlock = await ScriptEngine.suggester(
|
||||
app, display, values, "Select section from document"
|
||||
);
|
||||
if(!selectedBlock) return;
|
||||
|
||||
if(selectedBlock==="entire-file") {
|
||||
if(subpath==="") return;
|
||||
this.updateElement("", element, file);
|
||||
return;
|
||||
}
|
||||
|
||||
let blockID = selectedBlock.node.id;
|
||||
if(blockID && (`#^${blockID}` === subpath)) return;
|
||||
if (!blockID) {
|
||||
const offset = selectedBlock.node?.position?.end?.offset;
|
||||
if(!offset) return;
|
||||
blockID = nanoid();
|
||||
const fileContents = await app.vault.cachedRead(file);
|
||||
if(!fileContents) return;
|
||||
await app.vault.modify(file, fileContents.slice(0, offset) + ` ^${blockID}` + fileContents.slice(offset));
|
||||
await sleep(200); //wait for cache to update
|
||||
}
|
||||
this.updateElement(`#^${blockID}`, element, file);
|
||||
}}
|
||||
action={async () => this.actionMarkdownBlock(file, subpath, element)}
|
||||
icon={ICONS.ZoomToBlock}
|
||||
view={view}
|
||||
/>
|
||||
)}
|
||||
<ActionButton
|
||||
key={"ZoomToElement"}
|
||||
title={t("ZOOM_TO_FIT")}
|
||||
action={() => {
|
||||
if(!element) return;
|
||||
api.zoomToFit([element], 30, 0.1);
|
||||
}}
|
||||
action={() => this.actionZoomToElement(element,30)}
|
||||
icon={ICONS.ZoomToSelectedElement}
|
||||
view={view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"Properties"}
|
||||
title={t("PROPERTIES")}
|
||||
action={() => {
|
||||
if(!element) return;
|
||||
new EmbeddableSettings(view.plugin,view,file,element).open();
|
||||
}}
|
||||
action={() => this.actionProperties(element, file)}
|
||||
icon={ICONS.Properties}
|
||||
view={view}
|
||||
/>
|
||||
{isPDF && (
|
||||
<ActionButton
|
||||
key={"Crop"}
|
||||
title={t("CROP_PAGE")}
|
||||
action={() => this.actionCrop(element)}
|
||||
icon={ICONS.Crop}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -247,57 +311,34 @@ export class EmbeddableMenu {
|
||||
<ActionButton
|
||||
key={"Reload"}
|
||||
title={t("RELOAD")}
|
||||
action={()=>{
|
||||
iframe.src = link;
|
||||
}}
|
||||
action={()=> this.actionReload(iframe, link)}
|
||||
icon={ICONS.Reload}
|
||||
view={view}
|
||||
/>
|
||||
)}
|
||||
<ActionButton
|
||||
key={"Open"}
|
||||
title={t("OPEN_IN_BROWSER")}
|
||||
action={() => {
|
||||
openExternalLink(
|
||||
!iframe.src.startsWith("https://www.youtube.com") && !iframe.src.startsWith("https://player.vimeo.com")
|
||||
? iframe.src
|
||||
: element.link,
|
||||
view.app
|
||||
);
|
||||
}}
|
||||
action={() => this.actionOpen(iframe, element)}
|
||||
icon={ICONS.Globe}
|
||||
view={view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"ZoomToElement"}
|
||||
title={t("ZOOM_TO_FIT")}
|
||||
action={() => {
|
||||
if(!element) return;
|
||||
api.zoomToFit([element], view.plugin.settings.zoomToFitMaxLevel, 0.1);
|
||||
}}
|
||||
action={() => this.actionZoomToElement(element)}
|
||||
icon={ICONS.ZoomToSelectedElement}
|
||||
view={view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"Properties"}
|
||||
title={t("PROPERTIES")}
|
||||
action={() => {
|
||||
if(!element) return;
|
||||
new EmbeddableSettings(view.plugin,view,null,element).open();
|
||||
}}
|
||||
action={() => this.actionProperties(element, null)}
|
||||
icon={ICONS.Properties}
|
||||
view={view}
|
||||
/>
|
||||
{link?.startsWith("data:text/html") && (
|
||||
<ActionButton
|
||||
key={"CopyCode"}
|
||||
title={t("COPYCODE")}
|
||||
action={() => {
|
||||
if(!element) return;
|
||||
navigator.clipboard.writeText(atob(link.split(",")[1]));
|
||||
}}
|
||||
action={() => this.actionCopyCode(element, link)}
|
||||
icon={ICONS.Copy}
|
||||
view={view}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,7 @@ import { t } from "src/lang/helpers";
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
|
||||
export const setPen = (pen: PenStyle, api: any) => {
|
||||
export function setPen (pen: PenStyle, api: any) {
|
||||
const st = api.getAppState();
|
||||
api.updateScene({
|
||||
appState: {
|
||||
@@ -34,11 +34,12 @@ export const setPen = (pen: PenStyle, api: any) => {
|
||||
currentItemRoughness: st.currentItemRoughness,
|
||||
}}
|
||||
: null,
|
||||
}
|
||||
},
|
||||
storeAction: "update",
|
||||
})
|
||||
}
|
||||
|
||||
export const resetStrokeOptions = (resetCustomPen:any, api: ExcalidrawImperativeAPI, clearCurrentStrokeOptions: boolean) => {
|
||||
export function resetStrokeOptions (resetCustomPen:any, api: ExcalidrawImperativeAPI, clearCurrentStrokeOptions: boolean) {
|
||||
api.updateScene({
|
||||
appState: {
|
||||
...resetCustomPen ? {
|
||||
@@ -50,13 +51,16 @@ export const resetStrokeOptions = (resetCustomPen:any, api: ExcalidrawImperative
|
||||
}: null,
|
||||
resetCustomPen: null,
|
||||
...clearCurrentStrokeOptions ? {currentStrokeOptions: null} : null,
|
||||
}
|
||||
},
|
||||
storeAction: "update",
|
||||
});
|
||||
}
|
||||
|
||||
export class ObsidianMenu {
|
||||
private clickTimestamp:number[];
|
||||
private activePen: PenStyle;
|
||||
private longpressTimeout : { [key: number]: number } = {};
|
||||
private prevClickTimestamp: number = 0;
|
||||
constructor(
|
||||
private plugin: ExcalidrawPlugin,
|
||||
private toolsRef: React.MutableRefObject<any>,
|
||||
@@ -65,9 +69,98 @@ export class ObsidianMenu {
|
||||
this.clickTimestamp = Array.from({length: Object.keys(PENS).length}, () => 0);
|
||||
}
|
||||
|
||||
renderCustomPens = (isMobile: boolean, appState: AppState) => {
|
||||
private actionCustomPenLabelClick(index: number, pen: PenStyle) {
|
||||
const now = Date.now();
|
||||
const dblClick = now-this.clickTimestamp[index] < 500;
|
||||
//open pen settings on double click
|
||||
if(dblClick) {
|
||||
const penSettings = new PenSettingsModal(this.plugin,this.view,index);
|
||||
(async () => {
|
||||
await this.plugin.loadSettings();
|
||||
penSettings.open();
|
||||
})();
|
||||
return;
|
||||
}
|
||||
this.clickTimestamp[index] = now;
|
||||
|
||||
const api = this.view.excalidrawAPI;
|
||||
const st = api.getAppState();
|
||||
|
||||
//single second click to reset freedraw to default
|
||||
if(st.currentStrokeOptions === pen.penOptions && st.activeTool.type === "freedraw") {
|
||||
resetStrokeOptions(st.resetCustomPen, api, true);
|
||||
return;
|
||||
}
|
||||
|
||||
//apply pen settings to canvas
|
||||
this.activePen = {...pen};
|
||||
setPen(pen,api);
|
||||
api.setActiveTool({type:"freedraw"});
|
||||
}
|
||||
|
||||
private actionScriptButtonPonterUp(index: number, key: string) {
|
||||
if(this.longpressTimeout[index]) {
|
||||
this.view.ownerWindow.clearTimeout(this.longpressTimeout[index]);
|
||||
this.longpressTimeout[index] = 0;
|
||||
(async ()=>{
|
||||
const f = this.view.app.vault.getAbstractFileByPath(key);
|
||||
if (f && f instanceof TFile) {
|
||||
this.plugin.scriptEngine.executeScript(
|
||||
this.view,
|
||||
await this.view.app.vault.read(f),
|
||||
this.plugin.scriptEngine.getScriptName(f),
|
||||
f
|
||||
);
|
||||
}
|
||||
})()
|
||||
}
|
||||
}
|
||||
|
||||
private actionScriptButtonPointerDown(index: number, key: string) {
|
||||
const now = Date.now();
|
||||
if(this.longpressTimeout[index]>0) {
|
||||
this.view.ownerWindow.clearTimeout(this.longpressTimeout[index]);
|
||||
this.longpressTimeout[index] = 0;
|
||||
}
|
||||
if(now-this.prevClickTimestamp >= 500) {
|
||||
this.longpressTimeout[index] = this.view.ownerWindow.setTimeout(
|
||||
() => {
|
||||
this.longpressTimeout[index] = 0;
|
||||
(async () =>{
|
||||
await this.plugin.loadSettings();
|
||||
const index = this.plugin.settings.pinnedScripts.indexOf(key)
|
||||
if(index > -1) {
|
||||
this.plugin.settings.pinnedScripts.splice(index,1);
|
||||
this.view.excalidrawAPI?.setToast({message:`Pin removed: ${name}`, duration: 3000, closable: true});
|
||||
}
|
||||
await this.plugin.saveSettings();
|
||||
this.plugin.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> {
|
||||
if (v.view instanceof ExcalidrawView) v.view.updatePinnedScripts()
|
||||
})
|
||||
})()
|
||||
},
|
||||
1500
|
||||
)
|
||||
}
|
||||
this.prevClickTimestamp = now;
|
||||
}
|
||||
|
||||
private actionShowHideMenu (isMobile: boolean, appState: AppState) {
|
||||
this.toolsRef.current.setTheme(appState.theme);
|
||||
this.toolsRef.current.toggleVisibility(
|
||||
appState.zenModeEnabled || isMobile,
|
||||
);
|
||||
}
|
||||
|
||||
private actionInsertAnyFile() {
|
||||
this.view.setCurrentPositionToCenter();
|
||||
const insertFileModal = new UniversalInsertFileModal(this.plugin, this.view);
|
||||
insertFileModal.open();
|
||||
}
|
||||
|
||||
public renderCustomPens (isMobile: boolean, appState: AppState) {
|
||||
return(
|
||||
appState.customPens?.map((key,index)=>{
|
||||
appState.customPens?.map((_,index)=>{
|
||||
const pen = this.plugin.settings.customPens[index]
|
||||
//Reset stroke setting when changing to a different tool
|
||||
if(
|
||||
@@ -75,7 +168,7 @@ export class ObsidianMenu {
|
||||
appState.activeTool.type !== "freedraw" &&
|
||||
appState.currentStrokeOptions === pen.penOptions
|
||||
) {
|
||||
setTimeout(()=> resetStrokeOptions(appState.resetCustomPen, this.view.excalidrawAPI, false))
|
||||
setTimeout(()=> resetStrokeOptions(appState.resetCustomPen, this.view.excalidrawAPI, false));
|
||||
}
|
||||
//if Pen settings are loaded, select custom pen when activating the freedraw element
|
||||
if (
|
||||
@@ -111,34 +204,7 @@ export class ObsidianMenu {
|
||||
"is-mobile": isMobile,
|
||||
},
|
||||
)}
|
||||
onClick={() => {
|
||||
const now = Date.now();
|
||||
const dblClick = now-this.clickTimestamp[index] < 500;
|
||||
//open pen settings on double click
|
||||
if(dblClick) {
|
||||
const penSettings = new PenSettingsModal(this.plugin,this.view,index);
|
||||
(async () => {
|
||||
await this.plugin.loadSettings();
|
||||
penSettings.open();
|
||||
})();
|
||||
return;
|
||||
}
|
||||
this.clickTimestamp[index] = now;
|
||||
|
||||
const api = this.view.excalidrawAPI;
|
||||
const st = api.getAppState();
|
||||
|
||||
//single second click to reset freedraw to default
|
||||
if(st.currentStrokeOptions === pen.penOptions && st.activeTool.type === "freedraw") {
|
||||
resetStrokeOptions(st.resetCustomPen, api, true);
|
||||
return;
|
||||
}
|
||||
|
||||
//apply pen settings to canvas
|
||||
this.activePen = {...pen};
|
||||
setPen(pen,api);
|
||||
api.setActiveTool({type:"freedraw"});
|
||||
}}
|
||||
onClick={ this.actionCustomPenLabelClick.bind(this,index, pen) }
|
||||
>
|
||||
<div
|
||||
className="ToolIcon__icon"
|
||||
@@ -157,10 +223,7 @@ export class ObsidianMenu {
|
||||
)
|
||||
}
|
||||
|
||||
private longpressTimeout : { [key: number]: number } = {};
|
||||
|
||||
renderPinnedScriptButtons = (isMobile: boolean, appState: AppState) => {
|
||||
let prevClickTimestamp = 0;
|
||||
public renderPinnedScriptButtons (isMobile: boolean, appState: AppState) {
|
||||
return (
|
||||
appState?.pinnedScripts?.map((key,index)=>{ //pinned scripts
|
||||
const scriptProp = this.plugin.scriptEngine.scriptIconMap[key];
|
||||
@@ -179,51 +242,8 @@ export class ObsidianMenu {
|
||||
"is-mobile": isMobile,
|
||||
},
|
||||
)}
|
||||
onPointerUp={() => {
|
||||
if(this.longpressTimeout[index]) {
|
||||
window.clearTimeout(this.longpressTimeout[index]);
|
||||
this.longpressTimeout[index] = 0;
|
||||
(async ()=>{
|
||||
const f = app.vault.getAbstractFileByPath(key);
|
||||
if (f && f instanceof TFile) {
|
||||
this.plugin.scriptEngine.executeScript(
|
||||
this.view,
|
||||
await app.vault.read(f),
|
||||
this.plugin.scriptEngine.getScriptName(f),
|
||||
f
|
||||
);
|
||||
}
|
||||
})()
|
||||
}
|
||||
}}
|
||||
onPointerDown={()=>{
|
||||
const now = Date.now();
|
||||
if(this.longpressTimeout[index]>0) {
|
||||
window.clearTimeout(this.longpressTimeout[index]);
|
||||
this.longpressTimeout[index] = 0;
|
||||
}
|
||||
if(now-prevClickTimestamp >= 500) {
|
||||
this.longpressTimeout[index] = window.setTimeout(
|
||||
() => {
|
||||
this.longpressTimeout[index] = 0;
|
||||
(async () =>{
|
||||
await this.plugin.loadSettings();
|
||||
const index = this.plugin.settings.pinnedScripts.indexOf(key)
|
||||
if(index > -1) {
|
||||
this.plugin.settings.pinnedScripts.splice(index,1);
|
||||
this.view.excalidrawAPI?.setToast({message:`Pin removed: ${name}`, duration: 3000, closable: true});
|
||||
}
|
||||
await this.plugin.saveSettings();
|
||||
app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> {
|
||||
if (v.view instanceof ExcalidrawView) v.view.updatePinnedScripts()
|
||||
})
|
||||
})()
|
||||
},
|
||||
1500
|
||||
)
|
||||
}
|
||||
prevClickTimestamp = now;
|
||||
}}
|
||||
onPointerUp={this.actionScriptButtonPonterUp.bind(this,index,key)}
|
||||
onPointerDown={this.actionScriptButtonPointerDown.bind(this,index,key)}
|
||||
>
|
||||
<div
|
||||
className="ToolIcon__icon"
|
||||
@@ -237,7 +257,7 @@ export class ObsidianMenu {
|
||||
)
|
||||
}
|
||||
|
||||
renderButton = (isMobile: boolean, appState: AppState) => {
|
||||
public renderButton (isMobile: boolean, appState: AppState) {
|
||||
return (
|
||||
<>
|
||||
<label
|
||||
@@ -248,12 +268,7 @@ export class ObsidianMenu {
|
||||
"is-mobile": isMobile,
|
||||
},
|
||||
)}
|
||||
onClick={() => {
|
||||
this.toolsRef.current.setTheme(appState.theme);
|
||||
this.toolsRef.current.toggleVisibility(
|
||||
appState.zenModeEnabled || isMobile,
|
||||
);
|
||||
}}
|
||||
onClick={this.actionShowHideMenu.bind(this,isMobile,appState)}
|
||||
>
|
||||
<div className="ToolIcon__icon" aria-label={t("OBSIDIAN_TOOLS_PANEL")}>
|
||||
{ICONS.obsidian}
|
||||
@@ -267,11 +282,7 @@ export class ObsidianMenu {
|
||||
"is-mobile": isMobile,
|
||||
},
|
||||
)}
|
||||
onClick={() => {
|
||||
this.view.setCurrentPositionToCenter();
|
||||
const insertFileModal = new UniversalInsertFileModal(this.plugin, this.view);
|
||||
insertFileModal.open();
|
||||
}}
|
||||
onClick={this.actionInsertAnyFile.bind(this)}
|
||||
>
|
||||
<div className="ToolIcon__icon" aria-label={t("UNIVERSAL_ADD_FILE")}>
|
||||
{ICONS["add-file"]}
|
||||
@@ -282,4 +293,19 @@ export class ObsidianMenu {
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
destroy() {
|
||||
Object.values(this.longpressTimeout).forEach(
|
||||
t=>this.view.ownerWindow.clearTimeout(t)
|
||||
);
|
||||
this.longpressTimeout = {};
|
||||
this.activePen = null;
|
||||
this.plugin = null;
|
||||
this.toolsRef = null;
|
||||
this.view = null;
|
||||
this.clickTimestamp = null;
|
||||
this.renderButton = null;
|
||||
this.renderCustomPens = null;
|
||||
this.renderPinnedScriptButtons = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,9 @@ import { isWinALTorMacOPT, isWinCTRLorMacCMD, isSHIFT } from "src/utils/Modifier
|
||||
import { InsertPDFModal } from "src/dialogs/InsertPDFModal";
|
||||
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";
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
const dark = '<svg style="stroke:#ced4da;#212529;color:#ced4da;fill:#ced4da" ';
|
||||
@@ -22,8 +25,9 @@ const light = '<svg style="stroke:#212529;color:#212529;fill:#212529" ';
|
||||
|
||||
type PanelProps = {
|
||||
visible: boolean;
|
||||
view: ExcalidrawView;
|
||||
view: WeakRef<ExcalidrawView>;
|
||||
centerPointer: Function;
|
||||
observer: WeakRef<ResizeObserver>;
|
||||
};
|
||||
|
||||
export type PanelState = {
|
||||
@@ -36,10 +40,10 @@ export type PanelState = {
|
||||
isDirty: boolean;
|
||||
isFullscreen: boolean;
|
||||
isPreviewMode: boolean;
|
||||
scriptIconMap: ScriptIconMap;
|
||||
scriptIconMap: ScriptIconMap | null;
|
||||
};
|
||||
|
||||
const TOOLS_PANEL_WIDTH = 228;
|
||||
const TOOLS_PANEL_WIDTH = () => REM_VALUE * 14.2;
|
||||
|
||||
export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
pos1: number = 0;
|
||||
@@ -53,10 +57,22 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
onRightEdge: boolean = false;
|
||||
onBottomEdge: boolean = false;
|
||||
public containerRef: React.RefObject<HTMLDivElement>;
|
||||
private view: ExcalidrawView;
|
||||
|
||||
componentWillUnmount(): void {
|
||||
if (this.containerRef.current) {
|
||||
this.props.observer.deref()?.unobserve(this.containerRef.current);
|
||||
}
|
||||
this.setState({ scriptIconMap: null });
|
||||
this.containerRef = null;
|
||||
this.view = null;
|
||||
}
|
||||
|
||||
constructor(props: PanelProps) {
|
||||
super(props);
|
||||
const react = props.view.plugin.getPackage(props.view.ownerWindow).react;
|
||||
this.view = props.view.deref();
|
||||
const react = this.view.packages.react;
|
||||
|
||||
this.containerRef = react.createRef();
|
||||
this.state = {
|
||||
visible: props.visible,
|
||||
@@ -73,12 +89,14 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
}
|
||||
|
||||
updateScriptIconMap(scriptIconMap: ScriptIconMap) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.updateScriptIconMap,"ToolsPanel.updateScriptIconMap()");
|
||||
this.setState(() => {
|
||||
return { scriptIconMap };
|
||||
});
|
||||
}
|
||||
|
||||
setPreviewMode(isPreviewMode: boolean) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.setPreviewMode,"ToolsPanel.setPreviewMode()");
|
||||
this.setState(() => {
|
||||
return {
|
||||
isPreviewMode,
|
||||
@@ -87,6 +105,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
}
|
||||
|
||||
setFullscreen(isFullscreen: boolean) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.setFullscreen,"ToolsPanel.setFullscreen()");
|
||||
this.setState(() => {
|
||||
return {
|
||||
isFullscreen,
|
||||
@@ -95,6 +114,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
}
|
||||
|
||||
setDirty(isDirty: boolean) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.setDirty,"ToolsPanel.setDirty()");
|
||||
this.setState(()=> {
|
||||
return {
|
||||
isDirty,
|
||||
@@ -103,6 +123,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
}
|
||||
|
||||
setExcalidrawViewMode(isViewModeEnabled: boolean) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.setExcalidrawViewMode,"ToolsPanel.setExcalidrawViewMode()");
|
||||
this.setState(() => {
|
||||
return {
|
||||
excalidrawViewMode: isViewModeEnabled,
|
||||
@@ -111,6 +132,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
}
|
||||
|
||||
toggleVisibility(isMobileOrZen: boolean) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.toggleVisibility,"ToolsPanel.toggleVisibility()");
|
||||
this.setTopCenter(isMobileOrZen);
|
||||
this.setState((prevState: PanelState) => {
|
||||
return {
|
||||
@@ -120,6 +142,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
}
|
||||
|
||||
setTheme(theme: "dark" | "light") {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.setTheme,"ToolsPanel.setTheme()");
|
||||
this.setState((prevState: PanelState) => {
|
||||
return {
|
||||
theme,
|
||||
@@ -128,21 +151,23 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
}
|
||||
|
||||
setTopCenter(isMobileOrZen: boolean) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.setTopCenter,"ToolsPanel.setTopCenter()");
|
||||
this.setState(() => {
|
||||
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,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
updatePosition(deltaY: number = 0, deltaX: number = 0) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.updatePosition,"ToolsPanel.updatePosition()");
|
||||
this.setState(() => {
|
||||
const {
|
||||
offsetTop,
|
||||
@@ -183,7 +208,234 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
});
|
||||
}
|
||||
|
||||
actionOpenScriptInstallDialog() {
|
||||
new ScriptInstallPrompt(this.view.plugin).open();
|
||||
}
|
||||
|
||||
actionOpenReleaseNotes() {
|
||||
new ReleaseNotes(
|
||||
this.view.app,
|
||||
this.view.plugin,
|
||||
PLUGIN_VERSION,
|
||||
).open();
|
||||
}
|
||||
|
||||
actionConvertExcalidrawToMD() {
|
||||
this.view.convertExcalidrawToMD();
|
||||
}
|
||||
|
||||
actionToggleViewMode() {
|
||||
if (this.state.isPreviewMode) {
|
||||
this.view.changeTextMode(TextMode.raw);
|
||||
} else {
|
||||
this.view.changeTextMode(TextMode.parsed);
|
||||
}
|
||||
}
|
||||
|
||||
actionToggleTrayMode() {
|
||||
this.view.toggleTrayMode();
|
||||
}
|
||||
|
||||
actionToggleFullscreen() {
|
||||
if (this.state.isFullscreen) {
|
||||
this.view.exitFullscreen();
|
||||
} else {
|
||||
this.view.gotoFullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
actionSearch() {
|
||||
search(this.view);
|
||||
}
|
||||
|
||||
actionOCR(e:React.MouseEvent<HTMLButtonElement, MouseEvent>) {
|
||||
if(!this.view.plugin.settings.taskboneEnabled) {
|
||||
new Notice("Taskbone OCR is not enabled. Please go to plugins settings to enable it.",4000);
|
||||
return;
|
||||
}
|
||||
this.view.plugin.taskbone.getTextForView(this.view, {forceReScan: isWinCTRLorMacCMD(e)});
|
||||
}
|
||||
|
||||
actionOpenLink(e:React.MouseEvent<HTMLButtonElement, MouseEvent>) {
|
||||
const event = new MouseEvent("click", {
|
||||
ctrlKey: e.ctrlKey || !(DEVICE.isIOS || DEVICE.isMacOS),
|
||||
metaKey: e.metaKey || (DEVICE.isIOS || DEVICE.isMacOS),
|
||||
shiftKey: e.shiftKey,
|
||||
altKey: e.altKey,
|
||||
});
|
||||
this.view.handleLinkClick(event);
|
||||
}
|
||||
|
||||
actionOpenLinkProperties() {
|
||||
const event = new MouseEvent("click", {
|
||||
ctrlKey: true,
|
||||
metaKey: true,
|
||||
shiftKey: false,
|
||||
altKey: false,
|
||||
});
|
||||
this.view.handleLinkClick(event);
|
||||
}
|
||||
|
||||
actionForceSave() {
|
||||
this.view.forceSave();
|
||||
}
|
||||
|
||||
actionExportLibrary() {
|
||||
this.view.plugin.exportLibrary();
|
||||
}
|
||||
|
||||
actionExportImage() {
|
||||
const view = this.view;
|
||||
if(!view.exportDialog) {
|
||||
view.exportDialog = new ExportDialog(view.plugin, view,view.file);
|
||||
view.exportDialog.createForm();
|
||||
}
|
||||
view.exportDialog.open();
|
||||
}
|
||||
|
||||
actionOpenAsMarkdown() {
|
||||
this.view.openAsMarkdown();
|
||||
}
|
||||
|
||||
actionLinkToElement(e:React.MouseEvent<HTMLButtonElement, MouseEvent>) {
|
||||
if(isWinALTorMacOPT(e)) {
|
||||
openExternalLink("https://youtu.be/yZQoJg2RCKI", this.view.app);
|
||||
return;
|
||||
}
|
||||
this.view.copyLinkToSelectedElementToClipboard(
|
||||
isWinCTRLorMacCMD(e) ? "group=" : (isSHIFT(e) ? "area=" : "")
|
||||
);
|
||||
}
|
||||
|
||||
actionAddAnyFile() {
|
||||
this.props.centerPointer();
|
||||
const insertFileModal = new UniversalInsertFileModal(this.view.plugin, this.view);
|
||||
insertFileModal.open();
|
||||
}
|
||||
|
||||
actionInsertImage() {
|
||||
this.props.centerPointer();
|
||||
this.view.plugin.insertImageDialog.start(
|
||||
this.view,
|
||||
);
|
||||
}
|
||||
|
||||
actionInsertPDF() {
|
||||
this.props.centerPointer();
|
||||
const insertPDFModal = new InsertPDFModal(this.view.plugin, this.view);
|
||||
insertPDFModal.open();
|
||||
}
|
||||
|
||||
actionInsertMarkdown() {
|
||||
this.props.centerPointer();
|
||||
this.view.plugin.insertMDDialog.start(
|
||||
this.view,
|
||||
);
|
||||
}
|
||||
|
||||
actionInsertBackOfNote() {
|
||||
this.props.centerPointer();
|
||||
this.view.insertBackOfTheNoteCard();
|
||||
}
|
||||
|
||||
actionInsertLaTeX(e:React.MouseEvent<HTMLButtonElement, MouseEvent>) {
|
||||
if(isWinALTorMacOPT(e)) {
|
||||
openExternalLink("https://youtu.be/r08wk-58DPk", this.view.app);
|
||||
return;
|
||||
}
|
||||
this.props.centerPointer();
|
||||
insertLaTeXToView(this.view);
|
||||
}
|
||||
|
||||
actionInsertLink() {
|
||||
this.props.centerPointer();
|
||||
this.view.plugin.insertLinkDialog.start(
|
||||
this.view.file.path,
|
||||
(text: string, fontFamily?: 1 | 2 | 3 | 4, save?: boolean) => this.view.addText (text, fontFamily, save),
|
||||
);
|
||||
}
|
||||
|
||||
actionImportSVG(e:React.MouseEvent<HTMLButtonElement, MouseEvent>) {
|
||||
this.view.plugin.importSVGDialog.start(this.view);
|
||||
}
|
||||
|
||||
actionCropImage(e:React.MouseEvent<HTMLButtonElement, MouseEvent>) {
|
||||
// @ts-ignore
|
||||
this.view.app.commands.executeCommandById("obsidian-excalidraw-plugin:crop-image");
|
||||
}
|
||||
|
||||
async actionRunScript(key: string) {
|
||||
const view = this.view;
|
||||
const plugin = view.plugin;
|
||||
const f = app.vault.getAbstractFileByPath(key);
|
||||
if (f && f instanceof TFile) {
|
||||
plugin.scriptEngine.executeScript(
|
||||
view,
|
||||
await app.vault.read(f),
|
||||
plugin.scriptEngine.getScriptName(f),
|
||||
f
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async actionPinScript(key: string, scriptName: string) {
|
||||
const view = this.view;
|
||||
const api = view.excalidrawAPI as ExcalidrawImperativeAPI;
|
||||
const plugin = view.plugin;
|
||||
await plugin.loadSettings();
|
||||
const index = plugin.settings.pinnedScripts.indexOf(key)
|
||||
if(index > -1) {
|
||||
plugin.settings.pinnedScripts.splice(index,1);
|
||||
api?.setToast({message:`Pin removed: ${scriptName}`, duration: 3000, closable: true});
|
||||
} else {
|
||||
plugin.settings.pinnedScripts.push(key);
|
||||
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()
|
||||
})
|
||||
}
|
||||
|
||||
private islandOnClick(event: React.MouseEvent<HTMLDivElement, MouseEvent>) {
|
||||
event.preventDefault();
|
||||
if (
|
||||
Math.abs(this.penDownX - this.pos3) > 5 ||
|
||||
Math.abs(this.penDownY - this.pos4) > 5
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.setState((prevState: PanelState) => {
|
||||
return {
|
||||
minimized: !prevState.minimized,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private islandOnPointerDown(event: React.PointerEvent) {
|
||||
const onDrag = (e: PointerEvent) => {
|
||||
e.preventDefault();
|
||||
this.pos1 = this.pos3 - e.clientX;
|
||||
this.pos2 = this.pos4 - e.clientY;
|
||||
this.pos3 = e.clientX;
|
||||
this.pos4 = e.clientY;
|
||||
this.updatePosition(this.pos2, this.pos1);
|
||||
};
|
||||
|
||||
const onPointerUp = () => {
|
||||
this.view.ownerDocument?.removeEventListener("pointerup", onPointerUp);
|
||||
this.view.ownerDocument?.removeEventListener("pointermove", onDrag);
|
||||
};
|
||||
|
||||
event.preventDefault();
|
||||
this.penDownX = this.pos3 = event.clientX;
|
||||
this.penDownY = this.pos4 = event.clientY;
|
||||
this.view.ownerDocument.addEventListener("pointerup", onPointerUp);
|
||||
this.view.ownerDocument.addEventListener("pointermove", onDrag);
|
||||
};
|
||||
|
||||
render() {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.render,"ToolsPanel.render()");
|
||||
return (
|
||||
<div
|
||||
ref={this.containerRef}
|
||||
@@ -202,7 +454,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: `13.75rem`,
|
||||
display:
|
||||
this.state.visible && !this.state.excalidrawViewMode
|
||||
? "block"
|
||||
@@ -218,41 +470,8 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
width: "100%",
|
||||
cursor: "move",
|
||||
}}
|
||||
onClick={(event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
event.preventDefault();
|
||||
if (
|
||||
Math.abs(this.penDownX - this.pos3) > 5 ||
|
||||
Math.abs(this.penDownY - this.pos4) > 5
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.setState((prevState: PanelState) => {
|
||||
return {
|
||||
minimized: !prevState.minimized,
|
||||
};
|
||||
});
|
||||
}}
|
||||
onPointerDown={(event: React.PointerEvent) => {
|
||||
const onDrag = (e: PointerEvent) => {
|
||||
e.preventDefault();
|
||||
this.pos1 = this.pos3 - e.clientX;
|
||||
this.pos2 = this.pos4 - e.clientY;
|
||||
this.pos3 = e.clientX;
|
||||
this.pos4 = e.clientY;
|
||||
this.updatePosition(this.pos2, this.pos1);
|
||||
};
|
||||
|
||||
const onPointerUp = () => {
|
||||
this.props.view.ownerDocument?.removeEventListener("pointerup", onPointerUp);
|
||||
this.props.view.ownerDocument?.removeEventListener("pointermove", onDrag);
|
||||
};
|
||||
|
||||
event.preventDefault();
|
||||
this.penDownX = this.pos3 = event.clientX;
|
||||
this.penDownY = this.pos4 = event.clientY;
|
||||
this.props.view.ownerDocument.addEventListener("pointerup", onPointerUp);
|
||||
this.props.view.ownerDocument.addEventListener("pointermove", onDrag);
|
||||
}}
|
||||
onClick={this.islandOnClick.bind(this)}
|
||||
onPointerDown={this.islandOnPointerDown.bind(this)}
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
@@ -285,62 +504,39 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
<ActionButton
|
||||
key={"scriptEngine"}
|
||||
title={t("INSTALL_SCRIPT_BUTTON")}
|
||||
action={() => {
|
||||
new ScriptInstallPrompt(this.props.view.plugin).open();
|
||||
}}
|
||||
action={this.actionOpenScriptInstallDialog.bind(this)}
|
||||
icon={ICONS.scriptEngine}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"release-notes"}
|
||||
title={t("READ_RELEASE_NOTES")}
|
||||
action={() => {
|
||||
new ReleaseNotes(
|
||||
this.props.view.app,
|
||||
this.props.view.plugin,
|
||||
PLUGIN_VERSION,
|
||||
).open();
|
||||
}}
|
||||
action={this.actionOpenReleaseNotes.bind(this)}
|
||||
icon={ICONS.releaseNotes}
|
||||
view={this.props.view}
|
||||
/>
|
||||
{this.state.isPreviewMode === null ? (
|
||||
<ActionButton
|
||||
key={"convert"}
|
||||
title={t("CONVERT_FILE")}
|
||||
action={() => {
|
||||
this.props.view.convertExcalidrawToMD();
|
||||
}}
|
||||
action={(this.actionConvertExcalidrawToMD.bind(this))}
|
||||
icon={ICONS.convertFile}
|
||||
view={this.props.view}
|
||||
/>
|
||||
) : (
|
||||
<ActionButton
|
||||
key={"viewmode"}
|
||||
title={this.state.isPreviewMode ? t("PARSED") : t("RAW")}
|
||||
action={() => {
|
||||
if (this.state.isPreviewMode) {
|
||||
this.props.view.changeTextMode(TextMode.raw);
|
||||
} else {
|
||||
this.props.view.changeTextMode(TextMode.parsed);
|
||||
}
|
||||
}}
|
||||
action={this.actionToggleViewMode.bind(this)}
|
||||
icon={
|
||||
this.state.isPreviewMode
|
||||
? ICONS.rawMode
|
||||
: ICONS.parsedMode
|
||||
}
|
||||
view={this.props.view}
|
||||
/>
|
||||
)}
|
||||
<ActionButton
|
||||
key={"tray-mode"}
|
||||
title={t("TRAY_MODE")}
|
||||
action={() => {
|
||||
this.props.view.toggleTrayMode();
|
||||
}}
|
||||
action={this.actionToggleTrayMode.bind(this)}
|
||||
icon={ICONS.trayMode}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"fullscreen"}
|
||||
@@ -349,80 +545,42 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
? t("EXIT_FULLSCREEN")
|
||||
: t("GOTO_FULLSCREEN")
|
||||
}
|
||||
action={() => {
|
||||
if (this.state.isFullscreen) {
|
||||
this.props.view.exitFullscreen();
|
||||
} else {
|
||||
this.props.view.gotoFullscreen();
|
||||
}
|
||||
}}
|
||||
action={this.actionToggleFullscreen.bind(this)}
|
||||
icon={
|
||||
this.state.isFullscreen
|
||||
? ICONS.exitFullScreen
|
||||
: ICONS.gotoFullScreen
|
||||
}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"search"}
|
||||
title={t("SEARCH")}
|
||||
action={() => {
|
||||
search(this.props.view);
|
||||
}}
|
||||
action={this.actionSearch.bind(this)}
|
||||
icon={ICONS.search}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"ocr"}
|
||||
title={t("RUN_OCR")}
|
||||
action={(e:React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
if(!this.props.view.plugin.settings.taskboneEnabled) {
|
||||
new Notice("Taskbone OCR is not enabled. Please go to plugins settings to enable it.",4000);
|
||||
return;
|
||||
}
|
||||
this.props.view.plugin.taskbone.getTextForView(this.props.view, isWinCTRLorMacCMD(e));
|
||||
}}
|
||||
action={this.actionOCR.bind(this)}
|
||||
icon={ICONS.ocr}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"openLink"}
|
||||
title={t("OPEN_LINK_CLICK")}
|
||||
action={(e) => {
|
||||
const event = new MouseEvent("click", {
|
||||
ctrlKey: e.ctrlKey || !(DEVICE.isIOS || DEVICE.isMacOS),
|
||||
metaKey: e.metaKey || (DEVICE.isIOS || DEVICE.isMacOS),
|
||||
shiftKey: e.shiftKey,
|
||||
altKey: e.altKey,
|
||||
});
|
||||
this.props.view.handleLinkClick(event);
|
||||
}}
|
||||
action={this.actionOpenLink.bind(this)}
|
||||
icon={ICONS.openLink}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"openLinkProperties"}
|
||||
title={t("OPEN_LINK_PROPS")}
|
||||
action={() => {
|
||||
const event = new MouseEvent("click", {
|
||||
ctrlKey: true,
|
||||
metaKey: true,
|
||||
shiftKey: false,
|
||||
altKey: false,
|
||||
});
|
||||
this.props.view.handleLinkClick(event);
|
||||
}}
|
||||
action={this.actionOpenLinkProperties.bind(this)}
|
||||
icon={ICONS.openLinkProperties}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"save"}
|
||||
title={t("FORCE_SAVE")}
|
||||
action={() => {
|
||||
this.props.view.forceSave();
|
||||
}}
|
||||
action={this.actionForceSave.bind(this)}
|
||||
icon={saveIcon(this.state.isDirty)}
|
||||
view={this.props.view}
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -432,135 +590,85 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
<ActionButton
|
||||
key={"lib"}
|
||||
title={t("DOWNLOAD_LIBRARY")}
|
||||
action={() => {
|
||||
this.props.view.plugin.exportLibrary();
|
||||
}}
|
||||
action={this.actionExportLibrary.bind(this)}
|
||||
icon={ICONS.exportLibrary}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"exportIMG"}
|
||||
title={t("EXPORT_IMAGE")}
|
||||
action={() => {
|
||||
const view = this.props.view;
|
||||
if(!view.exportDialog) {
|
||||
view.exportDialog = new ExportDialog(view.plugin, view,view.file);
|
||||
view.exportDialog.createForm();
|
||||
}
|
||||
view.exportDialog.open();
|
||||
}}
|
||||
action={this.actionExportImage.bind(this)}
|
||||
icon={ICONS.ExportImage}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"md"}
|
||||
title={t("OPEN_AS_MD")}
|
||||
action={() => {
|
||||
this.props.view.openAsMarkdown();
|
||||
}}
|
||||
action={this.actionOpenAsMarkdown.bind(this)}
|
||||
icon={ICONS.switchToMarkdown}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"link-to-element"}
|
||||
title={t("INSERT_LINK_TO_ELEMENT")}
|
||||
action={this.actionLinkToElement.bind(this)}
|
||||
icon={ICONS.copyElementLink}
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Insert actions</legend>
|
||||
<div className="buttonList buttonListIcon">
|
||||
<ActionButton
|
||||
key={"anyfile"}
|
||||
title={t("UNIVERSAL_ADD_FILE")}
|
||||
action={this.actionAddAnyFile.bind(this)}
|
||||
icon={ICONS["add-file"]}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"image"}
|
||||
title={t("INSERT_IMAGE")}
|
||||
action={() => {
|
||||
this.props.centerPointer();
|
||||
this.props.view.plugin.insertImageDialog.start(
|
||||
this.props.view,
|
||||
);
|
||||
}}
|
||||
action={this.actionInsertImage.bind(this)}
|
||||
icon={ICONS.insertImage}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"pdf"}
|
||||
title={t("INSERT_PDF")}
|
||||
action={() => {
|
||||
this.props.centerPointer();
|
||||
const insertPDFModal = new InsertPDFModal(this.props.view.plugin, this.props.view);
|
||||
insertPDFModal.open();
|
||||
}}
|
||||
action={this.actionInsertPDF.bind(this)}
|
||||
icon={ICONS.insertPDF}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"insertMD"}
|
||||
title={t("INSERT_MD")}
|
||||
action={() => {
|
||||
this.props.centerPointer();
|
||||
this.props.view.plugin.insertMDDialog.start(
|
||||
this.props.view,
|
||||
);
|
||||
}}
|
||||
action={this.actionInsertMarkdown.bind(this)}
|
||||
icon={ICONS.insertMD}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"insertBackOfNote"}
|
||||
title={t("INSERT_CARD")}
|
||||
action={this.actionInsertBackOfNote.bind(this)}
|
||||
icon={ICONS.BackOfNote}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"latex"}
|
||||
title={t("INSERT_LATEX")}
|
||||
action={(e) => {
|
||||
if(isWinALTorMacOPT(e)) {
|
||||
openExternalLink("https://youtu.be/r08wk-58DPk", this.props.view.app);
|
||||
return;
|
||||
}
|
||||
this.props.centerPointer();
|
||||
insertLaTeXToView(this.props.view);
|
||||
}}
|
||||
action={this.actionInsertLaTeX.bind(this)}
|
||||
icon={ICONS.insertLaTeX}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"link"}
|
||||
title={t("INSERT_LINK")}
|
||||
action={() => {
|
||||
this.props.centerPointer();
|
||||
this.props.view.plugin.insertLinkDialog.start(
|
||||
this.props.view.file.path,
|
||||
(text: string, fontFamily?: 1 | 2 | 3 | 4, save?: boolean) => this.props.view.addText (text, fontFamily, save),
|
||||
);
|
||||
}}
|
||||
action={this.actionInsertLink.bind(this)}
|
||||
icon={ICONS.insertLink}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"link-to-element"}
|
||||
title={t("INSERT_LINK_TO_ELEMENT")}
|
||||
action={(e:React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
if(isWinALTorMacOPT(e)) {
|
||||
openExternalLink("https://youtu.be/yZQoJg2RCKI", this.props.view.app);
|
||||
return;
|
||||
}
|
||||
this.props.view.copyLinkToSelectedElementToClipboard(
|
||||
isWinCTRLorMacCMD(e) ? "group=" : (isSHIFT(e) ? "area=" : "")
|
||||
);
|
||||
}}
|
||||
icon={ICONS.copyElementLink}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"import-svg"}
|
||||
title={t("IMPORT_SVG")}
|
||||
action={(e:React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
this.props.view.plugin.importSVGDialog.start(this.props.view);
|
||||
}}
|
||||
action={this.actionImportSVG.bind(this)}
|
||||
icon={ICONS.importSVG}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"crop-image"}
|
||||
title={t("CROP_IMAGE")}
|
||||
action={(e:React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
// @ts-ignore
|
||||
this.props.view.app.commands.executeCommandById("obsidian-excalidraw-plugin:crop-image")
|
||||
}}
|
||||
action={this.actionCropImage.bind(this)}
|
||||
icon={ICONS.Crop}
|
||||
view={this.props.view}
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -574,11 +682,12 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
}
|
||||
|
||||
private renderScriptButtons(isDownloaded: boolean) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.renderScriptButtons,"ToolsPanel.renderScriptButtons()");
|
||||
if (Object.keys(this.state.scriptIconMap).length === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const downloadedScriptsRoot = `${this.props.view.plugin.settings.scriptFolderPath}/${SCRIPT_INSTALL_FOLDER}/`;
|
||||
const downloadedScriptsRoot = `${this.view.plugin.settings.scriptFolderPath}/${SCRIPT_INSTALL_FOLDER}/`;
|
||||
|
||||
const filterCondition = (key: string): boolean =>
|
||||
isDownloaded
|
||||
@@ -613,45 +722,15 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
<ActionButton
|
||||
key={key}
|
||||
title={value.name}
|
||||
action={async () => {
|
||||
const view = this.props.view;
|
||||
const plugin = view.plugin;
|
||||
const f = app.vault.getAbstractFileByPath(key);
|
||||
if (f && f instanceof TFile) {
|
||||
plugin.scriptEngine.executeScript(
|
||||
view,
|
||||
await app.vault.read(f),
|
||||
plugin.scriptEngine.getScriptName(f),
|
||||
f
|
||||
);
|
||||
}
|
||||
}}
|
||||
longpress={async () => {
|
||||
const view = this.props.view;
|
||||
const api = view.excalidrawAPI as ExcalidrawImperativeAPI;
|
||||
const plugin = view.plugin;
|
||||
await plugin.loadSettings();
|
||||
const index = plugin.settings.pinnedScripts.indexOf(key)
|
||||
if(index > -1) {
|
||||
plugin.settings.pinnedScripts.splice(index,1);
|
||||
api?.setToast({message:`Pin removed: ${value.name}`, duration: 3000, closable: true});
|
||||
} else {
|
||||
plugin.settings.pinnedScripts.push(key);
|
||||
api?.setToast({message:`Pinned: ${value.name}`, duration: 3000, closable: true})
|
||||
}
|
||||
await plugin.saveSettings();
|
||||
app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> {
|
||||
if (v.view instanceof ExcalidrawView) v.view.updatePinnedScripts()
|
||||
})
|
||||
}}
|
||||
action={this.actionRunScript.bind(this,key)}
|
||||
longpress={this.actionPinScript.bind(this,key, value.name)}
|
||||
icon={
|
||||
value.svgString
|
||||
new WeakRef(value.svgString
|
||||
? stringToSVG(value.svgString)
|
||||
: (
|
||||
ICONS.cog
|
||||
)
|
||||
)).deref()
|
||||
}
|
||||
view={this.props.view}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { createPNG, ExcalidrawAutomate } from "../ExcalidrawAutomate";
|
||||
import { ExcalidrawAutomate, createPNG } from "../ExcalidrawAutomate";
|
||||
import {Notice, requestUrl} from "obsidian"
|
||||
import ExcalidrawPlugin from "../main"
|
||||
import {log} from "../utils/Utils"
|
||||
import ExcalidrawView, { ExportSettings } from "../ExcalidrawView"
|
||||
import FrontmatterEditor from "src/utils/Frontmatter";
|
||||
import { ExcalidrawElement, ExcalidrawImageElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ExcalidrawElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { EmbeddedFilesLoader } from "src/EmbeddedFileLoader";
|
||||
import { blobToBase64 } from "src/utils/FileUtils";
|
||||
import { getEA } from "src";
|
||||
import { log } from "src/utils/DebugHelper";
|
||||
|
||||
const TASKBONE_URL = "https://api.taskbone.com/"; //"https://excalidraw-preview.onrender.com/";
|
||||
const TASKBONE_OCR_FN = "execute?id=60f394af-85f6-40bc-9613-5d26dc283cbb";
|
||||
@@ -21,6 +22,10 @@ export default class Taskbone {
|
||||
) {
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.plugin = null;
|
||||
}
|
||||
|
||||
public async initialize(save:boolean = true):Promise<string> {
|
||||
if(this.plugin.settings.taskboneAPIkey !== "") return;
|
||||
const response = await requestUrl({
|
||||
@@ -39,23 +44,9 @@ export default class Taskbone {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
public async getTextForView(view: ExcalidrawView, forceReScan: boolean) {
|
||||
await view.forceSave(true);
|
||||
const viewElements = view.excalidrawAPI.getSceneElements().filter((el:ExcalidrawElement) =>
|
||||
el.type==="freedraw" ||
|
||||
( el.type==="image" &&
|
||||
!this.plugin.isExcalidrawFile(view.excalidrawData.getFile(el.fileId)?.file)
|
||||
));
|
||||
if(viewElements.length === 0) {
|
||||
new Notice ("Aborting OCR because there are no image or freedraw elements on the canvas.",4000);
|
||||
return;
|
||||
}
|
||||
const fe = new FrontmatterEditor(view.data);
|
||||
if(fe.hasKey("taskbone-ocr") && !forceReScan) {
|
||||
new Notice ("The drawing has already been processed, you will find the result in the frontmatter in markdown view mode. If you ran the command from the Obsidian Panel in Excalidraw then you can CTRL(CMD)+click the command to force the rescaning.",4000)
|
||||
return;
|
||||
}
|
||||
const bb = this.plugin.ea.getBoundingBox(viewElements);
|
||||
public async getTextForElements(elements: ExcalidrawElement[], ea: ExcalidrawAutomate): Promise<string> {
|
||||
ea.copyViewElementsToEAforEditing(elements, true);
|
||||
const bb = ea.getBoundingBox(elements);
|
||||
const size = (bb.width*bb.height);
|
||||
const minRatio = Math.sqrt(360000/size);
|
||||
const maxRatio = Math.sqrt(size/16000000);
|
||||
@@ -79,27 +70,56 @@ export default class Taskbone {
|
||||
};
|
||||
|
||||
const img =
|
||||
await createPNG(
|
||||
view.file.path + "#^taskbone",
|
||||
await ea.createPNG(
|
||||
null,
|
||||
scale,
|
||||
exportSettings,
|
||||
loader,
|
||||
"light",
|
||||
null,
|
||||
null,
|
||||
[],
|
||||
this.plugin,
|
||||
0
|
||||
);
|
||||
)
|
||||
return await this.getTextForImage(img);
|
||||
}
|
||||
|
||||
const text = await this.getTextForImage(img);
|
||||
if(text) {
|
||||
fe.setKey("taskbone-ocr",text);
|
||||
view.data = fe.data;
|
||||
view.save(false);
|
||||
window.navigator.clipboard.writeText(text);
|
||||
new Notice("I placed the recognized in the drawing's frontmatter and onto the system clipboard.");
|
||||
public async getTextForView(view: ExcalidrawView, {
|
||||
forceReScan,
|
||||
selectedElementsOnly = false,
|
||||
addToFrontmatter = true,
|
||||
}: {
|
||||
forceReScan: boolean,
|
||||
selectedElementsOnly?: boolean,
|
||||
addToFrontmatter?: boolean,
|
||||
}) {
|
||||
await view.forceSave(true);
|
||||
const ea = getEA(view) as ExcalidrawAutomate;
|
||||
const viewElements = (selectedElementsOnly ? ea.getViewSelectedElements() : ea.getViewElements())
|
||||
.filter((el:ExcalidrawElement) =>
|
||||
el.type==="freedraw" ||
|
||||
( el.type==="image" &&
|
||||
!this.plugin.isExcalidrawFile(view.excalidrawData.getFile(el.fileId)?.file)
|
||||
));
|
||||
if(viewElements.length === 0) {
|
||||
new Notice ("Aborting OCR because there are no image or freedraw elements on the canvas.",4000);
|
||||
ea.destroy();
|
||||
return;
|
||||
}
|
||||
const fe = new FrontmatterEditor(view.data);
|
||||
if(addToFrontmatter && fe.hasKey("taskbone-ocr") && !forceReScan) {
|
||||
new Notice ("The drawing has already been processed, you will find the result in the frontmatter in markdown view mode. If you ran the command from the Obsidian Panel in Excalidraw then you can CTRL(CMD)+click the command to force the rescaning.",4000)
|
||||
ea.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
const text = await this.getTextForElements(viewElements, ea);
|
||||
if(text) {
|
||||
if(addToFrontmatter) {
|
||||
fe.setKey("taskbone-ocr",text);
|
||||
view.data = fe.data;
|
||||
view.save(false);
|
||||
}
|
||||
window.navigator.clipboard.writeText(text);
|
||||
new Notice(`I placed the recognized text onto the system clipboard${addToFrontmatter?" and to document properties":""}.`);
|
||||
}
|
||||
ea.destroy();
|
||||
}
|
||||
|
||||
private async getTextForImage(image: Blob):Promise<string> {
|
||||
@@ -114,7 +134,18 @@ export default class Taskbone {
|
||||
}]
|
||||
};
|
||||
|
||||
const apiResponse = await requestUrl ({
|
||||
const apiResponse = await fetch(url,{
|
||||
method: "post",
|
||||
//@ts-ignore
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify(input),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
authorization: `Bearer ${this.apiKey}`
|
||||
}});
|
||||
const content = await apiResponse?.json();
|
||||
|
||||
/*const apiResponse = await requestUrl ({
|
||||
url: url,
|
||||
method: "post",
|
||||
contentType: "application/json",
|
||||
@@ -124,7 +155,7 @@ export default class Taskbone {
|
||||
},
|
||||
throw: false
|
||||
});
|
||||
const content = apiResponse?.json;
|
||||
const content = apiResponse?.json;*/
|
||||
|
||||
if(!content || apiResponse.status !== 200) {
|
||||
new Notice("Something went wrong while processing your request. Please check developer console for more information");
|
||||
|
||||
548
src/settings.ts
@@ -2,6 +2,8 @@ import {
|
||||
App,
|
||||
ButtonComponent,
|
||||
DropdownComponent,
|
||||
getIcon,
|
||||
Modifier,
|
||||
normalizePath,
|
||||
PluginSettingTab,
|
||||
Setting,
|
||||
@@ -13,7 +15,7 @@ import ExcalidrawView from "./ExcalidrawView";
|
||||
import { t } from "./lang/helpers";
|
||||
import type ExcalidrawPlugin from "./main";
|
||||
import { PenStyle } from "./PenTypes";
|
||||
import { DynamicStyle } from "./types";
|
||||
import { DynamicStyle } from "./types/types";
|
||||
import { PreviewImageType } from "./utils/UtilTypes";
|
||||
import { setDynamicStyle } from "./utils/DynamicStyling";
|
||||
import {
|
||||
@@ -33,17 +35,25 @@ import { EmbeddalbeMDFileCustomDataSettingsComponent } from "./dialogs/Embeddabl
|
||||
import { startupScript } from "./constants/starutpscript";
|
||||
import { ModifierKeySet, ModifierSetType } from "./utils/ModifierkeyHelper";
|
||||
import { ModifierKeySettingsComponent } from "./dialogs/ModifierKeySettings";
|
||||
import { CROPPED_PREFIX } from "./utils/CarveOut";
|
||||
import { ANNOTATED_PREFIX, CROPPED_PREFIX } from "./utils/CarveOut";
|
||||
import { EDITOR_FADEOUT } from "./CodeMirrorExtension/EditorHandler";
|
||||
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";
|
||||
|
||||
export interface ExcalidrawSettings {
|
||||
folder: string;
|
||||
cropFolder: string;
|
||||
annotateFolder: string;
|
||||
embedUseExcalidrawFolder: boolean;
|
||||
templateFilePath: string;
|
||||
scriptFolderPath: string;
|
||||
compress: boolean;
|
||||
decompressForMDView: boolean;
|
||||
onceOffCompressFlagReset: boolean; //used to reset compress to true in 2.2.0
|
||||
onceOffGPTVersionReset: boolean; //used to reset GPT version in 2.2.11
|
||||
autosave: boolean;
|
||||
autosaveInterval: number;
|
||||
autosaveIntervalDesktop: number;
|
||||
autosaveIntervalMobile: number;
|
||||
drawingFilenamePrefix: string;
|
||||
@@ -52,12 +62,16 @@ export interface ExcalidrawSettings {
|
||||
drawingFilenameDateTime: string;
|
||||
useExcalidrawExtension: boolean;
|
||||
cropPrefix: string;
|
||||
annotatePrefix: string;
|
||||
annotatePreserveSize: boolean;
|
||||
displaySVGInPreview: boolean; //No longer used since 1.9.13
|
||||
previewImageType: PreviewImageType; //Introduced with 1.9.13
|
||||
allowImageCache: boolean;
|
||||
allowImageCacheInScene: boolean;
|
||||
displayExportedImageIfAvailable: boolean;
|
||||
previewMatchObsidianTheme: boolean;
|
||||
width: string;
|
||||
height: string;
|
||||
dynamicStyling: DynamicStyle;
|
||||
isLeftHanded: boolean;
|
||||
iframeMatchExcalidrawTheme: boolean;
|
||||
@@ -66,12 +80,19 @@ export interface ExcalidrawSettings {
|
||||
matchThemeTrigger: boolean;
|
||||
defaultMode: string;
|
||||
defaultPenMode: "never" | "mobile" | "always";
|
||||
penModeDoubleTapEraser: boolean;
|
||||
penModeCrosshairVisible: boolean;
|
||||
renderImageInMarkdownReadingMode: boolean,
|
||||
renderImageInHoverPreviewForMDNotes: boolean,
|
||||
renderImageInMarkdownToPDF: boolean,
|
||||
allowPinchZoom: boolean;
|
||||
allowWheelZoom: boolean;
|
||||
zoomToFitOnOpen: boolean;
|
||||
zoomToFitOnResize: boolean;
|
||||
zoomToFitMaxLevel: number;
|
||||
openInAdjacentPane: boolean;
|
||||
showSecondOrderLinks: boolean;
|
||||
focusOnFileTab: boolean;
|
||||
openInMainWorkspace: boolean;
|
||||
showLinkBrackets: boolean;
|
||||
linkPrefix: string;
|
||||
@@ -91,6 +112,7 @@ export interface ExcalidrawSettings {
|
||||
exportWithTheme: boolean;
|
||||
exportWithBackground: boolean;
|
||||
exportPaddingSVG: number;
|
||||
exportEmbedScene: boolean;
|
||||
keepInSync: boolean;
|
||||
autoexportSVG: boolean;
|
||||
autoexportPNG: boolean;
|
||||
@@ -104,8 +126,12 @@ export interface ExcalidrawSettings {
|
||||
experimentalFileType: boolean;
|
||||
experimentalFileTag: string;
|
||||
experimentalLivePreview: boolean;
|
||||
fadeOutExcalidrawMarkup: boolean;
|
||||
loadPropertySuggestions: boolean;
|
||||
experimentalEnableFourthFont: boolean;
|
||||
experimantalFourthFont: string;
|
||||
addDummyTextElement: boolean;
|
||||
zoteroCompatibility: boolean;
|
||||
fieldSuggester: boolean;
|
||||
//loadCount: number; //version 1.2 migration counter
|
||||
drawingOpenCount: number;
|
||||
@@ -145,6 +171,7 @@ export interface ExcalidrawSettings {
|
||||
numberOfCustomPens: number;
|
||||
pdfScale: number;
|
||||
pdfBorderBox: boolean;
|
||||
pdfFrame: boolean;
|
||||
pdfGapSize: number;
|
||||
pdfGroupPages: boolean;
|
||||
pdfLockAfterImport: boolean;
|
||||
@@ -158,6 +185,7 @@ export interface ExcalidrawSettings {
|
||||
COLOR: string,
|
||||
};
|
||||
embeddableMarkdownDefaults: EmbeddableMDCustomProps;
|
||||
markdownNodeOneClickEditing: boolean;
|
||||
canvasImmersiveEmbed: boolean,
|
||||
startupScriptPath: string,
|
||||
openAIAPIToken: string,
|
||||
@@ -174,6 +202,12 @@ export interface ExcalidrawSettings {
|
||||
},
|
||||
slidingPanesSupport: boolean;
|
||||
areaZoomLimit: number;
|
||||
longPressDesktop: number;
|
||||
longPressMobile: number;
|
||||
isDebugMode: boolean;
|
||||
rank: Rank;
|
||||
modifierKeyOverrides: {modifiers: Modifier[], key: string}[];
|
||||
showSplashscreen: boolean;
|
||||
}
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
@@ -181,26 +215,33 @@ declare const PLUGIN_VERSION:string;
|
||||
export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
folder: "Excalidraw",
|
||||
cropFolder: "",
|
||||
annotateFolder: "",
|
||||
embedUseExcalidrawFolder: false,
|
||||
templateFilePath: "Excalidraw/Template.excalidraw",
|
||||
scriptFolderPath: "Excalidraw/Scripts",
|
||||
compress: false,
|
||||
compress: true,
|
||||
decompressForMDView: false,
|
||||
onceOffCompressFlagReset: false,
|
||||
onceOffGPTVersionReset: false,
|
||||
autosave: true,
|
||||
autosaveInterval: 15000,
|
||||
autosaveIntervalDesktop: 15000,
|
||||
autosaveIntervalMobile: 10000,
|
||||
autosaveIntervalDesktop: 30000,
|
||||
autosaveIntervalMobile: 20000,
|
||||
drawingFilenamePrefix: "Drawing ",
|
||||
drawingEmbedPrefixWithFilename: true,
|
||||
drawingFilnameEmbedPostfix: " ",
|
||||
drawingFilenameDateTime: "YYYY-MM-DD HH.mm.ss",
|
||||
useExcalidrawExtension: true,
|
||||
cropPrefix: CROPPED_PREFIX,
|
||||
annotatePrefix: ANNOTATED_PREFIX,
|
||||
annotatePreserveSize: false,
|
||||
displaySVGInPreview: undefined,
|
||||
previewImageType: undefined,
|
||||
allowImageCache: true,
|
||||
allowImageCacheInScene: true,
|
||||
displayExportedImageIfAvailable: false,
|
||||
previewMatchObsidianTheme: false,
|
||||
width: "400",
|
||||
height: "",
|
||||
dynamicStyling: "colorful",
|
||||
isLeftHanded: false,
|
||||
iframeMatchExcalidrawTheme: true,
|
||||
@@ -209,6 +250,11 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
matchThemeTrigger: false,
|
||||
defaultMode: "normal",
|
||||
defaultPenMode: "never",
|
||||
penModeDoubleTapEraser: true,
|
||||
penModeCrosshairVisible: true,
|
||||
renderImageInMarkdownReadingMode: false,
|
||||
renderImageInHoverPreviewForMDNotes: false,
|
||||
renderImageInMarkdownToPDF: false,
|
||||
allowPinchZoom: false,
|
||||
allowWheelZoom: false,
|
||||
zoomToFitOnOpen: true,
|
||||
@@ -222,6 +268,8 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
hoverPreviewWithoutCTRL: false,
|
||||
linkOpacity: 1,
|
||||
openInAdjacentPane: false,
|
||||
showSecondOrderLinks: true,
|
||||
focusOnFileTab: false,
|
||||
openInMainWorkspace: true,
|
||||
showLinkBrackets: true,
|
||||
allowCtrlClick: true,
|
||||
@@ -234,6 +282,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
exportWithTheme: true,
|
||||
exportWithBackground: true,
|
||||
exportPaddingSVG: 10, //since 1.6.17, not only SVG but also PNG
|
||||
exportEmbedScene: false,
|
||||
keepInSync: false,
|
||||
autoexportSVG: false,
|
||||
autoexportPNG: false,
|
||||
@@ -246,8 +295,12 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
experimentalFileType: false,
|
||||
experimentalFileTag: "✏️",
|
||||
experimentalLivePreview: true,
|
||||
fadeOutExcalidrawMarkup: false,
|
||||
loadPropertySuggestions: true,
|
||||
experimentalEnableFourthFont: false,
|
||||
experimantalFourthFont: "Virgil",
|
||||
addDummyTextElement: false,
|
||||
zoteroCompatibility: false,
|
||||
fieldSuggester: true,
|
||||
compatibilityMode: false,
|
||||
//loadCount: 0,
|
||||
@@ -294,6 +347,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
numberOfCustomPens: 0,
|
||||
pdfScale: 4,
|
||||
pdfBorderBox: true,
|
||||
pdfFrame: false,
|
||||
pdfGapSize: 20,
|
||||
pdfGroupPages: false,
|
||||
pdfLockAfterImport: true,
|
||||
@@ -317,11 +371,12 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
borderOpacity: 0,
|
||||
filenameVisible: false,
|
||||
},
|
||||
markdownNodeOneClickEditing: false,
|
||||
canvasImmersiveEmbed: true,
|
||||
startupScriptPath: "",
|
||||
openAIAPIToken: "",
|
||||
openAIDefaultTextModel: "gpt-3.5-turbo-1106",
|
||||
openAIDefaultVisionModel: "gpt-4-vision-preview",
|
||||
openAIDefaultVisionModel: "gpt-4o",
|
||||
openAIDefaultImageGenerationModel: "dall-e-3",
|
||||
openAIURL: "https://api.openai.com/v1/chat/completions",
|
||||
openAIImageGenerationURL: "https://api.openai.com/v1/images/generations",
|
||||
@@ -409,6 +464,16 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
},
|
||||
slidingPanesSupport: false,
|
||||
areaZoomLimit: 1,
|
||||
longPressDesktop: 500,
|
||||
longPressMobile: 500,
|
||||
isDebugMode: false,
|
||||
rank: "Bronze",
|
||||
modifierKeyOverrides: [
|
||||
{modifiers: ["Mod"], key:"Enter"},
|
||||
{modifiers: ["Mod"], key:"k"},
|
||||
{modifiers: ["Mod"], key:"G"},
|
||||
],
|
||||
showSplashscreen: true,
|
||||
};
|
||||
|
||||
export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
@@ -417,6 +482,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
private requestReloadDrawings: boolean = false;
|
||||
private requestUpdatePinnedPens: boolean = false;
|
||||
private requestUpdateDynamicStyling: boolean = false;
|
||||
private hotkeyEditor: HotkeyEditor;
|
||||
//private reloadMathJax: boolean = false;
|
||||
//private applyDebounceTimer: number = 0;
|
||||
|
||||
@@ -443,21 +509,25 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}
|
||||
this.plugin.saveSettings();
|
||||
if (this.requestUpdatePinnedPens) {
|
||||
app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> {
|
||||
this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> {
|
||||
if (v.view instanceof ExcalidrawView) v.view.updatePinnedCustomPens()
|
||||
})
|
||||
}
|
||||
if (this.requestUpdateDynamicStyling) {
|
||||
app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> {
|
||||
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);
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
this.hotkeyEditor.unload();
|
||||
if (this.hotkeyEditor.isDirty) {
|
||||
this.plugin.registerHotkeyOverrides();
|
||||
}
|
||||
if (this.requestReloadDrawings) {
|
||||
const exs =
|
||||
app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
for (const v of exs) {
|
||||
if (v.view instanceof ExcalidrawView) {
|
||||
await v.view.save(false);
|
||||
@@ -497,6 +567,46 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
},
|
||||
});
|
||||
coffeeImg.height = 45;
|
||||
|
||||
const iconLinks = [
|
||||
{
|
||||
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"></path><path d="M9 18c-4.51 2-5-2-7-2"></path></svg>`,
|
||||
href: "https://github.com/zsviczian/obsidian-excalidraw-plugin/issues",
|
||||
aria: "Report bugs and raise feature requsts on the plugin's GitHub page",
|
||||
text: "Bugs and Feature Requests",
|
||||
},
|
||||
{
|
||||
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6 0 0 0 0 0 0z"></path><polygon points="10 15 15 12 10 9"></polygon></svg>`,
|
||||
href: "https://www.youtube.com/@VisualPKM",
|
||||
aria: "Check out my YouTube channel to learn about Visual Thinking and Excalidraw",
|
||||
text: "Visual PKM on YouTube",
|
||||
},
|
||||
{
|
||||
icon: `<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" stroke="none" strokeWidth="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 640 512"><path d="M524.531,69.836a1.5,1.5,0,0,0-.764-.7A485.065,485.065,0,0,0,404.081,32.03a1.816,1.816,0,0,0-1.923.91,337.461,337.461,0,0,0-14.9,30.6,447.848,447.848,0,0,0-134.426,0,309.541,309.541,0,0,0-15.135-30.6,1.89,1.89,0,0,0-1.924-.91A483.689,483.689,0,0,0,116.085,69.137a1.712,1.712,0,0,0-.788.676C39.068,183.651,18.186,294.69,28.43,404.354a2.016,2.016,0,0,0,.765,1.375A487.666,487.666,0,0,0,176.02,479.918a1.9,1.9,0,0,0,2.063-.676A348.2,348.2,0,0,0,208.12,430.4a1.86,1.86,0,0,0-1.019-2.588,321.173,321.173,0,0,1-45.868-21.853,1.885,1.885,0,0,1-.185-3.126c3.082-2.309,6.166-4.711,9.109-7.137a1.819,1.819,0,0,1,1.9-.256c96.229,43.917,200.41,43.917,295.5,0a1.812,1.812,0,0,1,1.924.233c2.944,2.426,6.027,4.851,9.132,7.16a1.884,1.884,0,0,1-.162,3.126,301.407,301.407,0,0,1-45.89,21.83,1.875,1.875,0,0,0-1,2.611,391.055,391.055,0,0,0,30.014,48.815,1.864,1.864,0,0,0,2.063.7A486.048,486.048,0,0,0,610.7,405.729a1.882,1.882,0,0,0,.765-1.352C623.729,277.594,590.933,167.465,524.531,69.836ZM222.491,337.58c-28.972,0-52.844-26.587-52.844-59.239S193.056,219.1,222.491,219.1c29.665,0,53.306,26.82,52.843,59.239C275.334,310.993,251.924,337.58,222.491,337.58Zm195.38,0c-28.971,0-52.843-26.587-52.843-59.239S388.437,219.1,417.871,219.1c29.667,0,53.307,26.82,52.844,59.239C470.715,310.993,447.538,337.58,417.871,337.58Z"/></svg>`,
|
||||
href: "https://discord.gg/DyfAXFwUHc",
|
||||
aria: "Join the Visual Thinking Workshop Discord Server",
|
||||
text: "Community on Discord",
|
||||
},
|
||||
{
|
||||
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 4s-.7 2.1-2 3.4c1.6 10-9.4 17.3-18 11.6 2.2.1 4.4-.6 6-2C3 15.5.5 9.6 3 5c2.2 2.6 5.6 4.1 9 4-.9-4.2 4-6.6 7-3.8 1.1 0 3-1.2 3-1.2z"></path></svg>`,
|
||||
href: "https://twitter.com/zsviczian",
|
||||
aria: "Follow me on Twitter",
|
||||
text: "Follow me on Twitter",
|
||||
},
|
||||
{
|
||||
icon: getIcon("graduation-cap").outerHTML,
|
||||
href: "https://visual-thinking-workshop.com",
|
||||
aria: "Learn about Visual PKM, Excalidraw, Obsidian, ExcaliBrain and more",
|
||||
text: "Join the Visual Thinking Workshop",
|
||||
}
|
||||
];
|
||||
|
||||
const linksEl = containerEl.createDiv("setting-item-description excalidraw-settings-links-container");
|
||||
iconLinks.forEach(({ icon, href, aria, text }) => {
|
||||
linksEl.createEl("a",{href, attr: { "aria-label": aria }}, (a)=> {
|
||||
a.innerHTML = icon + text;
|
||||
});
|
||||
});
|
||||
|
||||
// ------------------------------------------------
|
||||
// Saving
|
||||
@@ -570,6 +680,19 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("ANNOTATE_FOLDER_NAME"))
|
||||
.setDesc(fragWithHTML(t("ANNOTATE_FOLDER_DESC")))
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("e.g.: Excalidraw/Annotations")
|
||||
.setValue(this.plugin.settings.annotateFolder)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.annotateFolder = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("TEMPLATE_NAME"))
|
||||
.setDesc(fragWithHTML(t("TEMPLATE_DESC")))
|
||||
@@ -622,21 +745,31 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("DECOMPRESS_FOR_MD_NAME"))
|
||||
.setDesc(fragWithHTML(t("DECOMPRESS_FOR_MD_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.decompressForMDView)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.decompressForMDView = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("AUTOSAVE_INTERVAL_DESKTOP_NAME"))
|
||||
.setDesc(fragWithHTML(t("AUTOSAVE_INTERVAL_DESKTOP_DESC")))
|
||||
.addDropdown((dropdown) =>
|
||||
dropdown
|
||||
.addOption("15000", "Frequent (every 15 seconds)")
|
||||
.addOption("15000", "Very frequent (every 15 seconds)")
|
||||
.addOption("30000", "Frequent (every 30 seconds)")
|
||||
.addOption("60000", "Moderate (every 60 seconds)")
|
||||
.addOption("300000", "Rare (every 5 minutes)")
|
||||
.addOption("900000", "Practically never (every 15 minutes)")
|
||||
.setValue(this.plugin.settings.autosaveIntervalDesktop.toString())
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.autosaveIntervalDesktop = parseInt(value);
|
||||
this.plugin.settings.autosaveInterval = app.isMobile
|
||||
? this.plugin.settings.autosaveIntervalMobile
|
||||
: this.plugin.settings.autosaveIntervalDesktop;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
@@ -646,16 +779,14 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
.setDesc(fragWithHTML(t("AUTOSAVE_INTERVAL_MOBILE_DESC")))
|
||||
.addDropdown((dropdown) =>
|
||||
dropdown
|
||||
.addOption("10000", "Frequent (every 10 seconds)")
|
||||
.addOption("10000", "Very frequent (every 10 seconds)")
|
||||
.addOption("20000", "Frequent (every 20 seconds)")
|
||||
.addOption("30000", "Moderate (every 30 seconds)")
|
||||
.addOption("60000", "Rare (every 1 minute)")
|
||||
.addOption("300000", "Practically never (every 5 minutes)")
|
||||
.setValue(this.plugin.settings.autosaveIntervalMobile.toString())
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.autosaveIntervalMobile = parseInt(value);
|
||||
this.plugin.settings.autosaveInterval = app.isMobile
|
||||
? this.plugin.settings.autosaveIntervalMobile
|
||||
: this.plugin.settings.autosaveIntervalDesktop;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
@@ -766,7 +897,6 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("CROP_PREFIX_NAME"))
|
||||
.setDesc(fragWithHTML(t("CROP_PREFIX_DESC")))
|
||||
@@ -783,6 +913,36 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("ANNOTATE_PREFIX_NAME"))
|
||||
.setDesc(fragWithHTML(t("ANNOTATE_PREFIX_DESC")))
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("e.g.: Annotated_ ")
|
||||
.setValue(this.plugin.settings.annotatePrefix)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.annotatePrefix = value.replaceAll(
|
||||
/[<>:"/\\|?*]/g,
|
||||
"_",
|
||||
);
|
||||
text.setValue(this.plugin.settings.annotatePrefix);
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("ANNOTATE_PRESERVE_SIZE_NAME"))
|
||||
.setDesc(fragWithHTML(t("ANNOTATE_PRESERVE_SIZE_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.annotatePreserveSize)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.annotatePreserveSize = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
//------------------------------------------------
|
||||
// AI Settings
|
||||
//------------------------------------------------
|
||||
@@ -887,6 +1047,54 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("DISABLE_DOUBLE_TAP_ERASER_NAME"))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.penModeDoubleTapEraser)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.penModeDoubleTapEraser = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_NAME"))
|
||||
.setDesc(fragWithHTML(t("SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.penModeCrosshairVisible)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.penModeCrosshairVisible = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
const readingModeEl = new Setting(detailsEl)
|
||||
.setName(t("SHOW_DRAWING_OR_MD_IN_READING_MODE_NAME"))
|
||||
.setDesc(fragWithHTML(t("SHOW_DRAWING_OR_MD_IN_READING_MODE_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.renderImageInMarkdownReadingMode)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.renderImageInMarkdownReadingMode = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
readingModeEl.nameEl.setAttribute("id",TAG_MDREADINGMODE);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("SHOW_DRAWING_OR_MD_IN_HOVER_PREVIEW_NAME"))
|
||||
.setDesc(fragWithHTML(t("SHOW_DRAWING_OR_MD_IN_HOVER_PREVIEW_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.renderImageInHoverPreviewForMDNotes)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.renderImageInHoverPreviewForMDNotes = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("LEFTHANDED_MODE_NAME"))
|
||||
.setDesc(fragWithHTML(t("LEFTHANDED_MODE_DESC")))
|
||||
@@ -902,43 +1110,66 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
addIframe(detailsEl, "H8Njp7ZXYag",999);
|
||||
addIframe(detailsEl, "H8Njp7ZXYag",999);
|
||||
|
||||
detailsEl = displayDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
text: t("THEME_HEAD"),
|
||||
cls: "excalidraw-setting-h3",
|
||||
});
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("DYNAMICSTYLE_NAME"))
|
||||
.setDesc(fragWithHTML(t("DYNAMICSTYLE_DESC")))
|
||||
.addDropdown((dropdown) =>
|
||||
dropdown
|
||||
.addOption("none","Dynamic Styling OFF")
|
||||
.addOption("colorful","Match color")
|
||||
.addOption("gray","Gray, match tone")
|
||||
.setValue(this.plugin.settings.dynamicStyling)
|
||||
.onChange(async (value) => {
|
||||
this.requestUpdateDynamicStyling = true;
|
||||
this.plugin.settings.dynamicStyling = value as DynamicStyle;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
addIframe(detailsEl, "fypDth_-8q0");
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("IFRAME_MATCH_THEME_NAME"))
|
||||
.setDesc(fragWithHTML(t("IFRAME_MATCH_THEME_DESC")))
|
||||
new Setting(detailsEl)
|
||||
.setName(t("TOGGLE_SPLASHSCREEN"))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.iframeMatchExcalidrawTheme)
|
||||
.setValue(this.plugin.settings.showSplashscreen)
|
||||
.onChange((value)=> {
|
||||
this.plugin.settings.showSplashscreen = value;
|
||||
this.applySettingsUpdate();
|
||||
})
|
||||
)
|
||||
|
||||
detailsEl = displayDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
text: t("HOTKEY_OVERRIDE_HEAD"),
|
||||
cls: "excalidraw-setting-h3",
|
||||
});
|
||||
detailsEl.createEl("span", {}, (el) => {
|
||||
el.innerHTML = t("HOTKEY_OVERRIDE_DESC");
|
||||
});
|
||||
|
||||
this.hotkeyEditor = new HotkeyEditor(detailsEl, this.plugin.settings, this.applySettingsUpdate);
|
||||
this.hotkeyEditor.onload();
|
||||
|
||||
detailsEl = displayDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
text: t("THEME_HEAD"),
|
||||
cls: "excalidraw-setting-h3",
|
||||
});
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("DYNAMICSTYLE_NAME"))
|
||||
.setDesc(fragWithHTML(t("DYNAMICSTYLE_DESC")))
|
||||
.addDropdown((dropdown) =>
|
||||
dropdown
|
||||
.addOption("none","Dynamic Styling OFF")
|
||||
.addOption("colorful","Match color")
|
||||
.addOption("gray","Gray, match tone")
|
||||
.setValue(this.plugin.settings.dynamicStyling)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.iframeMatchExcalidrawTheme = value;
|
||||
this.applySettingsUpdate(true);
|
||||
this.requestUpdateDynamicStyling = true;
|
||||
this.plugin.settings.dynamicStyling = value as DynamicStyle;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
addIframe(detailsEl, "ICpoyMv6KSs");
|
||||
addIframe(detailsEl, "fypDth_-8q0");
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("IFRAME_MATCH_THEME_NAME"))
|
||||
.setDesc(fragWithHTML(t("IFRAME_MATCH_THEME_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.iframeMatchExcalidrawTheme)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.iframeMatchExcalidrawTheme = value;
|
||||
this.applySettingsUpdate(true);
|
||||
}),
|
||||
);
|
||||
addIframe(detailsEl, "ICpoyMv6KSs");
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("MATCH_THEME_NAME"))
|
||||
@@ -1145,6 +1376,48 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
});
|
||||
detailsEl.createDiv({ text: t("DRAG_MODIFIER_DESC"), cls: "setting-item-description" });
|
||||
|
||||
let longPressDesktop: HTMLDivElement;
|
||||
new Setting(detailsEl)
|
||||
.setName(t("LONG_PRESS_DESKTOP_NAME"))
|
||||
.setDesc(fragWithHTML(t("LONG_PRESS_DESKTOP_DESC")))
|
||||
.addSlider((slider) =>
|
||||
slider
|
||||
.setLimits(300, 3000, 100)
|
||||
.setValue(this.plugin.settings.longPressDesktop)
|
||||
.onChange(async (value) => {
|
||||
longPressDesktop.innerText = ` ${value.toString()}`;
|
||||
this.plugin.settings.longPressDesktop = value;
|
||||
this.applySettingsUpdate(true);
|
||||
}),
|
||||
)
|
||||
.settingEl.createDiv("", (el) => {
|
||||
longPressDesktop = el;
|
||||
el.style.minWidth = "2.3em";
|
||||
el.style.textAlign = "right";
|
||||
el.innerText = ` ${this.plugin.settings.longPressDesktop.toString()}`;
|
||||
});
|
||||
|
||||
let longPressMobile: HTMLDivElement;
|
||||
new Setting(detailsEl)
|
||||
.setName(t("LONG_PRESS_MOBILE_NAME"))
|
||||
.setDesc(fragWithHTML(t("LONG_PRESS_MOBILE_DESC")))
|
||||
.addSlider((slider) =>
|
||||
slider
|
||||
.setLimits(300, 3000, 100)
|
||||
.setValue(this.plugin.settings.longPressMobile)
|
||||
.onChange(async (value) => {
|
||||
longPressMobile.innerText = ` ${value.toString()}`;
|
||||
this.plugin.settings.longPressMobile = value;
|
||||
this.applySettingsUpdate(true);
|
||||
}),
|
||||
)
|
||||
.settingEl.createDiv("", (el) => {
|
||||
longPressMobile = el;
|
||||
el.style.minWidth = "2.3em";
|
||||
el.style.textAlign = "right";
|
||||
el.innerText = ` ${this.plugin.settings.longPressMobile.toString()}`;
|
||||
});
|
||||
|
||||
new ModifierKeySettingsComponent(
|
||||
detailsEl,
|
||||
this.plugin.settings.modifierKeyConfig,
|
||||
@@ -1168,6 +1441,18 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
(el) => (el.innerHTML = t("LINKS_DESC")),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("SECOND_ORDER_LINKS_NAME"))
|
||||
.setDesc(fragWithHTML(t("SECOND_ORDER_LINKS_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.showSecondOrderLinks)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.showSecondOrderLinks = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("ADJACENT_PANE_NAME"))
|
||||
.setDesc(fragWithHTML(t("ADJACENT_PANE_DESC")))
|
||||
@@ -1179,7 +1464,18 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("FOCUS_ON_EXISTING_TAB_NAME"))
|
||||
.setDesc(fragWithHTML(t("FOCUS_ON_EXISTING_TAB_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.focusOnFileTab)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.focusOnFileTab = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("MAINWORKSPACE_PANE_NAME"))
|
||||
@@ -1544,6 +1840,19 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
this.applySettingsUpdate();
|
||||
})
|
||||
)
|
||||
new Setting(detailsEl)
|
||||
.setName(t("SCENE_IMAGE_CACHE_NAME"))
|
||||
.setDesc(fragWithHTML(t("SCENE_IMAGE_CACHE_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.allowImageCacheInScene)
|
||||
.onChange((value) => {
|
||||
this.plugin.settings.allowImageCacheInScene = value;
|
||||
this.applySettingsUpdate();
|
||||
})
|
||||
)
|
||||
new Setting(detailsEl)
|
||||
.setName(t("EMBED_IMAGE_CACHE_CLEAR"))
|
||||
.addButton((button) =>
|
||||
button
|
||||
.setButtonText(t("EMBED_IMAGE_CACHE_CLEAR"))
|
||||
@@ -1551,6 +1860,8 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
imageCache.clearImageCache();
|
||||
})
|
||||
)
|
||||
new Setting(detailsEl)
|
||||
.setName(t("BACKUP_CACHE_CLEAR"))
|
||||
.addButton((button) =>
|
||||
button
|
||||
.setButtonText(t("BACKUP_CACHE_CLEAR"))
|
||||
@@ -1583,12 +1894,37 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
cls: "excalidraw-setting-h3",
|
||||
});
|
||||
addIframe(detailsEl, "wTtaXmRJ7wg",171);
|
||||
|
||||
const pdfExportEl = new Setting(detailsEl)
|
||||
.setName(t("SHOW_DRAWING_OR_MD_IN_EXPORTPDF_NAME"))
|
||||
.setDesc(fragWithHTML(t("SHOW_DRAWING_OR_MD_IN_EXPORTPDF_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.renderImageInMarkdownToPDF)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.renderImageInMarkdownToPDF = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
pdfExportEl.nameEl.setAttribute("id",TAG_PDFEXPORT);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("EXPORT_EMBED_SCENE_NAME"))
|
||||
.setDesc(fragWithHTML(t("EXPORT_EMBED_SCENE_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.exportEmbedScene)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.exportEmbedScene = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
detailsEl = exportDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
text: t("EMBED_SIZING"),
|
||||
cls: "excalidraw-setting-h4",
|
||||
});
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("EMBED_WIDTH_NAME"))
|
||||
.setDesc(fragWithHTML(t("EMBED_WIDTH_DESC")))
|
||||
@@ -1603,6 +1939,20 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("EMBED_HEIGHT_NAME"))
|
||||
.setDesc(fragWithHTML(t("EMBED_HEIGHT_DESC")))
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("e.g.: 400")
|
||||
.setValue(this.plugin.settings.height)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.height = value;
|
||||
this.applySettingsUpdate();
|
||||
this.requestEmbedUpdate = true;
|
||||
}),
|
||||
);
|
||||
|
||||
let scaleText: HTMLDivElement;
|
||||
|
||||
new Setting(detailsEl)
|
||||
@@ -1696,6 +2046,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
text: t("EXPORT_HEAD"),
|
||||
cls: "excalidraw-setting-h4",
|
||||
});
|
||||
detailsEl.setAttribute("id",TAG_AUTOEXPORT);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("EXPORT_SYNC_NAME"))
|
||||
@@ -1816,7 +2167,26 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
text: t("MD_EMBED_CUSTOMDATA_HEAD_NAME"),
|
||||
cls: "excalidraw-setting-h3",
|
||||
});
|
||||
detailsEl.createEl("span", {text: t("MD_EMBED_CUSTOMDATA_HEAD_DESC")});
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("MD_EMBED_SINGLECLICK_EDIT_NAME"))
|
||||
.setDesc(fragWithHTML(t("MD_EMBED_SINGLECLICK_EDIT_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.markdownNodeOneClickEditing)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.markdownNodeOneClickEditing = value;
|
||||
this.applySettingsUpdate();
|
||||
})
|
||||
);
|
||||
|
||||
detailsEl.createEl("hr", { cls: "excalidraw-setting-hr" });
|
||||
detailsEl.createEl("span", {}, (el) => {
|
||||
el.innerHTML = t("MD_EMBED_CUSTOMDATA_HEAD_DESC");
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
new EmbeddalbeMDFileCustomDataSettingsComponent(
|
||||
detailsEl,
|
||||
@@ -1893,7 +2263,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
d.addOption("Assistant", "Assistant");
|
||||
this.app.vault
|
||||
.getFiles()
|
||||
.filter((f) => ["ttf", "woff", "woff2"].contains(f.extension))
|
||||
.filter((f) => ["ttf", "woff", "woff2", "otf"].contains(f.extension))
|
||||
.forEach((f: TFile) => {
|
||||
d.addOption(f.path, f.name);
|
||||
});
|
||||
@@ -1979,7 +2349,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
areaZoomText.innerText = ` ${value.toString()}`;
|
||||
this.plugin.settings.areaZoomLimit = value;
|
||||
this.applySettingsUpdate();
|
||||
this.plugin.excalidrawConfig.updateValues();
|
||||
this.plugin.excalidrawConfig.updateValues(this.plugin);
|
||||
}),
|
||||
)
|
||||
.settingEl.createDiv("", (el) => {
|
||||
@@ -2124,6 +2494,31 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("FADE_OUT_EXCALIDRAW_MARKUP_NAME"))
|
||||
.setDesc(fragWithHTML(t("FADE_OUT_EXCALIDRAW_MARKUP_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.fadeOutExcalidrawMarkup)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.fadeOutExcalidrawMarkup = value;
|
||||
this.plugin.editorHandler.updateCMExtensionState(EDITOR_FADEOUT, value)
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("EXCALIDRAW_PROPERTIES_NAME"))
|
||||
.setDesc(fragWithHTML(t("EXCALIDRAW_PROPERTIES_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.loadPropertySuggestions)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.loadPropertySuggestions = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
detailsEl = experimentalDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
text: t("TASKBONE_HEAD"),
|
||||
@@ -2252,6 +2647,47 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
cls: "excalidraw-setting-h1",
|
||||
});
|
||||
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("DUMMY_TEXT_ELEMENT_LINT_SUPPORT_NAME"))
|
||||
.setDesc(fragWithHTML(t("DUMMY_TEXT_ELEMENT_LINT_SUPPORT_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.addDummyTextElement)
|
||||
.onChange((value) => {
|
||||
this.plugin.settings.addDummyTextElement = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("PRESERVE_TEXT_AFTER_DRAWING_NAME"))
|
||||
.setDesc(fragWithHTML(t("PRESERVE_TEXT_AFTER_DRAWING_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.zoteroCompatibility)
|
||||
.onChange((value) => {
|
||||
this.plugin.settings.zoteroCompatibility = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
new Setting(detailsEl)
|
||||
.setName(t("DEBUGMODE_NAME"))
|
||||
.setDesc(fragWithHTML(t("DEBUGMODE_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.isDebugMode)
|
||||
.onChange((value) => {
|
||||
this.plugin.settings.isDebugMode = value;
|
||||
setDebugging(value);
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("SLIDING_PANES_NAME"))
|
||||
.setDesc(fragWithHTML(t("SLIDING_PANES_DESC")))
|
||||
|
||||
@@ -4,37 +4,55 @@ import { createTreeWalker, walk } from "./walker";
|
||||
|
||||
export type ConversionResult = {
|
||||
hasErrors: boolean;
|
||||
errors: NodeListOf<Element> | null;
|
||||
errors: string;
|
||||
content: any; // Serialized Excalidraw JSON
|
||||
};
|
||||
|
||||
export const svgToExcalidraw = (svgString: string): ConversionResult => {
|
||||
const parser = new DOMParser();
|
||||
const svgDOM = parser.parseFromString(svgString, "image/svg+xml");
|
||||
try {
|
||||
const parser = new DOMParser();
|
||||
const svgDOM = parser.parseFromString(svgString, "image/svg+xml");
|
||||
|
||||
// was there a parsing error?
|
||||
const errorsElements = svgDOM.querySelectorAll("parsererror");
|
||||
const hasErrors = errorsElements.length > 0;
|
||||
let content = null;
|
||||
// was there a parsing error?
|
||||
const errorsElements = svgDOM.querySelectorAll("parsererror");
|
||||
const hasErrors = errorsElements.length > 0;
|
||||
let content = null;
|
||||
|
||||
if (hasErrors) {
|
||||
console.error(
|
||||
"There were errors while parsing the given SVG: ",
|
||||
[...errorsElements].map((el) => el.innerHTML),
|
||||
);
|
||||
} else {
|
||||
const tw = createTreeWalker(svgDOM);
|
||||
const scene = new ExcalidrawScene();
|
||||
const groups: Group[] = [];
|
||||
if (hasErrors) {
|
||||
console.error(
|
||||
"There were errors while parsing the given SVG: ",
|
||||
[...errorsElements].map((el) => el.innerHTML),
|
||||
);
|
||||
} else {
|
||||
const tw = createTreeWalker(svgDOM);
|
||||
const scene = new ExcalidrawScene();
|
||||
const groups: Group[] = [];
|
||||
|
||||
walk({ tw, scene, groups, root: svgDOM }, tw.nextNode());
|
||||
walk({ tw, scene, groups, root: svgDOM }, tw.nextNode());
|
||||
|
||||
content = scene.elements; //scene.toExJSON();
|
||||
const hasVisibleElements = Boolean(scene.elements.find((el)=>el.opacity !== 0));
|
||||
if (!hasVisibleElements) {
|
||||
scene.elements.forEach((el) => {
|
||||
el.opacity = 100;
|
||||
});
|
||||
}
|
||||
scene.elements.forEach((el) => {
|
||||
if(el.opacity <= 1) el.opacity = 100;
|
||||
});
|
||||
content = scene.elements; //scene.toExJSON();
|
||||
}
|
||||
|
||||
return {
|
||||
hasErrors,
|
||||
errors: hasErrors ? `${[...errorsElements].map((el) => el.innerHTML)}` : "",
|
||||
content,
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return {
|
||||
hasErrors: true,
|
||||
errors: `${error}`,
|
||||
content:[],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
hasErrors,
|
||||
errors: hasErrors ? errorsElements : null,
|
||||
content,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -37,6 +37,7 @@ const SUPPORTED_TAGS = [
|
||||
"rect",
|
||||
"polyline",
|
||||
"polygon",
|
||||
"switch",
|
||||
];
|
||||
|
||||
const nodeValidator = (node: Element): number => {
|
||||
@@ -120,6 +121,18 @@ const walkers = {
|
||||
walk(args, args.tw.nextNode());
|
||||
},
|
||||
|
||||
switch: (args: WalkerArgs) => {
|
||||
const nextArgs = {
|
||||
...args,
|
||||
tw: createTreeWalker(args.tw.currentNode),
|
||||
groups: [...args.groups, new Group(args.tw.currentNode as Element)],
|
||||
};
|
||||
|
||||
walk(nextArgs, nextArgs.tw.nextNode());
|
||||
|
||||
walk(args, args.tw.nextSibling());
|
||||
},
|
||||
|
||||
g: (args: WalkerArgs) => {
|
||||
const nextArgs = {
|
||||
...args,
|
||||
|
||||