mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
47 Commits
2.0.20
...
2.1.8.2-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31f54db433 | ||
|
|
26812dd297 | ||
|
|
ae4f4b4f08 | ||
|
|
4ac0a4c565 | ||
|
|
69c9f824a0 | ||
|
|
6a2220c960 | ||
|
|
aa501c2843 | ||
|
|
efce44f0a7 | ||
|
|
491eb83d35 | ||
|
|
35cf0802d1 | ||
|
|
f768548f60 | ||
|
|
37789f9907 | ||
|
|
131294464e | ||
|
|
b7652a41f8 | ||
|
|
91c5f85ec6 | ||
|
|
985983b31d | ||
|
|
9a27e38ce2 | ||
|
|
0017ed7c92 | ||
|
|
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 |
12
.github/ISSUE_TEMPLATE/bug_report.md
vendored
12
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,12 +1,22 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help me improve Excalidraw
|
||||
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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
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>
|
||||
|
||||
|
||||
12
docs/API/ExcalidrawAutomate.d.ts
vendored
12
docs/API/ExcalidrawAutomate.d.ts
vendored
@@ -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
|
||||
@@ -148,7 +148,7 @@ export declare class ExcalidrawAutomate {
|
||||
}>;
|
||||
/**
|
||||
* get all elements from ExcalidrawAutomate elementsDict
|
||||
* @returns elements from elemenetsDict
|
||||
* @returns elements from elementsDict
|
||||
*/
|
||||
getElements(): ExcalidrawElement[];
|
||||
/**
|
||||
@@ -698,25 +698,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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,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
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
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,11 +17,11 @@ 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??""
|
||||
templatePath: excalidrawTemplates?.[0].path??""
|
||||
};
|
||||
}
|
||||
|
||||
@@ -45,11 +45,21 @@ if(!settings["Templates"]) {
|
||||
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));
|
||||
|
||||
@@ -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) {
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -631,7 +631,7 @@ modal.onOpen = async () => {
|
||||
.addDropdown(dropdown=>dropdown
|
||||
.addOption("none","None")
|
||||
.addOption("top-down","Top down")
|
||||
.addOption("bottom-up","Bootom up")
|
||||
.addOption("bottom-up","Bottom up")
|
||||
.addOption("center-out","Center out")
|
||||
.addOption("center-in","Center in")
|
||||
.setValue(vDirection)
|
||||
|
||||
@@ -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,7 +58,7 @@ 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)|
|
||||
|[Reverse arrows](Reverse%20arrows.md)|Reverse the direction of **arrows** within the scope of selected elements.||[@zsviczian](https://github.com/zsviczian)|
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
@@ -97,6 +98,7 @@ const selectAttributesToCopy = () => {
|
||||
{name: "End arrowhead", key: "endArrowhead"},
|
||||
{name: "Height", key: "height"},
|
||||
{name: "Width", key: "width"},
|
||||
{name: "ImageID", key: "fileId"},
|
||||
];
|
||||
|
||||
attributes.forEach(attr => {
|
||||
@@ -165,7 +167,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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -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.
|
||||
@@ -116,6 +116,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/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/GPT-Draw-a-UI.svg"/></div>|[[#GPT Draw-a-UI]]|
|
||||
@@ -197,7 +198,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 +273,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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.0.1-beta-2",
|
||||
"version": "2.1.8.2-beta-1",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.0.20",
|
||||
"version": "2.1.8",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
22
package.json
22
package.json
@@ -18,23 +18,23 @@
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@zsviczian/excalidraw": "0.17.1-obsidian-13",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@zsviczian/excalidraw": "0.17.1-obsidian-21",
|
||||
"chroma-js": "^2.4.2",
|
||||
"clsx": "^2.0.0",
|
||||
"colormaster": "^1.2.1",
|
||||
"gl-matrix": "^3.4.3",
|
||||
"lucide-react": "^0.263.1",
|
||||
"mathjax-full": "^3.2.2",
|
||||
"monkey-around": "^2.3.0",
|
||||
"nanoid": "^4.0.2",
|
||||
"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"
|
||||
"js-yaml": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"lz-string": "^1.5.0",
|
||||
"@babel/core": "^7.22.9",
|
||||
"@babel/preset-env": "^7.22.10",
|
||||
"@babel/preset-react": "^7.22.5",
|
||||
@@ -50,10 +50,13 @@
|
||||
"@types/node": "^20.10.5",
|
||||
"@types/react": "^18.2.45",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@zerollup/ts-transform-paths": "^1.7.18",
|
||||
"cross-env": "^7.0.3",
|
||||
"cssnano": "^6.0.2",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"lz-string": "^1.5.0",
|
||||
"obsidian": "^1.4.0",
|
||||
"prettier": "^3.0.1",
|
||||
"rollup": "^2.70.1",
|
||||
@@ -65,8 +68,11 @@
|
||||
"tslib": "^2.6.1",
|
||||
"ttypescript": "^1.5.15",
|
||||
"typescript": "^5.2.2",
|
||||
"cssnano": "^6.0.2"
|
||||
|
||||
"@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"
|
||||
},
|
||||
"resolutions": {
|
||||
"@typescript-eslint/typescript-estree": "5.3.0"
|
||||
|
||||
@@ -26,6 +26,7 @@ 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) {
|
||||
const excalidraw_styles = isProd
|
||||
@@ -58,7 +59,23 @@ const packageString = isLib
|
||||
|
||||
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) =>
|
||||
@@ -107,7 +124,9 @@ const BUILD_CONFIG = {
|
||||
// npm install brettz9/rollup-plugin-postprocess#update --save-dev
|
||||
// https://github.com/developit/rollup-plugin-postprocess/issues/10
|
||||
postprocess([
|
||||
[/,React=require\("react"\);/, packageString],
|
||||
//[/,React=require\("react"\);/, packageString],
|
||||
[/React=require\("react"\),state=require\("@codemirror\/state"\),view=require\("@codemirror\/view"\)/,
|
||||
`state=require("@codemirror/state"),view=require("@codemirror/view")` + packageString]
|
||||
])
|
||||
]
|
||||
: [
|
||||
|
||||
45
src/CodeMirrorExtension/EditorHandler.ts
Normal file
45
src/CodeMirrorExtension/EditorHandler.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
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) {}
|
||||
|
||||
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
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,7 +1,7 @@
|
||||
//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 {
|
||||
@@ -23,7 +23,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,
|
||||
@@ -42,6 +42,8 @@ import {
|
||||
import { ValueOf } from "./types";
|
||||
import { getMermaidImageElements, getMermaidText, shouldRenderMermaid } from "./utils/MermaidUtils";
|
||||
import { mermaidToExcalidraw } from "src/constants/constants";
|
||||
import { ImageKey, imageCache } from "./utils/ImageCache";
|
||||
import { PreviewImageType } from "./utils/UtilTypes";
|
||||
|
||||
//An ugly workaround for the following situation.
|
||||
//File A is a markdown file that has an embedded Excalidraw file B
|
||||
@@ -164,7 +166,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) {
|
||||
@@ -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(
|
||||
@@ -358,22 +360,46 @@ export class EmbeddedFilesLoader {
|
||||
withTheme: !!forceTheme,
|
||||
isMask,
|
||||
};
|
||||
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 shouldUseCache = file && imageCache.isReady();
|
||||
const cacheKey:ImageKey = {
|
||||
filepath: file.path,
|
||||
blockref: null,
|
||||
sectionref: null,
|
||||
isDark,
|
||||
previewImageType: PreviewImageType.SVG,
|
||||
scale: 1,
|
||||
isTransparent: !exportSettings.withBackground,
|
||||
hasBlockref: false,
|
||||
hasGroupref: false,
|
||||
hasTaskbone: false,
|
||||
hasArearef: false,
|
||||
hasFrameref: false,
|
||||
hasSectionref: false,
|
||||
linkpartReference: null,
|
||||
linkpartAlias: null,
|
||||
}
|
||||
|
||||
const maybeSVG = await imageCache.getImageFromCache(cacheKey);
|
||||
|
||||
const svg = (maybeSVG && (maybeSVG instanceof SVGSVGElement))
|
||||
? maybeSVG
|
||||
: 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;
|
||||
|
||||
//https://stackoverflow.com/questions/51154171/remove-css-filter-on-child-elements
|
||||
const imageList = svg.querySelectorAll(
|
||||
@@ -382,7 +408,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 +420,9 @@ export class EmbeddedFilesLoader {
|
||||
if (!hasSVGwithBitmap && svg.getAttribute("hasbitmap")) {
|
||||
hasSVGwithBitmap = true;
|
||||
}
|
||||
if(shouldUseCache && !Boolean(maybeSVG)) {
|
||||
imageCache.addImageToCache(cacheKey,"", svg);
|
||||
}
|
||||
const dURL = svgToBase64(svg.outerHTML) as DataURL;
|
||||
return {dataURL: dURL as DataURL, hasSVGwithBitmap};
|
||||
};
|
||||
@@ -526,9 +556,11 @@ export class EmbeddedFilesLoader {
|
||||
public async loadSceneFiles(
|
||||
excalidrawData: ExcalidrawData,
|
||||
addFiles: (files: FileData[], isDark: boolean, final?: boolean) => void,
|
||||
depth:number
|
||||
depth:number,
|
||||
isThemeChange:boolean = false,
|
||||
) {
|
||||
if(depth > 4) {
|
||||
|
||||
if(depth > 7) {
|
||||
new Notice(t("INFINITE_LOOP_WARNING")+depth.toString(), 6000);
|
||||
return;
|
||||
}
|
||||
@@ -562,7 +594,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],
|
||||
@@ -784,7 +817,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);
|
||||
|
||||
@@ -36,9 +36,8 @@ import {
|
||||
THEME_FILTER,
|
||||
mermaidToExcalidraw,
|
||||
} from "src/constants/constants";
|
||||
import { blobToBase64, checkAndCreateFolder, getDrawingFilename, getNewUniqueFilepath, } from "src/utils/FileUtils";
|
||||
import { blobToBase64, checkAndCreateFolder, getDrawingFilename, getExcalidrawEmbeddedFilesFiletree, getListOfTemplateFiles, getNewUniqueFilepath, hasExcalidrawEmbeddedImagesTreeChanged, } from "src/utils/FileUtils";
|
||||
import {
|
||||
arrayToMap,
|
||||
//debug,
|
||||
embedFontsInSVG,
|
||||
errorlog,
|
||||
@@ -49,15 +48,15 @@ import {
|
||||
getSVG,
|
||||
isMaskFile,
|
||||
isVersionNewerThanOther,
|
||||
log,
|
||||
scaleLoadedImage,
|
||||
wrapTextAtCharLength,
|
||||
arrayToMap,
|
||||
} from "src/utils/Utils";
|
||||
import { getAttachmentsFolderAndFilePath, getLeaf, getNewOrAdjacentLeaf, isObsidianThemeDark } from "src/utils/ObsidianUtils";
|
||||
import { getAttachmentsFolderAndFilePath, getLeaf, getNewOrAdjacentLeaf, isObsidianThemeDark, mergeMarkdownFiles, openLeaf } from "src/utils/ObsidianUtils";
|
||||
import { AppState, BinaryFileData, DataURL, ExcalidrawImperativeAPI, Point } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { EmbeddedFile, EmbeddedFilesLoader, FileData } from "src/EmbeddedFileLoader";
|
||||
import { tex2dataURL } from "src/LaTeX";
|
||||
import { GenericInputPrompt, NewFileActions, Prompt } from "src/dialogs/Prompt";
|
||||
import { GenericInputPrompt, NewFileActions } from "src/dialogs/Prompt";
|
||||
import { t } from "src/lang/helpers";
|
||||
import { ScriptEngine } from "src/Scripts";
|
||||
import { ConnectionPoint, DeviceType } from "src/types";
|
||||
@@ -79,7 +78,7 @@ import { TInput } from "colormaster/types";
|
||||
import {ConversionResult, svgToExcalidraw} from "src/svgToExcalidraw/parser"
|
||||
import { ROUNDNESS } from "src/constants/constants";
|
||||
import { ClipboardData } from "@zsviczian/excalidraw/types/excalidraw/clipboard";
|
||||
import { emulateKeysForLinkClick, KeyEvent, PaneTarget } from "src/utils/ModifierkeyHelper";
|
||||
import { emulateKeysForLinkClick, PaneTarget } from "src/utils/ModifierkeyHelper";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import PolyBool from "polybooljs";
|
||||
import { EmbeddableMDCustomProps } from "./dialogs/EmbeddableSettings";
|
||||
@@ -89,8 +88,8 @@ import {
|
||||
extractCodeBlocks as _extractCodeBlocks,
|
||||
} from "./utils/AIUtils";
|
||||
import { EXCALIDRAW_AUTOMATE_INFO, EXCALIDRAW_SCRIPTENGINE_INFO } from "./dialogs/SuggesterInfo";
|
||||
import { CropImage } from "./utils/CropImage";
|
||||
import { has } from "./svgToExcalidraw/attributes";
|
||||
import { getFrameBasedOnFrameNameOrId } from "./utils/ExcalidrawViewUtils";
|
||||
import { log } from "./utils/DebugHelper";
|
||||
|
||||
extendPlugins([
|
||||
HarmonyPlugin,
|
||||
@@ -131,7 +130,7 @@ export class ExcalidrawAutomate {
|
||||
|
||||
public help(target: Function | string) {
|
||||
if (!target) {
|
||||
console.log("Usage: ea.help(ea.functionName) or ea.help('propertyName') or ea.help('utils.functionName') - notice property name and utils function name is in quotes");
|
||||
log("Usage: ea.help(ea.functionName) or ea.help('propertyName') or ea.help('utils.functionName') - notice property name and utils function name is in quotes");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -150,11 +149,17 @@ export class ExcalidrawAutomate {
|
||||
}
|
||||
|
||||
if(!funcInfo) {
|
||||
console.log("Usage: ea.help(ea.functionName) or ea.help('propertyName') or ea.help('utils.functionName') - notice property name and utils function name is in quotes");
|
||||
log("Usage: ea.help(ea.functionName) or ea.help('propertyName') or ea.help('utils.functionName') - notice property name and utils function name is in quotes");
|
||||
return;
|
||||
}
|
||||
|
||||
let isMissing = true;
|
||||
if (funcInfo.code) {
|
||||
isMissing = false;
|
||||
log(`Declaration: ${funcInfo.code}`);
|
||||
}
|
||||
if (funcInfo.desc) {
|
||||
isMissing = false;
|
||||
const formattedDesc = funcInfo.desc
|
||||
.replaceAll("<br>", "\n")
|
||||
.replace(/<code>(.*?)<\/code>/g, '%c\u200b$1%c') // Zero-width space
|
||||
@@ -162,11 +167,10 @@ export class ExcalidrawAutomate {
|
||||
.replace(/<a onclick='window\.open\("(.*?)"\)'>(.*?)<\/a>/g, (_, href, text) => `%c\u200b${text}%c\u200b (link: ${href})`); // Zero-width non-joiner
|
||||
|
||||
const styles = Array.from({ length: (formattedDesc.match(/%c/g) || []).length }, (_, i) => i % 2 === 0 ? 'color: #007bff;' : '');
|
||||
|
||||
console.log(`Declaration: ${funcInfo.code}`);
|
||||
console.log(`Description: ${formattedDesc}`, ...styles);
|
||||
} else {
|
||||
console.log("Description not available for this function.");
|
||||
log(`Description: ${formattedDesc}`, ...styles);
|
||||
}
|
||||
if (isMissing) {
|
||||
log("Description not available for this function.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,6 +240,30 @@ export class ExcalidrawAutomate {
|
||||
return getNewUniqueFilepath(app.vault, filename, folderpath);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns the Excalidraw Template files or null.
|
||||
*/
|
||||
public getListOfTemplateFiles(): TFile[] | null {
|
||||
return getListOfTemplateFiles(this.plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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[] {
|
||||
if(!excalidrawFile && this.targetView && this.targetView.file) {
|
||||
excalidrawFile = this.targetView.file;
|
||||
}
|
||||
if(!excalidrawFile) {
|
||||
return [];
|
||||
}
|
||||
return getExcalidrawEmbeddedFilesFiletree(excalidrawFile, this.plugin);
|
||||
}
|
||||
|
||||
public async getAttachmentFilepath(filename: string): Promise<string> {
|
||||
if (!this.targetView || !this.targetView?.file) {
|
||||
errorMessage("targetView not set", "getAttachmentFolderAndFilePath()");
|
||||
@@ -292,7 +320,7 @@ export class ExcalidrawAutomate {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -543,7 +571,7 @@ export class ExcalidrawAutomate {
|
||||
|
||||
/**
|
||||
* get all elements from ExcalidrawAutomate elementsDict
|
||||
* @returns elements from elemenetsDict
|
||||
* @returns elements from elementsDict
|
||||
*/
|
||||
getElements(): Mutable<ExcalidrawElement>[] {
|
||||
const elements = [];
|
||||
@@ -585,11 +613,13 @@ export class ExcalidrawAutomate {
|
||||
"excalidraw-export-dark"?: boolean;
|
||||
"excalidraw-export-padding"?: number;
|
||||
"excalidraw-export-pngscale"?: number;
|
||||
"excalidraw-export-embed-scene"?: boolean;
|
||||
"excalidraw-default-mode"?: "view" | "zen";
|
||||
"excalidraw-onload-script"?: string;
|
||||
"excalidraw-linkbutton-opacity"?: number;
|
||||
"excalidraw-autoexport"?: boolean;
|
||||
"excalidraw-mask"?: boolean;
|
||||
"excalidraw-open-md"?: boolean;
|
||||
"cssclasses"?: string;
|
||||
};
|
||||
plaintext?: string; //text to insert above the `# Text Elements` section
|
||||
@@ -629,6 +659,12 @@ export class ExcalidrawAutomate {
|
||||
: FRONTMATTER;
|
||||
}
|
||||
|
||||
frontmatter += params.plaintext ? params.plaintext + "\n\n" : "";
|
||||
if(template?.frontmatter && params?.frontmatterKeys) {
|
||||
//the frontmatter tags supplyed to create take priority
|
||||
frontmatter = mergeMarkdownFiles(template.frontmatter,frontmatter);
|
||||
}
|
||||
|
||||
const scene = {
|
||||
type: "excalidraw",
|
||||
version: 2,
|
||||
@@ -681,9 +717,8 @@ export class ExcalidrawAutomate {
|
||||
};
|
||||
|
||||
const generateMD = ():string => {
|
||||
let outString = params.plaintext ? params.plaintext + "\n\n" : "";
|
||||
const textElements = this.getElements().filter(el => el.type === "text") as ExcalidrawTextElement[];
|
||||
outString += "# Text Elements\n";
|
||||
let outString = `# Excalidraw Data\n## Text Elements\n`;
|
||||
textElements.forEach(te=> {
|
||||
outString += `${te.rawText ?? (te.originalText ?? te.text)} ^${te.id}\n\n`;
|
||||
});
|
||||
@@ -694,7 +729,7 @@ export class ExcalidrawAutomate {
|
||||
})
|
||||
|
||||
outString += Object.keys(this.imagesDict).length > 0
|
||||
? "\n# Embedded files\n"
|
||||
? `\n## Embedded Files\n`
|
||||
: "";
|
||||
|
||||
Object.keys(this.imagesDict).forEach((key: FileId)=> {
|
||||
@@ -722,7 +757,7 @@ export class ExcalidrawAutomate {
|
||||
}
|
||||
}
|
||||
})
|
||||
return outString;
|
||||
return outString + "\n%%\n";
|
||||
}
|
||||
|
||||
const filename = params?.filename
|
||||
@@ -746,14 +781,6 @@ export class ExcalidrawAutomate {
|
||||
}
|
||||
};
|
||||
|
||||
/* getCropImageObject(): CropImage {
|
||||
const scene = this.targetView.getScene();
|
||||
return new CropImage(
|
||||
scene.elements,
|
||||
scene.files,
|
||||
);
|
||||
}*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @param templatePath
|
||||
@@ -2190,6 +2217,19 @@ export class ExcalidrawAutomate {
|
||||
color: string,
|
||||
) => void = null;
|
||||
|
||||
/**
|
||||
* If set, this callback is triggered whenever a drawing is exported to SVG.
|
||||
* The string returned will replace the link in the exported SVG.
|
||||
* The hook is only executed if the link is to a file internal to Obsidian
|
||||
* see: https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1605
|
||||
*/
|
||||
onUpdateElementLinkForExportHook: (data: {
|
||||
originalLink: string,
|
||||
obsidianLink: string,
|
||||
linkedFile: TFile | null,
|
||||
hostFile: TFile,
|
||||
}) => string = null;
|
||||
|
||||
/**
|
||||
* utility function to generate EmbeddedFilesLoader object
|
||||
* @param isDark
|
||||
@@ -2374,8 +2414,13 @@ export class ExcalidrawAutomate {
|
||||
if (!this.targetView) {
|
||||
return null;
|
||||
}
|
||||
const leaf = getNewOrAdjacentLeaf(this.plugin, this.targetView.leaf);
|
||||
leaf.openFile(file, openState ?? {active: true});
|
||||
|
||||
const {leaf, promise} = openLeaf({
|
||||
plugin: this.plugin,
|
||||
fnGetLeaf: () => getNewOrAdjacentLeaf(this.plugin, this.targetView.leaf),
|
||||
file,
|
||||
openState: openState ?? {active: true}
|
||||
});
|
||||
return leaf;
|
||||
};
|
||||
|
||||
@@ -2515,7 +2560,7 @@ export class ExcalidrawAutomate {
|
||||
};
|
||||
|
||||
/**
|
||||
* Depricated. Use getCM / ColorMaster instead
|
||||
* Deprecated. Use getCM / ColorMaster instead
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
@@ -2525,7 +2570,7 @@ export class ExcalidrawAutomate {
|
||||
};
|
||||
|
||||
/**
|
||||
* Depricated. Use getCM / ColorMaster instead
|
||||
* Deprecated. Use getCM / ColorMaster instead
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
@@ -2535,7 +2580,7 @@ export class ExcalidrawAutomate {
|
||||
};
|
||||
|
||||
/**
|
||||
* Depricated. Use getCM / ColorMaster instead
|
||||
* Deprecated. Use getCM / ColorMaster instead
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
@@ -2545,7 +2590,7 @@ export class ExcalidrawAutomate {
|
||||
};
|
||||
|
||||
/**
|
||||
* Depricated. Use getCM / ColorMaster instead
|
||||
* Deprecated. Use getCM / ColorMaster instead
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
@@ -2596,7 +2641,7 @@ export class ExcalidrawAutomate {
|
||||
importSVG(svgString:string):boolean {
|
||||
const res:ConversionResult = svgToExcalidraw(svgString);
|
||||
if(res.hasErrors) {
|
||||
new Notice (`There were errors while parsing the given SVG:\n${[...res.errors].map((el) => el.innerHTML)}`);
|
||||
new Notice (`There were errors while parsing the given SVG:\n${res.errors}`);
|
||||
return false;
|
||||
}
|
||||
this.copyViewElementsToEAforEditing(res.content);
|
||||
@@ -2729,9 +2774,9 @@ async function getTemplate(
|
||||
textMode,
|
||||
);
|
||||
|
||||
let trimLocation = data.search("# Text Elements\n");
|
||||
let trimLocation = data.search(/^##? Text Elements$/m);
|
||||
if (trimLocation == -1) {
|
||||
trimLocation = data.search("# Drawing\n");
|
||||
trimLocation = data.search(/##? Drawing\n/);
|
||||
}
|
||||
|
||||
let scene = excalidrawData.scene;
|
||||
@@ -2767,9 +2812,10 @@ async function getTemplate(
|
||||
}
|
||||
}
|
||||
if(filenameParts.hasFrameref) {
|
||||
const el = scene.elements.filter((el: ExcalidrawElement)=>el.id===filenameParts.blockref)
|
||||
if(el.length === 1) {
|
||||
groupElements = plugin.ea.getElementsInFrame(el[0],scene.elements)
|
||||
const el = getFrameBasedOnFrameNameOrId(filenameParts.blockref,scene.elements);
|
||||
|
||||
if(el) {
|
||||
groupElements = plugin.ea.getElementsInFrame(el,scene.elements)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2887,7 +2933,15 @@ export const updateElementLinksToObsidianLinks = ({elements, hostFile}:{
|
||||
if(!file) {
|
||||
return el;
|
||||
}
|
||||
const link = app.getObsidianUrl(file);
|
||||
let link = app.getObsidianUrl(file);
|
||||
if(window.ExcalidrawAutomate?.onUpdateElementLinkForExportHook) {
|
||||
link = window.ExcalidrawAutomate.onUpdateElementLinkForExportHook({
|
||||
originalLink: el.link,
|
||||
obsidianLink: link,
|
||||
linkedFile: file,
|
||||
hostFile: hostFile
|
||||
});
|
||||
}
|
||||
const newElement: Mutable<ExcalidrawElement> = cloneElement(el);
|
||||
newElement.link = link;
|
||||
return newElement;
|
||||
@@ -3052,7 +3106,7 @@ function errorMessage(message: string, source: string) {
|
||||
errorlog({
|
||||
where: "ExcalidrawAutomate",
|
||||
source,
|
||||
message: "this function is not avalable on Obsidian Mobile",
|
||||
message: "this function is not available on Obsidian Mobile",
|
||||
});
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -18,6 +18,8 @@ import {
|
||||
ERROR_IFRAME_CONVERSION_CANCELED,
|
||||
JSON_parse,
|
||||
FRONTMATTER_KEYS,
|
||||
refreshTextDimensions,
|
||||
getContainerElement,
|
||||
} from "./constants/constants";
|
||||
import { _measureText } from "./ExcalidrawAutomate";
|
||||
import ExcalidrawPlugin from "./main";
|
||||
@@ -25,11 +27,10 @@ import { TextMode } from "./ExcalidrawView";
|
||||
import {
|
||||
addAppendUpdateCustomData,
|
||||
compress,
|
||||
debug,
|
||||
decompress,
|
||||
//getBakPath,
|
||||
getBinaryFileFromDataURL,
|
||||
getContainerElement,
|
||||
_getContainerElement,
|
||||
getExportTheme,
|
||||
getLinkParts,
|
||||
hasExportTheme,
|
||||
@@ -37,17 +38,21 @@ import {
|
||||
LinkParts,
|
||||
updateFrontmatterInString,
|
||||
wrapTextAtCharLength,
|
||||
arrayToMap,
|
||||
} from "./utils/Utils";
|
||||
import { cleanBlockRef, cleanSectionHeading, getAttachmentsFolderAndFilePath, isObsidianThemeDark } from "./utils/ObsidianUtils";
|
||||
import {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawImageElement,
|
||||
ExcalidrawTextElement,
|
||||
FileId,
|
||||
} from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { BinaryFiles, DataURL, SceneData } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { EmbeddedFile, MimeType } from "./EmbeddedFileLoader";
|
||||
import { ConfirmationPrompt } from "./dialogs/Prompt";
|
||||
import { getMermaidImageElements, getMermaidText, shouldRenderMermaid } from "./utils/MermaidUtils";
|
||||
import { debug } from "./utils/DebugHelper";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
|
||||
type SceneDataWithFiles = SceneData & { files: BinaryFiles };
|
||||
|
||||
@@ -114,12 +119,12 @@ export const REGEX_LINK = {
|
||||
};
|
||||
|
||||
//added \n at and of DRAWING_REG: https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/357
|
||||
const DRAWING_REG = /\n# Drawing\n[^`]*(```json\n)([\s\S]*?)```\n/gm; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/182
|
||||
const DRAWING_REG_FALLBACK = /\n# Drawing\n(```json\n)?(.*)(```)?(%%)?/gm;
|
||||
const DRAWING_REG = /\n##? Drawing\n[^`]*(```json\n)([\s\S]*?)```\n/gm; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/182
|
||||
const DRAWING_REG_FALLBACK = /\n##? Drawing\n(```json\n)?(.*)(```)?(%%)?/gm;
|
||||
export const DRAWING_COMPRESSED_REG =
|
||||
/(\n# Drawing\n[^`]*(?:```compressed\-json\n))([\s\S]*?)(```\n)/gm; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/182
|
||||
/(\n##? Drawing\n[^`]*(?:```compressed\-json\n))([\s\S]*?)(```\n)/gm; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/182
|
||||
const DRAWING_COMPRESSED_REG_FALLBACK =
|
||||
/(\n# Drawing\n(?:```compressed\-json\n)?)(.*)((```)?(%%)?)/gm;
|
||||
/(\n##? Drawing\n(?:```compressed\-json\n)?)(.*)((```)?(%%)?)/gm;
|
||||
export const REG_LINKINDEX_HYPERLINK = /^\w+:\/\//;
|
||||
|
||||
const isCompressedMD = (data: string): boolean => {
|
||||
@@ -201,10 +206,10 @@ export function getMarkdownDrawingSection(
|
||||
compressed: boolean,
|
||||
) {
|
||||
return compressed
|
||||
? `%%\n# Drawing\n\x60\x60\x60compressed-json\n${compress(
|
||||
? `## Drawing\n\x60\x60\x60compressed-json\n${compress(
|
||||
jsonString,
|
||||
)}\n\x60\x60\x60\n%%`
|
||||
: `%%\n# Drawing\n\x60\x60\x60json\n${jsonString}\n\x60\x60\x60\n%%`;
|
||||
: `## Drawing\n\x60\x60\x60json\n${jsonString}\n\x60\x60\x60\n%%`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -237,30 +242,173 @@ const estimateMaxLineLen = (text: string, originalText: string): number => {
|
||||
const wrap = (text: string, lineLen: number) =>
|
||||
lineLen ? wrapTextAtCharLength(text, lineLen, false, 0) : text;
|
||||
|
||||
export const getExcalidrawMarkdownHeaderSection = (data:string, keys:[string,string][]):string => {
|
||||
let trimLocation = data.search(/(^%%\n)?# Text Elements\n/m);
|
||||
if (trimLocation == -1) {
|
||||
trimLocation = data.search(/(%%\n)?# Drawing\n/);
|
||||
//WITHSECTION refers to back of the card note (see this.inputEl.onkeyup in SelectCard.ts)
|
||||
const RE_EXCALIDRAWDATA_WITHSECTION_OK = new RegExp(`^#\n%%\n# Excalidraw Data(?:\n|$)`, "m");
|
||||
const RE_EXCALIDRAWDATA_WITHSECTION_NOTOK = new RegExp(`#\n%%\n# Excalidraw Data(?:\n|$)`, "m");
|
||||
const RE_EXCALIDRAWDATA_NOSECTION_OK = new RegExp(`^(%%\n)?# Excalidraw Data(?:\n|$)`, "m");
|
||||
|
||||
//WITHSECTION refers to back of the card note (see this.inputEl.onkeyup in SelectCard.ts)
|
||||
const RE_TEXTELEMENTS_WITHSECTION_OK = new RegExp(`^#\n%%\n##? Text Elements(?:\n|$)`, "m");
|
||||
const RE_TEXTELEMENTS_WITHSECTION_NOTOK = new RegExp(`#\n%%\n##? Text Elements(?:\n|$)`, "m");
|
||||
const RE_TEXTELEMENTS_NOSECTION_OK = new RegExp(`^(%%\n)?##? Text Elements(?:\n|$)`, "m");
|
||||
|
||||
|
||||
//The issue is that when editing in markdown embeds the user can delete the last enter causing two sections
|
||||
//to collide. This is particularly problematic when the user is editing the last section before # Text Elements
|
||||
const RE_EXCALIDRAWDATA_FALLBACK_1 = new RegExp(`(.*)%%\n# Excalidraw Data(?:\n|$)`, "m");
|
||||
const RE_EXCALIDRAWDATA_FALLBACK_2 = new RegExp(`(.*)# Excalidraw Data(?:\n|$)`, "m");
|
||||
|
||||
const RE_TEXTELEMENTS_FALLBACK_1 = new RegExp(`(.*)%%\n##? Text Elements(?:\n|$)`, "m");
|
||||
const RE_TEXTELEMENTS_FALLBACK_2 = new RegExp(`(.*)##? Text Elements(?:\n|$)`, "m");
|
||||
|
||||
|
||||
const RE_DRAWING = new RegExp(`(%%\n)?##? Drawing\n`);
|
||||
|
||||
export const getExcalidrawMarkdownHeaderSection = (data:string, keys?:[string,string][]):string => {
|
||||
//The base case scenario is at the top, continued with fallbacks in order of likelihood and file structure
|
||||
//change history for sake of backward compatibility
|
||||
|
||||
/* Expected markdown structure:
|
||||
bla bla bla
|
||||
#
|
||||
%%
|
||||
# Excalidraw Data
|
||||
*/
|
||||
let trimLocation = data.search(RE_EXCALIDRAWDATA_WITHSECTION_OK);
|
||||
let shouldFixTrailingHashtag = false;
|
||||
if(trimLocation > 0) {
|
||||
trimLocation += 2; //accounts for the "#\n" which I want to leave there untouched
|
||||
}
|
||||
if (trimLocation == -1) {
|
||||
return data;
|
||||
|
||||
/* Expected markdown structure (this happens when the user deletes the last empty line of the last back-of-the-card note):
|
||||
bla bla bla#
|
||||
%%
|
||||
# Excalidraw Data
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
trimLocation = data.search(RE_EXCALIDRAWDATA_WITHSECTION_NOTOK);
|
||||
if(trimLocation > 0) {
|
||||
shouldFixTrailingHashtag = true;
|
||||
}
|
||||
}
|
||||
/* Expected markdown structure
|
||||
a)
|
||||
bla bla bla
|
||||
%%
|
||||
# Excalidraw Data
|
||||
b)
|
||||
bla bla bla
|
||||
# Excalidraw Data
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
trimLocation = data.search(RE_EXCALIDRAWDATA_NOSECTION_OK);
|
||||
}
|
||||
/* Expected markdown structure:
|
||||
bla bla bla%%
|
||||
# Excalidraw Data
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
const res = data.match(RE_EXCALIDRAWDATA_FALLBACK_1);
|
||||
if(res && Boolean(res[1])) {
|
||||
trimLocation = res.index + res[1].length;
|
||||
}
|
||||
}
|
||||
/* Expected markdown structure:
|
||||
bla bla bla# Excalidraw Data
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
const res = data.match(RE_EXCALIDRAWDATA_FALLBACK_2);
|
||||
if(res && Boolean(res[1])) {
|
||||
trimLocation = res.index + res[1].length;
|
||||
}
|
||||
}
|
||||
/* Expected markdown structure:
|
||||
bla bla bla
|
||||
#
|
||||
%%
|
||||
# Text Elements
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
trimLocation = data.search(RE_TEXTELEMENTS_WITHSECTION_OK);
|
||||
if(trimLocation > 0) {
|
||||
trimLocation += 2; //accounts for the "#\n" which I want to leave there untouched
|
||||
}
|
||||
}
|
||||
/* Expected markdown structure:
|
||||
bla bla bla#
|
||||
%%
|
||||
# Text Elements
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
trimLocation = data.search(RE_TEXTELEMENTS_WITHSECTION_NOTOK);
|
||||
if(trimLocation > 0) {
|
||||
shouldFixTrailingHashtag = true;
|
||||
}
|
||||
}
|
||||
/* Expected markdown structure
|
||||
a)
|
||||
bla bla bla
|
||||
%%
|
||||
# Text Elements
|
||||
b)
|
||||
bla bla bla
|
||||
# Text Elements
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
trimLocation = data.search(RE_TEXTELEMENTS_NOSECTION_OK);
|
||||
}
|
||||
/* Expected markdown structure:
|
||||
bla bla bla%%
|
||||
# Text Elements
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
const res = data.match(RE_TEXTELEMENTS_FALLBACK_1);
|
||||
if(res && Boolean(res[1])) {
|
||||
trimLocation = res.index + res[1].length;
|
||||
}
|
||||
}
|
||||
/* Expected markdown structure:
|
||||
bla bla bla# Text Elements
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
const res = data.match(RE_TEXTELEMENTS_FALLBACK_2);
|
||||
if(res && Boolean(res[1])) {
|
||||
trimLocation = res.index + res[1].length;
|
||||
}
|
||||
}
|
||||
/* Expected markdown structure:
|
||||
a)
|
||||
bla bla bla
|
||||
# Drawing
|
||||
b)
|
||||
bla bla bla
|
||||
%%
|
||||
# Drawing
|
||||
*/
|
||||
if (trimLocation === -1) {
|
||||
trimLocation = data.search(RE_DRAWING);
|
||||
}
|
||||
if (trimLocation === -1) {
|
||||
return data.endsWith("\n") ? data : (data + "\n");
|
||||
}
|
||||
|
||||
let header = updateFrontmatterInString(data.substring(0, trimLocation),keys);
|
||||
//this should be removed at a later time. Left it here to remediate 1.4.9 mistake
|
||||
const REG_IMG = /(^---[\w\W]*?---\n)(!\[\[.*?]]\n(%%\n)?)/m; //(%%\n)? because of 1.4.8-beta... to be backward compatible with anyone who installed that version
|
||||
/*const REG_IMG = /(^---[\w\W]*?---\n)(!\[\[.*?]]\n(%%\n)?)/m; //(%%\n)? because of 1.4.8-beta... to be backward compatible with anyone who installed that version
|
||||
if (header.match(REG_IMG)) {
|
||||
header = header.replace(REG_IMG, "$1");
|
||||
}
|
||||
}*/
|
||||
//end of remove
|
||||
return header;
|
||||
return shouldFixTrailingHashtag
|
||||
? header + "\n#\n"
|
||||
: header.endsWith("\n") ? header : (header + "\n");
|
||||
}
|
||||
|
||||
|
||||
export class ExcalidrawData {
|
||||
public textElements: Map<
|
||||
string,
|
||||
{ raw: string; parsed: string; wrapAt: number | null }
|
||||
{ raw: string; parsed: string}
|
||||
> = null;
|
||||
public elementLinks: Map<string, string> = null;
|
||||
public scene: any = null;
|
||||
@@ -278,6 +426,7 @@ export class ExcalidrawData {
|
||||
private equations: Map<FileId, { latex: string; isLoaded: boolean }> = null; //fileId, path
|
||||
private mermaids: Map<FileId, { mermaid: string; isLoaded: boolean }> = null; //fileId, path
|
||||
private compatibilityMode: boolean = false;
|
||||
private textElementCommentedOut: boolean = false;
|
||||
selectedElementIds: {[key:string]:boolean} = {}; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/609
|
||||
|
||||
constructor(
|
||||
@@ -314,7 +463,10 @@ export class ExcalidrawData {
|
||||
map.set(item.id, item.type);
|
||||
alreadyHasText = true;
|
||||
} else {
|
||||
elements.find((el:ExcalidrawElement)=>el.id===item.id).containerId = null;
|
||||
const elementToClean = elements.find((el:ExcalidrawElement)=>el.id===item.id);
|
||||
if(elementToClean) { //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1600
|
||||
elementToClean.containerId = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
map.set(item.id, item.type);
|
||||
@@ -457,10 +609,10 @@ export class ExcalidrawData {
|
||||
this.selectedElementIds = {};
|
||||
this.textElements = new Map<
|
||||
string,
|
||||
{ raw: string; parsed: string; wrapAt: number }
|
||||
{ raw: string; parsed: string}
|
||||
>();
|
||||
this.elementLinks = new Map<string, string>();
|
||||
if (this.file != file) {
|
||||
if (this.file !== file) {
|
||||
//this is a reload - files, equations and mermaids will take care of reloading when needed
|
||||
this.files.clear();
|
||||
this.equations.clear();
|
||||
@@ -551,25 +703,84 @@ export class ExcalidrawData {
|
||||
|
||||
data = data.substring(0, sceneJSONandPOS.pos);
|
||||
|
||||
//The Markdown # Text Elements take priority over the JSON text elements. Assuming the scenario in which the link was updated due to filename changes
|
||||
//The Markdown # Text Elements take priority over the JSON text elements. Assuming the scenario in which the
|
||||
//link was updated due to filename changes
|
||||
//The .excalidraw JSON is modified to reflect the MD in case of difference
|
||||
//Read the text elements into the textElements Map
|
||||
let position = data.search(/(^%%\n)?# Text Elements\n/m);
|
||||
let position = data.search(RE_EXCALIDRAWDATA_NOSECTION_OK);
|
||||
if (position === -1) {
|
||||
//resillience in case back of the note was saved right on top of text elements
|
||||
// # back of note section
|
||||
// ....# Excalidraw Data
|
||||
// ....
|
||||
// --------------
|
||||
// instead of
|
||||
// --------------
|
||||
// # back of note section
|
||||
// ....
|
||||
// # Excalidraw Data
|
||||
position = data.search(RE_EXCALIDRAWDATA_FALLBACK_2);
|
||||
}
|
||||
|
||||
if(position === -1) {
|
||||
// # back of note section
|
||||
// ....
|
||||
// # Text Elements
|
||||
position = data.search(RE_TEXTELEMENTS_NOSECTION_OK);
|
||||
}
|
||||
|
||||
if (position === -1) {
|
||||
//resillience in case back of the note was saved right on top of text elements
|
||||
// # back of note section
|
||||
// ....# Text Elements
|
||||
// ....
|
||||
// --------------
|
||||
// instead of
|
||||
// --------------
|
||||
// # back of note section
|
||||
// ....
|
||||
// # Text Elements
|
||||
position = data.search(RE_TEXTELEMENTS_FALLBACK_2);
|
||||
}
|
||||
if (position === -1) {
|
||||
await this.setTextMode(textMode, false);
|
||||
this.loaded = true;
|
||||
return true; //Text Elements header does not exist
|
||||
}
|
||||
position += data.match(/((^%%\n)?# Text Elements\n)/m)[0].length;
|
||||
data = data.slice(position);
|
||||
const normalMatch = data.match(/^((%%\n)?# Excalidraw Data\n## Text Elements(?:\n|$))/m)
|
||||
??data.match(/^((%%\n)?##? Text Elements(?:\n|$))/m);
|
||||
|
||||
data = data.substring(position);
|
||||
const textElementsMatch = normalMatch
|
||||
? normalMatch[0]
|
||||
: data.match(/(.*##? Text Elements(?:\n|$))/m)[0];
|
||||
|
||||
data = data.slice(textElementsMatch.length);
|
||||
this.textElementCommentedOut = textElementsMatch.startsWith("%%\n");
|
||||
position = 0;
|
||||
let parts;
|
||||
|
||||
//load element links
|
||||
const elementLinkMap = new Map<string,string>();
|
||||
const indexOfNewElementLinks = data.indexOf("## Element Links\n");
|
||||
const lengthOfNewElementLinks = 17; //`## Element Links\n`.length
|
||||
const indexOfOldElementLinks = data.indexOf("# Element Links\n");
|
||||
const lengthOfOldElementLinks = 16; //`# Element Links\n`.length
|
||||
const elementLinksData = indexOfNewElementLinks>-1
|
||||
? data.substring(indexOfNewElementLinks + lengthOfNewElementLinks)
|
||||
: data.substring(indexOfOldElementLinks + lengthOfOldElementLinks);
|
||||
//Load Embedded files
|
||||
const RE_ELEMENT_LINKS = /^(.{8}):\s*(\[\[[^\]]*]])$/gm;
|
||||
const linksRes = elementLinksData.matchAll(RE_ELEMENT_LINKS);
|
||||
while (!(parts = linksRes.next()).done) {
|
||||
elementLinkMap.set(parts.value[1], parts.value[2]);
|
||||
}
|
||||
|
||||
//iterating through all the text elements in .md
|
||||
//Text elements always contain the raw value
|
||||
const BLOCKREF_LEN: number = " ^12345678\n\n".length;
|
||||
const BLOCKREF_LEN: number = 12; // " ^12345678\n\n".length;
|
||||
const RE_TEXT_ELEMENT_LINK = /^%%\*\*\*>>>text element-link:(\[\[[^<*\]]*]])<<<\*\*\*%%/gm;
|
||||
let res = data.matchAll(/\s\^(.{8})[\n]+/g);
|
||||
let parts;
|
||||
while (!(parts = res.next()).done) {
|
||||
let text = data.substring(position, parts.value.index);
|
||||
const id: string = parts.value[1];
|
||||
@@ -577,6 +788,7 @@ export class ExcalidrawData {
|
||||
if (textEl) {
|
||||
if (textEl.type !== "text") {
|
||||
//markdown link attached to elements
|
||||
//legacy fileformat support as of 2.0.26
|
||||
if (textEl.link !== text) {
|
||||
textEl.link = text;
|
||||
textEl.version++;
|
||||
@@ -584,20 +796,22 @@ export class ExcalidrawData {
|
||||
}
|
||||
this.elementLinks.set(id, text);
|
||||
} else {
|
||||
const wrapAt = estimateMaxLineLen(textEl.text, textEl.originalText);
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/566
|
||||
const elementLinkRes = text.matchAll(/^%%\*\*\*>>>text element-link:(\[\[[^<*\]]*]])<<<\*\*\*%%/gm);
|
||||
const elementLinkRes = text.matchAll(RE_TEXT_ELEMENT_LINK);
|
||||
const elementLink = elementLinkRes.next();
|
||||
if(!elementLink.done) {
|
||||
text = text.replace(/^%%\*\*\*>>>text element-link:\[\[[^<*\]]*]]<<<\*\*\*%%/gm,"");
|
||||
text = text.replace(RE_TEXT_ELEMENT_LINK,"");
|
||||
textEl.link = elementLink.value[1];
|
||||
}
|
||||
}
|
||||
if(elementLinkMap.has(id)) {
|
||||
textEl.link = elementLinkMap.get(id);
|
||||
elementLinkMap.delete(id);
|
||||
}
|
||||
const parseRes = await this.parse(text);
|
||||
textEl.rawText = text;
|
||||
this.textElements.set(id, {
|
||||
raw: text,
|
||||
parsed: parseRes.parsed,
|
||||
wrapAt,
|
||||
});
|
||||
if (parseRes.link) {
|
||||
textEl.link = parseRes.link;
|
||||
@@ -611,54 +825,72 @@ export class ExcalidrawData {
|
||||
position = parts.value.index + BLOCKREF_LEN;
|
||||
}
|
||||
|
||||
data = data.substring(
|
||||
data.indexOf("# Embedded files\n") + "# Embedded files\n".length,
|
||||
);
|
||||
//Load Embedded files
|
||||
const REG_FILEID_FILEPATH = /([\w\d]*):\s*\[\[([^\]]*)]]\s?(\{[^}]*})?\n/gm;
|
||||
res = data.matchAll(REG_FILEID_FILEPATH);
|
||||
while (!(parts = res.next()).done) {
|
||||
const embeddedFile = new EmbeddedFile(
|
||||
this.plugin,
|
||||
this.file.path,
|
||||
parts.value[2],
|
||||
parts.value[3],
|
||||
);
|
||||
this.setFile(parts.value[1] as FileId, embeddedFile);
|
||||
//In theory only non-text elements should be left in the elementLinkMap
|
||||
//new file format from 2.0.26
|
||||
for (const [id, link] of elementLinkMap) {
|
||||
const textEl = this.scene.elements.filter((el: any) => el.id === id)[0];
|
||||
if (textEl) {
|
||||
textEl.link = link;
|
||||
textEl.version++;
|
||||
textEl.versionNonce++;
|
||||
this.elementLinks.set(id, link);
|
||||
}
|
||||
}
|
||||
|
||||
//Load links
|
||||
const REG_LINKID_FILEPATH = /([\w\d]*):\s*((?:https?|file|ftps?):\/\/[^\s]*)\n/gm;
|
||||
res = data.matchAll(REG_LINKID_FILEPATH);
|
||||
while (!(parts = res.next()).done) {
|
||||
const embeddedFile = new EmbeddedFile(
|
||||
this.plugin,
|
||||
null,
|
||||
parts.value[2],
|
||||
);
|
||||
this.setFile(parts.value[1] as FileId, embeddedFile);
|
||||
}
|
||||
const indexOfNewEmbeddedFiles = data.indexOf("## Embedded Files\n");
|
||||
const embeddedFilesNewLength = 18; //"## Embedded Files\n".length
|
||||
const indexOfOldEmbeddedFiles = data.indexOf("# Embedded files\n");
|
||||
const embeddedFilesOldLength = 17; //"# Embedded files\n".length
|
||||
|
||||
//Load Equations
|
||||
const REG_FILEID_EQUATION = /([\w\d]*):\s*\$\$([\s\S]*?)(\$\$\s*\n)/gm;
|
||||
res = data.matchAll(REG_FILEID_EQUATION);
|
||||
while (!(parts = res.next()).done) {
|
||||
this.setEquation(parts.value[1] as FileId, {
|
||||
latex: parts.value[2],
|
||||
isLoaded: false,
|
||||
});
|
||||
}
|
||||
if(indexOfNewEmbeddedFiles>-1 || indexOfOldEmbeddedFiles>-1) {
|
||||
data = indexOfNewEmbeddedFiles>-1
|
||||
? data.substring(indexOfNewEmbeddedFiles + embeddedFilesNewLength)
|
||||
: data.substring(indexOfOldEmbeddedFiles + embeddedFilesOldLength);
|
||||
//Load Embedded files
|
||||
const REG_FILEID_FILEPATH = /([\w\d]*):\s*\[\[([^\]]*)]]\s?(\{[^}]*})?\n/gm;
|
||||
res = data.matchAll(REG_FILEID_FILEPATH);
|
||||
while (!(parts = res.next()).done) {
|
||||
const embeddedFile = new EmbeddedFile(
|
||||
this.plugin,
|
||||
this.file.path,
|
||||
parts.value[2],
|
||||
parts.value[3],
|
||||
);
|
||||
this.setFile(parts.value[1] as FileId, embeddedFile);
|
||||
}
|
||||
|
||||
//Load Mermaids
|
||||
const mermaidElements = getMermaidImageElements(this.scene.elements);
|
||||
if(mermaidElements.length>0 && !shouldRenderMermaid()) {
|
||||
new Notice ("Mermaid images are only supported in Obsidian 1.4.14 and above. Please update Obsidian to see the mermaid images in this drawing. Obsidian mobile 1.4.14 currently only avaiable to Obsidian insiders", 5000);
|
||||
} else {
|
||||
mermaidElements.forEach(el =>
|
||||
this.setMermaid(el.fileId, {mermaid: getMermaidText(el), isLoaded: false})
|
||||
);
|
||||
}
|
||||
//Load links
|
||||
const REG_LINKID_FILEPATH = /([\w\d]*):\s*((?:https?|file|ftps?):\/\/[^\s]*)\n/gm;
|
||||
res = data.matchAll(REG_LINKID_FILEPATH);
|
||||
while (!(parts = res.next()).done) {
|
||||
const embeddedFile = new EmbeddedFile(
|
||||
this.plugin,
|
||||
null,
|
||||
parts.value[2],
|
||||
);
|
||||
this.setFile(parts.value[1] as FileId, embeddedFile);
|
||||
}
|
||||
|
||||
//Load Equations
|
||||
const REG_FILEID_EQUATION = /([\w\d]*):\s*\$\$([\s\S]*?)(\$\$\s*\n)/gm;
|
||||
res = data.matchAll(REG_FILEID_EQUATION);
|
||||
while (!(parts = res.next()).done) {
|
||||
this.setEquation(parts.value[1] as FileId, {
|
||||
latex: parts.value[2],
|
||||
isLoaded: false,
|
||||
});
|
||||
}
|
||||
|
||||
//Load Mermaids
|
||||
const mermaidElements = getMermaidImageElements(this.scene.elements);
|
||||
if(mermaidElements.length>0 && !shouldRenderMermaid()) {
|
||||
new Notice ("Mermaid images are only supported in Obsidian 1.4.14 and above. Please update Obsidian to see the mermaid images in this drawing. Obsidian mobile 1.4.14 currently only avaiable to Obsidian insiders", 5000);
|
||||
} else {
|
||||
mermaidElements.forEach(el =>
|
||||
this.setMermaid(el.fileId, {mermaid: getMermaidText(el), isLoaded: false})
|
||||
);
|
||||
}
|
||||
}
|
||||
//Check to see if there are text elements in the JSON that were missed from the # Text Elements section
|
||||
//e.g. if the entire text elements section was deleted.
|
||||
this.findNewTextElementsInScene();
|
||||
@@ -678,7 +910,7 @@ export class ExcalidrawData {
|
||||
this.file = file;
|
||||
this.textElements = new Map<
|
||||
string,
|
||||
{ raw: string; parsed: string; wrapAt: number }
|
||||
{ raw: string; parsed: string}
|
||||
>();
|
||||
this.elementLinks = new Map<string, string>();
|
||||
this.setShowLinkBrackets();
|
||||
@@ -709,34 +941,6 @@ export class ExcalidrawData {
|
||||
await this.updateSceneTextElements(forceupdate);
|
||||
}
|
||||
|
||||
//update a single text element in the scene if the newText is different
|
||||
public updateTextElement(
|
||||
sceneTextElement: any,
|
||||
newText: string,
|
||||
newOriginalText: string,
|
||||
forceUpdate: boolean = false,
|
||||
containerType?: string,
|
||||
) {
|
||||
if (forceUpdate || newText != sceneTextElement.text) {
|
||||
const measure = _measureText(
|
||||
newText,
|
||||
sceneTextElement.fontSize,
|
||||
sceneTextElement.fontFamily,
|
||||
sceneTextElement.lineHeight??getDefaultLineHeight(sceneTextElement.fontFamily),
|
||||
);
|
||||
sceneTextElement.text = newText;
|
||||
sceneTextElement.originalText = newOriginalText;
|
||||
|
||||
if (!sceneTextElement.containerId || containerType==="arrow") {
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/376
|
||||
//I leave the setting of text width to excalidraw, when text is in a container
|
||||
//because text width is fixed to the container width
|
||||
sceneTextElement.width = measure.w;
|
||||
}
|
||||
sceneTextElement.height = measure.h;
|
||||
sceneTextElement.baseline = measure.baseline;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the TextElements in the Excalidraw scene based on textElements MAP in ExcalidrawData
|
||||
@@ -747,26 +951,27 @@ export class ExcalidrawData {
|
||||
private async updateSceneTextElements(forceupdate: boolean = false) {
|
||||
//update text in scene based on textElements Map
|
||||
//first get scene text elements
|
||||
const texts = this.scene.elements?.filter((el: any) => el.type === "text");
|
||||
const elementsMap = arrayToMap(this.scene.elements);
|
||||
const texts = this.scene.elements?.filter((el: any) => el.type === "text") as Mutable<ExcalidrawTextElement>[];
|
||||
for (const te of texts) {
|
||||
const container = getContainerElement(te,this.scene);
|
||||
const container = getContainerElement(te, elementsMap);
|
||||
const originalText =
|
||||
(await this.getText(te.id)) ?? te.originalText ?? te.text;
|
||||
const wrapAt = this.textElements.get(te.id)?.wrapAt;
|
||||
const {text, x, y, width, height} = refreshTextDimensions(
|
||||
te,
|
||||
container,
|
||||
elementsMap,
|
||||
originalText,
|
||||
)
|
||||
try { //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1062
|
||||
this.updateTextElement(
|
||||
te,
|
||||
wrapAt ? wrapText(
|
||||
originalText,
|
||||
getFontString({fontSize: te.fontSize, fontFamily: te.fontFamily}),
|
||||
getBoundTextMaxWidth(container as any)
|
||||
) : originalText,
|
||||
originalText,
|
||||
forceupdate,
|
||||
container?.type,
|
||||
); //(await this.getText(te.id))??te.text serves the case when the whole #Text Elements section is deleted by accident
|
||||
te.originalText = originalText;
|
||||
te.text = text;
|
||||
te.x = x;
|
||||
te.y = y;
|
||||
te.width = width;
|
||||
te.height = height;
|
||||
} catch(e) {
|
||||
debug({where: "ExcalidrawData.updateSceneTextElements", fn: this.updateSceneTextElements, textElement: te});
|
||||
debug(`ExcalidrawData.updateSceneTextElements, textElement: ${te?.id}`, te, this.updateSceneTextElements);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -783,7 +988,6 @@ export class ExcalidrawData {
|
||||
this.textElements.set(id, {
|
||||
raw: text.raw,
|
||||
parsed: (await this.parse(text.raw)).parsed,
|
||||
wrapAt: text.wrapAt,
|
||||
});
|
||||
}
|
||||
//console.log("parsed",this.textElements.get(id).parsed);
|
||||
@@ -819,7 +1023,7 @@ export class ExcalidrawData {
|
||||
if (el.id.length > 8) {
|
||||
result = true;
|
||||
id = nanoid();
|
||||
jsonString = jsonString.replaceAll(el.id, id); //brute force approach to replace all occurances (e.g. links, groups,etc.)
|
||||
jsonString = jsonString.replaceAll(el.id, id); //brute force approach to replace all occurrences (e.g. links, groups,etc.)
|
||||
}
|
||||
this.elementLinks.set(id, el.link);
|
||||
}
|
||||
@@ -853,28 +1057,25 @@ export class ExcalidrawData {
|
||||
delete this.selectedElementIds[te.id];
|
||||
this.selectedElementIds[id] = true;
|
||||
}
|
||||
jsonString = jsonString.replaceAll(te.id, id); //brute force approach to replace all occurances (e.g. links, groups,etc.)
|
||||
jsonString = jsonString.replaceAll(te.id, id); //brute force approach to replace all occurrences (e.g. links, groups,etc.)
|
||||
if (this.textElements.has(te.id)) {
|
||||
//element was created with onBeforeTextSubmit
|
||||
const text = this.textElements.get(te.id);
|
||||
this.textElements.set(id, {
|
||||
raw: text.raw,
|
||||
parsed: text.parsed,
|
||||
wrapAt: text.wrapAt,
|
||||
});
|
||||
this.textElements.delete(te.id); //delete the old ID from the Map
|
||||
}
|
||||
if (!this.textElements.has(id)) {
|
||||
const raw = te.rawText && te.rawText !== "" ? te.rawText : te.text; //this is for compatibility with drawings created before the rawText change on ExcalidrawTextElement
|
||||
const wrapAt = estimateMaxLineLen(te.text, te.originalText);
|
||||
this.textElements.set(id, { raw, parsed: null, wrapAt });
|
||||
this.parseasync(id, raw, wrapAt);
|
||||
this.textElements.set(id, { raw, parsed: null});
|
||||
this.parseasync(id, raw);
|
||||
}
|
||||
} else if (!this.textElements.has(te.id)) {
|
||||
const raw = te.rawText && te.rawText !== "" ? te.rawText : te.text; //this is for compatibility with drawings created before the rawText change on ExcalidrawTextElement
|
||||
const wrapAt = estimateMaxLineLen(te.text, te.originalText);
|
||||
this.textElements.set(id, { raw, parsed: null, wrapAt });
|
||||
this.parseasync(id, raw, wrapAt);
|
||||
this.textElements.set(id, { raw, parsed: null});
|
||||
this.parseasync(id, raw);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -922,22 +1123,19 @@ export class ExcalidrawData {
|
||||
? el[0].rawText
|
||||
: (el[0].originalText ?? el[0].text);
|
||||
if (text !== (el[0].originalText ?? el[0].text)) {
|
||||
const wrapAt = estimateMaxLineLen(el[0].text, el[0].originalText);
|
||||
this.textElements.set(key, {
|
||||
raw,
|
||||
parsed: (await this.parse(raw)).parsed,
|
||||
wrapAt,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async parseasync(key: string, raw: string, wrapAt: number) {
|
||||
private async parseasync(key: string, raw: string) {
|
||||
this.textElements.set(key, {
|
||||
raw,
|
||||
parsed: (await this.parse(raw)).parsed,
|
||||
wrapAt,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1127,27 +1325,37 @@ export class ExcalidrawData {
|
||||
*/
|
||||
disableCompression: boolean = false;
|
||||
generateMD(deletedElements: ExcalidrawElement[] = []): string {
|
||||
let outString = "# Text Elements\n";
|
||||
let outString = this.textElementCommentedOut ? "%%\n" : "";
|
||||
outString += `# Excalidraw Data\n## Text Elements\n`;
|
||||
const textElementLinks = new Map<string, string>();
|
||||
for (const key of this.textElements.keys()) {
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/566
|
||||
const element = this.scene.elements.filter((el:any)=>el.id===key);
|
||||
let elementString = this.textElements.get(key).raw;
|
||||
if(element && element.length===1 && element[0].link && element[0].rawText === element[0].originalText) {
|
||||
if(element[0].link.match(/^\[\[[^\]]*]]$/g)) { //apply this only to markdown links
|
||||
elementString = `%%***>>>text element-link:${element[0].link}<<<***%%` + elementString;
|
||||
textElementLinks.set(key, element[0].link);
|
||||
//elementString = `%%***>>>text element-link:${element[0].link}<<<***%%` + elementString;
|
||||
}
|
||||
}
|
||||
outString += `${elementString} ^${key}\n\n`;
|
||||
}
|
||||
|
||||
for (const key of this.elementLinks.keys()) {
|
||||
outString += `${this.elementLinks.get(key)} ^${key}\n\n`;
|
||||
if (this.elementLinks.size > 0 || textElementLinks.size > 0) {
|
||||
outString += `## Element Links\n`;
|
||||
for (const key of this.elementLinks.keys()) {
|
||||
outString += `${key}: ${this.elementLinks.get(key)}\n`;
|
||||
}
|
||||
for (const key of textElementLinks.keys()) {
|
||||
outString += `${key}: ${textElementLinks.get(key)}\n`;
|
||||
}
|
||||
outString += "\n";
|
||||
}
|
||||
|
||||
// deliberately not adding mermaids to here. It is enough to have the mermaidText in the image element's customData
|
||||
outString +=
|
||||
this.equations.size > 0 || this.files.size > 0
|
||||
? "\n# Embedded files\n"
|
||||
? "## Embedded Files\n"
|
||||
: "";
|
||||
if (this.equations.size > 0) {
|
||||
for (const key of this.equations.keys()) {
|
||||
@@ -1182,6 +1390,7 @@ export class ExcalidrawData {
|
||||
}, null, "\t");
|
||||
return (
|
||||
outString +
|
||||
(this.textElementCommentedOut ? "" : "%%\n") +
|
||||
getMarkdownDrawingSection(
|
||||
sceneJSONstring,
|
||||
this.disableCompression ? false : this.plugin.settings.compress,
|
||||
@@ -1286,14 +1495,18 @@ export class ExcalidrawData {
|
||||
const processedIds = new Set<string>();
|
||||
fileIds.forEach((fileId,idx)=>{
|
||||
if(processedIds.has(fileId)) {
|
||||
const file = this.getFile(fileId);
|
||||
const embeddedFile = this.getFile(fileId);
|
||||
const equation = this.getEquation(fileId);
|
||||
const mermaid = this.getMermaid(fileId);
|
||||
|
||||
|
||||
|
||||
//images should have a single reference, but equations, and markdown embeds should have as many as instances of the file in the scene
|
||||
if(file && (file.isHyperLink || file.isLocalLink || (file.file && (file.file.extension !== "md" || this.plugin.isExcalidrawFile(file.file))))) {
|
||||
if (embeddedFile &&
|
||||
(embeddedFile.isHyperLink || embeddedFile.isLocalLink ||
|
||||
(embeddedFile.file &&
|
||||
(embeddedFile.file.extension !== "md" || this.plugin.isExcalidrawFile(embeddedFile.file))
|
||||
)
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if(mermaid) {
|
||||
@@ -1305,6 +1518,11 @@ export class ExcalidrawData {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!embeddedFile && !equation && !mermaid) {
|
||||
//processing freshly pasted images from likely anotehr instance of excalidraw (e.g. Excalidraw.com, or another Obsidian instance)
|
||||
return;
|
||||
}
|
||||
|
||||
const newId = fileid();
|
||||
(scene
|
||||
.elements
|
||||
@@ -1313,8 +1531,8 @@ export class ExcalidrawData {
|
||||
.fileId = newId;
|
||||
dirty = true;
|
||||
processedIds.add(newId);
|
||||
if(file) {
|
||||
this.setFile(newId as FileId,new EmbeddedFile(this.plugin,this.file.path,file.linkParts.original));
|
||||
if(embeddedFile) {
|
||||
this.setFile(newId as FileId,new EmbeddedFile(this.plugin,this.file.path,embeddedFile.linkParts.original));
|
||||
}
|
||||
if(equation) {
|
||||
this.setEquation(newId as FileId, {latex:equation.latex, isLoaded:false});
|
||||
@@ -1384,12 +1602,12 @@ export class ExcalidrawData {
|
||||
* @param id
|
||||
* @returns
|
||||
*/
|
||||
public getParsedText(id: string): [parseResultWrapped: string, parseResultOriginal: string, link: string] {
|
||||
public getParsedText(id: string): string {
|
||||
const t = this.textElements.get(id);
|
||||
if (!t) {
|
||||
return [null, null, null];
|
||||
return null;
|
||||
}
|
||||
return [wrap(t.parsed, t.wrapAt), t.parsed, null];
|
||||
return t.parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1406,24 +1624,23 @@ export class ExcalidrawData {
|
||||
* @param rawText
|
||||
* @param rawOriginalText
|
||||
* @param updateSceneCallback
|
||||
* @returns [parseResultWrapped: string, parseResultOriginal: string, link: string]
|
||||
* @returns [parseResultOriginal: string, link: string]
|
||||
*/
|
||||
public setTextElement(
|
||||
elementID: string,
|
||||
rawText: string,
|
||||
rawOriginalText: string,
|
||||
updateSceneCallback: Function,
|
||||
): [parseResultWrapped: string, parseResultOriginal: string, link: string] {
|
||||
const maxLineLen = estimateMaxLineLen(rawText, rawOriginalText);
|
||||
|
||||
): [parseResultOriginal: string, link: string] {
|
||||
//const maxLineLen = estimateMaxLineLen(rawText, rawOriginalText);
|
||||
const [parseResult, link] = this.quickParse(rawOriginalText); //will return the parsed result if raw text does not include transclusion
|
||||
if (parseResult) {
|
||||
//No transclusion
|
||||
this.textElements.set(elementID, {
|
||||
raw: rawOriginalText,
|
||||
parsed: parseResult,
|
||||
wrapAt: maxLineLen,
|
||||
});
|
||||
return [wrap(parseResult, maxLineLen), parseResult, link];
|
||||
return [parseResult, link];
|
||||
}
|
||||
//transclusion needs to be resolved asynchornously
|
||||
this.parse(rawOriginalText).then((parseRes) => {
|
||||
@@ -1431,35 +1648,28 @@ export class ExcalidrawData {
|
||||
this.textElements.set(elementID, {
|
||||
raw: rawOriginalText,
|
||||
parsed: parsedText,
|
||||
wrapAt: maxLineLen,
|
||||
});
|
||||
if (parsedText) {
|
||||
updateSceneCallback(wrap(parsedText, maxLineLen), parsedText);
|
||||
updateSceneCallback(parsedText);
|
||||
}
|
||||
});
|
||||
return [null, null, null];
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
public async addTextElement(
|
||||
elementID: string,
|
||||
rawText: string,
|
||||
rawOriginalText: string,
|
||||
): Promise<[string, string, string]> {
|
||||
let wrapAt: number = estimateMaxLineLen(rawText, rawOriginalText);
|
||||
if (this.textElements.has(elementID)) {
|
||||
wrapAt = this.textElements.get(elementID).wrapAt;
|
||||
}
|
||||
): Promise<{parseResult: string, link:string}> {
|
||||
const parseResult = await this.parse(rawOriginalText);
|
||||
this.textElements.set(elementID, {
|
||||
raw: rawOriginalText,
|
||||
parsed: parseResult.parsed,
|
||||
wrapAt,
|
||||
});
|
||||
return [
|
||||
wrap(parseResult.parsed, wrapAt),
|
||||
parseResult.parsed,
|
||||
parseResult.link,
|
||||
];
|
||||
return {
|
||||
parseResult: parseResult.parsed,
|
||||
link: parseResult.link,
|
||||
};
|
||||
}
|
||||
|
||||
public deleteTextElement(id: string) {
|
||||
@@ -1473,7 +1683,8 @@ export class ExcalidrawData {
|
||||
: this.plugin.settings.defaultMode;
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["default-mode"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["default-mode"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["default-mode"].name] !== "undefined")
|
||||
) {
|
||||
mode = fileCache.frontmatter[FRONTMATTER_KEYS["default-mode"].name];
|
||||
}
|
||||
@@ -1493,7 +1704,8 @@ export class ExcalidrawData {
|
||||
let opacity = this.plugin.settings.linkOpacity;
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["linkbutton-opacity"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["linkbutton-opacity"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["linkbutton-opacity"].name] !== "undefined")
|
||||
) {
|
||||
opacity = fileCache.frontmatter[FRONTMATTER_KEYS["linkbutton-opacity"].name];
|
||||
}
|
||||
@@ -1504,7 +1716,8 @@ export class ExcalidrawData {
|
||||
const fileCache = this.app.metadataCache.getFileCache(this.file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["onload-script"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["onload-script"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["onload-script"].name] !== "undefined")
|
||||
) {
|
||||
return fileCache.frontmatter[FRONTMATTER_KEYS["onload-script"].name];
|
||||
}
|
||||
@@ -1516,13 +1729,13 @@ export class ExcalidrawData {
|
||||
const fileCache = this.app.metadataCache.getFileCache(this.file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["link-prefix"].name] != null
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["link-prefix"].name] !== "undefined")
|
||||
) {
|
||||
this.linkPrefix = fileCache.frontmatter[FRONTMATTER_KEYS["link-prefix"].name];
|
||||
this.linkPrefix = fileCache.frontmatter[FRONTMATTER_KEYS["link-prefix"].name]??"";
|
||||
} else {
|
||||
this.linkPrefix = this.plugin.settings.linkPrefix;
|
||||
}
|
||||
return linkPrefix != this.linkPrefix;
|
||||
return linkPrefix !== this.linkPrefix;
|
||||
}
|
||||
|
||||
private setUrlPrefix(): boolean {
|
||||
@@ -1530,20 +1743,21 @@ export class ExcalidrawData {
|
||||
const fileCache = this.app.metadataCache.getFileCache(this.file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["url-prefix"].name] != null
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["url-prefix"].name] !== "undefined")
|
||||
) {
|
||||
this.urlPrefix = fileCache.frontmatter[FRONTMATTER_KEYS["url-prefix"].name];
|
||||
this.urlPrefix = fileCache.frontmatter[FRONTMATTER_KEYS["url-prefix"].name]??"";
|
||||
} else {
|
||||
this.urlPrefix = this.plugin.settings.urlPrefix;
|
||||
}
|
||||
return urlPrefix != this.urlPrefix;
|
||||
return urlPrefix !== this.urlPrefix;
|
||||
}
|
||||
|
||||
private setAutoexportPreferences() {
|
||||
const fileCache = this.app.metadataCache.getFileCache(this.file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["autoexport"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["autoexport"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["autoexport"].name] !== "undefined")
|
||||
) {
|
||||
switch ((fileCache.frontmatter[FRONTMATTER_KEYS["autoexport"].name]).toLowerCase()) {
|
||||
case "none": this.autoexportPreference = AutoexportPreference.none; break;
|
||||
@@ -1562,7 +1776,8 @@ export class ExcalidrawData {
|
||||
const fileCache = this.app.metadataCache.getFileCache(this.file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["iframe-theme"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["iframe-theme"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["iframe-theme"].name] !== "undefined")
|
||||
) {
|
||||
this.embeddableTheme = fileCache.frontmatter[FRONTMATTER_KEYS["iframe-theme"].name].toLowerCase();
|
||||
if (!EMBEDDABLE_THEME_FRONTMATTER_VALUES.includes(this.embeddableTheme)) {
|
||||
@@ -1571,7 +1786,7 @@ export class ExcalidrawData {
|
||||
} else {
|
||||
this.embeddableTheme = this.plugin.settings.iframeMatchExcalidrawTheme ? "auto" : "default";
|
||||
}
|
||||
return embeddableTheme != this.embeddableTheme;
|
||||
return embeddableTheme !== this.embeddableTheme;
|
||||
}
|
||||
|
||||
private setShowLinkBrackets(): boolean {
|
||||
@@ -1579,14 +1794,15 @@ export class ExcalidrawData {
|
||||
const fileCache = this.app.metadataCache.getFileCache(this.file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["link-brackets"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["link-brackets"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["link-brackets"].name] !== "undefined")
|
||||
) {
|
||||
this.showLinkBrackets =
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["link-brackets"].name] != false;
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["link-brackets"].name] !== false;
|
||||
} else {
|
||||
this.showLinkBrackets = this.plugin.settings.showLinkBrackets;
|
||||
}
|
||||
return showLinkBrackets != this.showLinkBrackets;
|
||||
return showLinkBrackets !== this.showLinkBrackets;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1819,7 +2035,7 @@ export const getTransclusion = async (
|
||||
{ isCancelled: () => false },
|
||||
file,
|
||||
)
|
||||
).blocks.filter((block: any) => block.node.type != "comment");
|
||||
).blocks.filter((block: any) => block.node.type !== "comment");
|
||||
if (!blocks) {
|
||||
return { contents: linkParts.original.trim(), lineNum: 0 };
|
||||
}
|
||||
|
||||
20
src/ExcalidrawLib.d.ts
vendored
20
src/ExcalidrawLib.d.ts
vendored
@@ -1,7 +1,7 @@
|
||||
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 { ElementsMap, ExcalidrawBindableElement, ExcalidrawElement, ExcalidrawFrameElement, ExcalidrawTextElement, FontFamilyValues, FontString, NonDeleted, NonDeletedExcalidrawElement, Theme } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ElementsMap, ExcalidrawBindableElement, ExcalidrawElement, ExcalidrawFrameElement, ExcalidrawTextContainer, 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 { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
|
||||
@@ -87,6 +87,24 @@ 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,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@ import {
|
||||
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";
|
||||
@@ -45,11 +45,20 @@ let metadataCache: MetadataCache;
|
||||
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;
|
||||
@@ -77,7 +86,7 @@ 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};
|
||||
|
||||
if(cacheReady) {
|
||||
const src = await imageCache.getImageFromCache(cacheKey);
|
||||
@@ -123,9 +132,14 @@ 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
|
||||
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 +163,7 @@ 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};
|
||||
const cacheKey = {...filenameParts, isDark: theme==="dark", previewImageType: PreviewImageType.SVGIMG, scale:1, isTransparent: !exportSettings.withBackground};
|
||||
if(cacheReady) {
|
||||
const src = await imageCache.getImageFromCache(cacheKey);
|
||||
if(src && typeof src === "string") {
|
||||
@@ -206,13 +220,13 @@ 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};
|
||||
const cacheKey = {...filenameParts, isDark: theme==="dark", previewImageType: PreviewImageType.SVG, scale:1, isTransparent: !exportSettings.withBackground};
|
||||
let maybeSVG;
|
||||
if(cacheReady) {
|
||||
maybeSVG = await imageCache.getImageFromCache(cacheKey);
|
||||
}
|
||||
|
||||
let svg = maybeSVG && (maybeSVG instanceof SVGSVGElement)
|
||||
let svg = (maybeSVG && (maybeSVG instanceof SVGSVGElement))
|
||||
? maybeSVG
|
||||
: convertSVGStringToElement((await createSVG(
|
||||
filenameParts.hasGroupref || filenameParts.hasBlockref || filenameParts.hasSectionref || filenameParts.hasFrameref
|
||||
@@ -363,7 +377,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;
|
||||
@@ -403,7 +417,10 @@ const createImgElement = async (
|
||||
});
|
||||
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 = setTimeout(()=>clickEvent(ev),timeoutValue);
|
||||
pointerDownEvent = ev;
|
||||
});
|
||||
eventElement.addEventListener("pointerup",()=>{
|
||||
@@ -510,9 +527,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);
|
||||
@@ -573,8 +592,18 @@ const tmpObsidianWYSIWYG = async (
|
||||
|
||||
//@ts-ignore
|
||||
const containerEl = ctx.containerEl;
|
||||
|
||||
if(!plugin.settings.renderImageInMarkdownReadingMode && containerEl.parentElement?.parentElement?.hasClass("markdown-reading-view")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!plugin.settings.renderImageInMarkdownToPDF && 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") &&
|
||||
@@ -594,23 +623,41 @@ const tmpObsidianWYSIWYG = async (
|
||||
return; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/835
|
||||
}
|
||||
|
||||
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 = internalEmbedDiv.hasClass("markdown-reading-view") || isPrinting;
|
||||
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=)[^ ]*)/)?.[1];
|
||||
if(ref) {
|
||||
attr.fname = file.path + ref;
|
||||
areaPreview = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if(!isFrontmatterDiv && !areaPreview) {
|
||||
if(el.parentElement === containerEl) containerEl.removeChild(el);
|
||||
return;
|
||||
}
|
||||
@@ -667,7 +714,7 @@ const tmpObsidianWYSIWYG = async (
|
||||
internalEmbedDiv.appendChild(imgDiv);
|
||||
}, 500);
|
||||
}
|
||||
const observer = isDebugMode
|
||||
const observer = isDebugMode()
|
||||
? new CustomMutationObserver(markdownObserverFn, "markdowPostProcessorObserverFn")
|
||||
: new MutationObserver(markdownObserverFn);
|
||||
observer.observe(internalEmbedDiv, {
|
||||
@@ -698,7 +745,8 @@ 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) {
|
||||
const isPrinting = Boolean(document.body.querySelectorAll("body > .print"));
|
||||
if (excalidrawFile && !isPrinting) {
|
||||
el.style.display = "none";
|
||||
return;
|
||||
}
|
||||
@@ -760,10 +808,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 +823,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 +848,7 @@ const legacyExcalidrawPopoverObserverFn: MutationCallback = async (m) => {
|
||||
node.appendChild(div);
|
||||
};
|
||||
|
||||
export const legacyExcalidrawPopoverObserver = isDebugMode
|
||||
export const legacyExcalidrawPopoverObserver = isDebugMode()
|
||||
? new CustomMutationObserver(legacyExcalidrawPopoverObserverFn, "legacyExcalidrawPopoverObserverFn")
|
||||
: new MutationObserver(legacyExcalidrawPopoverObserverFn);
|
||||
|
||||
|
||||
@@ -172,6 +172,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,6 +213,7 @@ export class ScriptEngine {
|
||||
if (!view || !script || !title) {
|
||||
return;
|
||||
}
|
||||
script = script.replace(/^---.*?---\n/gs, "");
|
||||
const ea = getEA(view);
|
||||
ea.activeScript = title;
|
||||
|
||||
|
||||
@@ -2,8 +2,19 @@ import { customAlphabet } from "nanoid";
|
||||
import { DeviceType } from "../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;
|
||||
};
|
||||
const MD_EXCALIDRAW = "# Excalidraw Data";
|
||||
const MD_TEXTELEMENTS = "## Text Elements";
|
||||
const MD_DRAWING = "## Drawing";
|
||||
const MD_ELEMENTLINKS = "## Element Links";
|
||||
const MD_EMBEDFILES = "## Embedded Files";
|
||||
export const MD_EX_SECTIONS = [MD_EXCALIDRAW, MD_TEXTELEMENTS, MD_DRAWING, MD_ELEMENTLINKS, MD_EMBEDFILES];
|
||||
|
||||
export const ERROR_IFRAME_CONVERSION_CANCELED = "iframe conversion canceled";
|
||||
|
||||
@@ -82,6 +93,8 @@ export const {
|
||||
mutateElement,
|
||||
restore,
|
||||
mermaidToExcalidraw,
|
||||
getContainerElement,
|
||||
refreshTextDimensions,
|
||||
} = excalidrawLib;
|
||||
|
||||
export function JSON_parse(x: string): any {
|
||||
@@ -152,6 +165,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 +176,9 @@ 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"},
|
||||
"open-as-markdown": {name: "excalidraw-open-md", type: "checkbox"},
|
||||
};
|
||||
|
||||
export const EMBEDDABLE_THEME_FRONTMATTER_VALUES = ["light", "dark", "auto", "dafault"];
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -89,7 +89,7 @@ function RenderObsidianView(
|
||||
const react = view.plugin.getPackage(view.ownerWindow).react;
|
||||
|
||||
//@ts-ignore
|
||||
const leafRef = react.useRef<{leaf: WorkspaceLeaf; node?: ObsidianCanvasNode} | null>(null);
|
||||
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);
|
||||
@@ -154,7 +154,7 @@ 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(() => {
|
||||
if(!containerRef?.current) {
|
||||
@@ -176,7 +176,8 @@ function RenderObsidianView(
|
||||
rootSplit.containerEl.style.borderRadius = "var(--embeddable-radius)";
|
||||
leafRef.current = {
|
||||
leaf: app.workspace.createLeafInParent(rootSplit, 0),
|
||||
node: null
|
||||
node: null,
|
||||
editNode: null,
|
||||
};
|
||||
|
||||
const setKeepOnTop = () => {
|
||||
@@ -230,6 +231,9 @@ function RenderObsidianView(
|
||||
return () => {}; //cleanup on unmount
|
||||
}, [linkText, subpath, containerRef]);
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
//Set colors of the canvas node
|
||||
//--------------------------------------------------------------------------------
|
||||
const setColors = (canvasNode: HTMLDivElement, element: NonDeletedExcalidrawElement, mdProps: EmbeddableMDCustomProps, canvasColor: string) => {
|
||||
if(!mdProps) return;
|
||||
if (!leafRef.current?.hasOwnProperty("node")) return;
|
||||
@@ -289,6 +293,9 @@ function RenderObsidianView(
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
//Set colors of the canvas node
|
||||
//--------------------------------------------------------------------------------
|
||||
react.useEffect(() => {
|
||||
if(!containerRef.current) {
|
||||
return;
|
||||
@@ -304,6 +311,9 @@ function RenderObsidianView(
|
||||
canvasColor,
|
||||
])
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
//Switch to preview mode when the iframe is not active
|
||||
//--------------------------------------------------------------------------------
|
||||
react.useEffect(() => {
|
||||
if(isEditingRef.current) {
|
||||
if(leafRef.current?.node) {
|
||||
@@ -318,9 +328,9 @@ function RenderObsidianView(
|
||||
//--------------------------------------------------------------------------------
|
||||
//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,6 +359,22 @@ 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
|
||||
//--------------------------------------------------------------------------------
|
||||
@@ -404,7 +430,6 @@ export const CustomEmbeddable: React.FC<{element: NonDeletedExcalidrawElement; v
|
||||
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);
|
||||
|
||||
@@ -212,7 +212,12 @@ 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: {}});
|
||||
})();
|
||||
|
||||
}
|
||||
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;
|
||||
@@ -40,7 +40,7 @@ export class ExportDialog extends Modal {
|
||||
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);
|
||||
|
||||
@@ -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,260 @@ 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.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)
|
||||
@@ -856,7 +1110,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.
|
||||
|
||||
@@ -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,11 +8,12 @@ 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";
|
||||
@@ -499,7 +500,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,8 +516,12 @@ 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() {
|
||||
@@ -697,24 +702,27 @@ export class ConfirmationPrompt extends Modal {
|
||||
}
|
||||
}
|
||||
|
||||
export const linkPrompt = async (linkText:string, app: App, view?: ExcalidrawView):Promise<[file:TFile, linkText:string, subpath: string]> => {
|
||||
export const linkPrompt = async (linkText:string, app: App, view?: ExcalidrawView, message: string = "Select link to open"):Promise<[file:TFile, linkText:string, subpath: string]> => {
|
||||
const partsArray = REGEX_LINK.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;
|
||||
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)),
|
||||
message,
|
||||
);
|
||||
if(!parts) return;
|
||||
}
|
||||
|
||||
if(!parts) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!parts.value) {
|
||||
openTagSearch(linkText, app);
|
||||
@@ -738,4 +746,16 @@ 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];
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
108
src/dialogs/SelectCard.ts
Normal file
108
src/dialogs/SelectCard.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
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 { getExcalidrawMarkdownHeaderSection } from "src/ExcalidrawData";
|
||||
import { MD_EX_SECTIONS } from "src/constants/constants";
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { cleanSectionHeading } from "src/utils/ObsidianUtils";
|
||||
|
||||
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;
|
||||
}
|
||||
(async () => {
|
||||
const data = view.data;
|
||||
const header = getExcalidrawMarkdownHeaderSection(data);
|
||||
const body = data.split(header)[1];
|
||||
const shouldAddHashtag = body && body.startsWith("%%");
|
||||
const shouldRemoveTrailingHashtag = header.endsWith("#\n");
|
||||
view.data = data.replace(
|
||||
header,
|
||||
(shouldRemoveTrailingHashtag ? header.substring(0,header.length-2) : header) +
|
||||
`\n# ${item}\n\n${shouldAddHashtag ? "#\n" : ""}`);
|
||||
await view.forceSave(true);
|
||||
let watchdog = 0;
|
||||
await sleep(200);
|
||||
let found:string;
|
||||
while (watchdog++ < 10 && !(found=(await this.app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },view.file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "heading")
|
||||
.filter((b: any) => !MD_EX_SECTIONS.includes(b.display))
|
||||
.map((b: any) => cleanSectionHeading(b.display))
|
||||
.find((b: any) => b === item))) {
|
||||
await sleep(200);
|
||||
}
|
||||
|
||||
const ea = getEA(this.view) as ExcalidrawAutomate;
|
||||
const id = ea.addEmbeddable(
|
||||
0,0,400,500,
|
||||
`[[${this.view.file.path}#${item}]]`
|
||||
);
|
||||
await ea.addElementsToView(true, false, true);
|
||||
|
||||
const api = view.excalidrawAPI as ExcalidrawImperativeAPI;
|
||||
const el = ea.getViewElements().find(el=>el.id === id);
|
||||
api.selectElements([el]);
|
||||
setTimeout(()=>{
|
||||
api.updateScene({appState: {activeEmbeddable: {element: el, state: "active"}}});
|
||||
if(found) view.getEmbeddableLeafElementById(el.id)?.editNode?.();
|
||||
});
|
||||
})();
|
||||
//create new section
|
||||
//`# ${this.inputEl.value}\n\n`;
|
||||
//Do not allow MD_EX_SECTIONS
|
||||
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]);
|
||||
})();
|
||||
}
|
||||
|
||||
public start(): void {
|
||||
this.emptyStateText = t("EMPTY_SECTION_MESSAGE");
|
||||
this.setPlaceholder(t("SELECT_SECTION_OR_TYPE_NEW"));
|
||||
this.open();
|
||||
}
|
||||
}
|
||||
@@ -197,6 +197,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' +
|
||||
@@ -562,6 +563,21 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
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: "getAttachmentFilepath",
|
||||
code: "async getAttachmentFilepath(filename: string): Promise<string>",
|
||||
@@ -595,7 +611,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 +659,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>"+
|
||||
@@ -813,6 +829,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: "excalidraw-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,
|
||||
|
||||
@@ -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";
|
||||
@@ -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();
|
||||
|
||||
@@ -9,6 +9,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 +24,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 +36,7 @@ export default {
|
||||
TRANSCLUDE: "Embed a drawing",
|
||||
TRANSCLUDE_MOST_RECENT: "Embed the most recently edited drawing",
|
||||
TOGGLE_LEFTHANDED_MODE: "Toggle left-handed mode",
|
||||
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,6 +50,7 @@ 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 markdown link for selected element(s)",
|
||||
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.`,
|
||||
INSERT_LINK_TO_ELEMENT_GROUP:
|
||||
@@ -68,14 +71,18 @@ 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",
|
||||
INSERT_LATEX:
|
||||
`Insert LaTeX formula (e.g. \\binom{n}{k} = \\frac{n!}{k!(n-k)!}). ${labelALT()}+CLICK to watch a help video.`,
|
||||
ENTER_LATEX: "Enter a valid LaTeX expression",
|
||||
READ_RELEASE_NOTES: "Read latest release notes",
|
||||
RUN_OCR: "OCR: Grab text from freedraw scribble and pictures to clipboard",
|
||||
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_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",
|
||||
TEMPORARY_DISABLE_AUTOSAVE: "Disable autosave until next time Obsidian starts (only set this if you know what you are doing)",
|
||||
@@ -105,10 +112,14 @@ 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",
|
||||
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",
|
||||
|
||||
//settings.ts
|
||||
RELEASE_NOTES_NAME: "Display Release Notes after update",
|
||||
@@ -129,22 +140,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. " +
|
||||
@@ -195,6 +219,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 +297,21 @@ FILENAME_HEAD: "Filename",
|
||||
DEFAULT_PEN_MODE_NAME: "Pen mode",
|
||||
DEFAULT_PEN_MODE_DESC:
|
||||
"Should pen mode be automatically enabled when opening Excalidraw?",
|
||||
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_READING_MODE_NAME: "Render image when in markdown reading mode",
|
||||
SHOW_DRAWING_OR_MD_IN_READING_MODE_DESC:
|
||||
"Must close the active excalidraw/markdown file and reopen it for this change to take effect.<br>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, when you embed the drawing into a markdown document or when rendering hover preview.<br><ul>" +
|
||||
"<li>See other related setting for <b>PDF Export</b> under 'Embedding and Exporting' further below.</li>" +
|
||||
"<li>Be sure to check out the <b>Fade Out setting</b> in the 'Miscellaneous fetures' section.</li></ul>",
|
||||
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_NAME: "Render image when EXPORT TO PDF in markdown mode",
|
||||
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_DESC:
|
||||
"Must close the active excalidraw/markdown file and reopen for this change to take effect.<br>When you are printing the markdown side of the note to PDF (aka. the back side of the drawing), should the Excalidraw drawing be rendered as an image?<br><ul>" +
|
||||
"<li>See other related setting for <b>Markdown Reading Mode</b> under 'Appearnace and Behavior' further above.</li>" +
|
||||
"<li>Be sure to check out the <b>Fade Out setting</b> in the 'Miscellaneous fetures' section.</li></ul>",
|
||||
THEME_HEAD: "Theme and styling",
|
||||
ZOOM_HEAD: "Zoom",
|
||||
DEFAULT_PINCHZOOM_NAME: "Allow pinch zoom in pen mode",
|
||||
@@ -306,6 +353,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 +408,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. " +
|
||||
@@ -458,6 +517,11 @@ 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 " +
|
||||
@@ -486,6 +550,10 @@ FILENAME_HEAD: "Filename",
|
||||
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 +575,9 @@ 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.",
|
||||
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>" +
|
||||
@@ -575,6 +646,11 @@ 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.",
|
||||
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.",
|
||||
CUSTOM_FONT_HEAD: "Fourth font",
|
||||
ENABLE_FOURTH_FONT_NAME: "Enable fourth font option",
|
||||
ENABLE_FOURTH_FONT_DESC:
|
||||
@@ -619,6 +695,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",
|
||||
@@ -691,6 +774,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",
|
||||
|
||||
557
src/main.ts
557
src/main.ts
@@ -41,6 +41,8 @@ import {
|
||||
EXPORT_IMG_ICON,
|
||||
LOCALE,
|
||||
IMAGE_TYPES,
|
||||
setExcalidrawPlugin,
|
||||
DEVICE
|
||||
} from "./constants/constants";
|
||||
import {
|
||||
VIRGIL_FONT,
|
||||
@@ -72,33 +74,36 @@ import {
|
||||
insertLaTeXToView,
|
||||
search,
|
||||
} from "./ExcalidrawAutomate";
|
||||
import { Prompt } from "./dialogs/Prompt";
|
||||
import { Prompt, templatePromt } from "./dialogs/Prompt";
|
||||
import { around, dedupe } from "monkey-around";
|
||||
import { t } from "./lang/helpers";
|
||||
import {
|
||||
checkAndCreateFolder,
|
||||
download,
|
||||
fileShouldDefaultAsExcalidraw,
|
||||
getAliasWithSize,
|
||||
getAnnotationFileNameAndFolder,
|
||||
getCropFileNameAndFolder,
|
||||
getDrawingFilename,
|
||||
getEmbedFilename,
|
||||
getIMGFilename,
|
||||
getLink,
|
||||
getListOfTemplateFiles,
|
||||
getNewUniqueFilepath,
|
||||
getURLImageExtension,
|
||||
splitFolderAndFilename,
|
||||
} from "./utils/FileUtils";
|
||||
import {
|
||||
getFontDataURL,
|
||||
errorlog,
|
||||
log,
|
||||
setLeftHandedMode,
|
||||
sleep,
|
||||
isVersionNewerThanOther,
|
||||
getExportTheme,
|
||||
isCallerFromTemplaterPlugin,
|
||||
decompress,
|
||||
getImageSize,
|
||||
} from "./utils/Utils";
|
||||
import { extractSVGPNGFileName, getActivePDFPageNumberFromPDFView, getAttachmentsFolderAndFilePath, getNewOrAdjacentLeaf, getParentOfClass, isObsidianThemeDark } from "./utils/ObsidianUtils";
|
||||
import { editorInsertText, extractSVGPNGFileName, foldExcalidrawSection, getActivePDFPageNumberFromPDFView, getAttachmentsFolderAndFilePath, getNewOrAdjacentLeaf, getParentOfClass, isObsidianThemeDark, mergeMarkdownFiles, openLeaf } from "./utils/ObsidianUtils";
|
||||
import { ExcalidrawElement, ExcalidrawEmbeddableElement, ExcalidrawImageElement, ExcalidrawTextElement, FileId } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ScriptEngine } from "./Scripts";
|
||||
import {
|
||||
@@ -126,9 +131,11 @@ import { processLinkText } from "./utils/CustomEmbeddableUtils";
|
||||
import { getEA } from "src";
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import { CustomMutationObserver, durationTreshold, isDebugMode } from "./utils/DebugHelper";
|
||||
import { CustomMutationObserver, debug, durationTreshold, log } from "./utils/DebugHelper";
|
||||
import { carveOutImage, carveOutPDF, createImageCropperFile, CROPPED_PREFIX } from "./utils/CarveOut";
|
||||
import { ExcalidrawConfig } from "./utils/ExcalidrawConfig";
|
||||
import { EditorHandler } from "./CodeMirrorExtension/EditorHandler";
|
||||
import de from "./lang/locale/de";
|
||||
|
||||
declare const EXCALIDRAW_PACKAGES:string;
|
||||
declare const react:any;
|
||||
@@ -177,6 +184,10 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
private removeEventLisnters:(()=>void)[] = [];
|
||||
private stylesManager:StylesManager;
|
||||
private textMeasureDiv:HTMLDivElement = null;
|
||||
public editorHandler: EditorHandler;
|
||||
public activeLeafChangeEventHandler: (leaf: WorkspaceLeaf) => Promise<void>;
|
||||
//if set, the next time this file is opened it will be opened as markdown
|
||||
public forceToOpenInMarkdownFilepath: string = null;
|
||||
|
||||
constructor(app: App, manifest: PluginManifest) {
|
||||
super(app, manifest);
|
||||
@@ -186,6 +197,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
>();
|
||||
this.equationsMaster = new Map<FileId, string>();
|
||||
this.mermaidsMaster = new Map<FileId, string>();
|
||||
setExcalidrawPlugin(this);
|
||||
}
|
||||
|
||||
get locale() {
|
||||
@@ -219,7 +231,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
|
||||
public registerEvent(event: any) {
|
||||
if(!isDebugMode) {
|
||||
if(!this.settings.isDebugMode) {
|
||||
super.registerEvent(event);
|
||||
return;
|
||||
}
|
||||
@@ -258,6 +270,8 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
await this.loadSettings({reEnableAutosave:true});
|
||||
this.excalidrawConfig = new ExcalidrawConfig(this);
|
||||
await loadMermaid();
|
||||
this.editorHandler = new EditorHandler(this);
|
||||
this.editorHandler.setup();
|
||||
|
||||
this.addSettingTab(new ExcalidrawSettingTab(this.app, this));
|
||||
this.ea = await initExcalidrawAutomate(this);
|
||||
@@ -308,6 +322,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
const self = this;
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
debug(`ExcalidrawPlugin.onload.app.workspace.onLayoutReady`);
|
||||
this.scriptEngine = new ScriptEngine(self);
|
||||
imageCache.initializeDB(self);
|
||||
});
|
||||
@@ -317,6 +332,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
private setPropertyTypes() {
|
||||
const app = this.app;
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
debug(`ExcalidrawPlugin.setPropertyTypes app.workspace.onLayoutReady`);
|
||||
Object.keys(FRONTMATTER_KEYS).forEach((key) => {
|
||||
if(FRONTMATTER_KEYS[key].depricated === true) return;
|
||||
const {name, type} = FRONTMATTER_KEYS[key];
|
||||
@@ -327,6 +343,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
public initializeFonts() {
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
debug(`ExcalidrawPlugin.initializeFonts app.workspace.onLayoutReady`);
|
||||
const font = await getFontDataURL(
|
||||
this.app,
|
||||
this.settings.experimantalFourthFont,
|
||||
@@ -370,7 +387,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
private getOpenObsidianDocuments(): Document[] {
|
||||
const visitedDocs = new Set<Document>();
|
||||
this.app.workspace.iterateAllLeaves((leaf)=>{
|
||||
const ownerDocument = this.app.isMobile?document:leaf.view.containerEl.ownerDocument;
|
||||
const ownerDocument = DEVICE.isMobile?document:leaf.view.containerEl.ownerDocument;
|
||||
if(!ownerDocument) return;
|
||||
if(visitedDocs.has(ownerDocument)) return;
|
||||
visitedDocs.add(ownerDocument);
|
||||
@@ -381,15 +398,17 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
private switchToExcalidarwAfterLoad() {
|
||||
const self = this;
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
debug(`ExcalidrawPlugin.switchToExcalidarwAfterLoad app.workspace.onLayoutReady`);
|
||||
let leaf: WorkspaceLeaf;
|
||||
for (leaf of app.workspace.getLeavesOfType("markdown")) {
|
||||
if (
|
||||
leaf.view instanceof MarkdownView &&
|
||||
self.isExcalidrawFile(leaf.view.file)
|
||||
) {
|
||||
self.excalidrawFileModes[(leaf as any).id || leaf.view.file.path] =
|
||||
VIEW_TYPE_EXCALIDRAW;
|
||||
self.setExcalidrawView(leaf);
|
||||
for (leaf of this.app.workspace.getLeavesOfType("markdown")) {
|
||||
if ( leaf.view instanceof MarkdownView && self.isExcalidrawFile(leaf.view.file)) {
|
||||
if (fileShouldDefaultAsExcalidraw(leaf.view.file?.path, self.app)) {
|
||||
self.excalidrawFileModes[(leaf as any).id || leaf.view.file.path] =
|
||||
VIEW_TYPE_EXCALIDRAW;
|
||||
self.setExcalidrawView(leaf);
|
||||
} else {
|
||||
foldExcalidrawSection(leaf.view);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -607,16 +626,19 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
* Displays a transcluded .excalidraw image in markdown preview mode
|
||||
*/
|
||||
private addMarkdownPostProcessor() {
|
||||
//Licat: Are you registering your post processors in onLayoutReady? You should register them in onload instead
|
||||
initializeMarkdownPostProcessor(this);
|
||||
this.registerMarkdownPostProcessor(markdownPostProcessor);
|
||||
|
||||
const self = this;
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
initializeMarkdownPostProcessor(self);
|
||||
self.registerMarkdownPostProcessor(markdownPostProcessor);
|
||||
debug(`ExcalidrawPlugin.addMarkdownPostProcessor app.workspace.onLayoutReady`);
|
||||
|
||||
// internal-link quick preview
|
||||
self.registerEvent(self.app.workspace.on("hover-link", hoverEvent));
|
||||
|
||||
//only add the legacy file observer if there are legacy files in the vault
|
||||
if(this.app.vault.getFiles().some(f=>f.extension === "excalidraw")) {
|
||||
if(self.app.vault.getFiles().some(f=>f.extension === "excalidraw")) {
|
||||
self.enableLegacyFilePopoverObserver();
|
||||
}
|
||||
});
|
||||
@@ -659,7 +681,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
});
|
||||
};
|
||||
|
||||
this.themeObserver = isDebugMode
|
||||
this.themeObserver = this.settings.isDebugMode
|
||||
? new CustomMutationObserver(themeObserverFn, "themeObserver")
|
||||
: new MutationObserver(themeObserverFn);
|
||||
|
||||
@@ -691,7 +713,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
*/
|
||||
private experimentalFileTypeDisplay() {
|
||||
const insertFiletype = (el: HTMLElement) => {
|
||||
if (el.childElementCount != 1) {
|
||||
if (el.childElementCount !== 1) {
|
||||
return;
|
||||
}
|
||||
const filename = el.getAttribute("data-path");
|
||||
@@ -725,12 +747,13 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
});
|
||||
};
|
||||
|
||||
this.fileExplorerObserver = isDebugMode
|
||||
this.fileExplorerObserver = this.settings.isDebugMode
|
||||
? new CustomMutationObserver(fileExplorerObserverFn, "fileExplorerObserver")
|
||||
: new MutationObserver(fileExplorerObserverFn);
|
||||
|
||||
const self = this;
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
debug(`ExcalidrawPlugin.experimentalFileTypeDisplay app.workspace.onLayoutReady`);
|
||||
document.querySelectorAll(".nav-file-title").forEach(insertFiletype); //apply filetype to files already displayed
|
||||
const container = document.querySelector(".nav-files-container");
|
||||
if (container) {
|
||||
@@ -855,9 +878,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
(async () => {
|
||||
const data = await this.app.vault.read(activeFile);
|
||||
const parts = data.split("%%\n# Drawing\n```compressed-json\n");
|
||||
const parts = data.split("\n## Drawing\n```compressed-json\n");
|
||||
if(parts.length!==2) return;
|
||||
const header = parts[0] + "%%\n# Drawing\n```json\n";
|
||||
const header = parts[0] + "\n## Drawing\n```json\n";
|
||||
const compressed = parts[1].split("\n```\n%%");
|
||||
if(compressed.length!==2) return;
|
||||
const decompressed = decompress(compressed[0]);
|
||||
@@ -1032,7 +1055,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
if (checking) {
|
||||
return (
|
||||
Boolean(this.app.workspace.getActiveViewOfType(MarkdownView)) &&
|
||||
this.lastActiveExcalidrawFilePath != null
|
||||
this.lastActiveExcalidrawFilePath !== null
|
||||
);
|
||||
}
|
||||
const file = this.app.vault.getAbstractFileByPath(
|
||||
@@ -1075,7 +1098,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
name: t("NEW_IN_POPOUT_WINDOW"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
return !app.isMobile
|
||||
return !DEVICE.isMobile;
|
||||
}
|
||||
this.createAndOpenDrawing(getDrawingFilename(this.settings), "popout-window");
|
||||
},
|
||||
@@ -1147,7 +1170,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
name: t("NEW_IN_POPOUT_WINDOW_EMBED"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
return !app.isMobile && Boolean(this.app.workspace.getActiveViewOfType(MarkdownView));
|
||||
return !DEVICE.isMobile && Boolean(this.app.workspace.getActiveViewOfType(MarkdownView));
|
||||
}
|
||||
insertDrawingToDoc("popout-window");
|
||||
return true;
|
||||
@@ -1169,7 +1192,51 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
new Notice("Taskbone OCR is not enabled. Please go to plugins settings to enable it.",4000);
|
||||
return true;
|
||||
}
|
||||
this.taskbone.getTextForView(view, false);
|
||||
this.taskbone.getTextForView(view, {forceReScan: false});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "rerun-ocr",
|
||||
name: t("RERUN_OCR"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (checking) {
|
||||
return (
|
||||
Boolean(view)
|
||||
);
|
||||
}
|
||||
if (view) {
|
||||
if(!this.settings.taskboneEnabled) {
|
||||
new Notice("Taskbone OCR is not enabled. Please go to plugins settings to enable it.",4000);
|
||||
return true;
|
||||
}
|
||||
this.taskbone.getTextForView(view, {forceReScan: true});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "run-ocr-selectedelements",
|
||||
name: t("RUN_OCR_ELEMENTS"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (checking) {
|
||||
return (
|
||||
Boolean(view)
|
||||
);
|
||||
}
|
||||
if (view) {
|
||||
if(!this.settings.taskboneEnabled) {
|
||||
new Notice("Taskbone OCR is not enabled. Please go to plugins settings to enable it.",4000);
|
||||
return true;
|
||||
}
|
||||
this.taskbone.getTextForView(view, {forceReScan: false, selectedElementsOnly: true, addToFrontmatter: false});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -1518,6 +1585,31 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "flip-image",
|
||||
name: t("FLIP_IMAGE"),
|
||||
checkCallback: (checking:boolean) => {
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if(!view) return false;
|
||||
if(!view.excalidrawAPI) return false;
|
||||
const els = view.getViewSelectedElements().filter(el=>el.type==="image");
|
||||
if(els.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
const el = els[0] as ExcalidrawImageElement;
|
||||
let ef = view.excalidrawData.getFile(el.fileId);
|
||||
if(!ef) {
|
||||
return false;
|
||||
}
|
||||
if(!this.isExcalidrawFile(ef.file)) {
|
||||
return false;
|
||||
}
|
||||
if(checking) return true;
|
||||
this.forceToOpenInMarkdownFilepath = ef.file?.path;
|
||||
this.openDrawing(ef.file, DEVICE.isMobile ? "new-tab":"popout-window", true);
|
||||
}
|
||||
})
|
||||
|
||||
this.addCommand({
|
||||
id: "reset-image-to-100",
|
||||
name: t("RESET_IMG_TO_100"),
|
||||
@@ -1531,16 +1623,18 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
new Notice("Select a single image element and try again");
|
||||
return false;
|
||||
}
|
||||
const el = els[0] as ExcalidrawImageElement;
|
||||
const ef = view.excalidrawData.getFile(el.fileId);
|
||||
if(!ef) {
|
||||
if(checking) return false;
|
||||
new Notice("Select a single image element and try again");
|
||||
return false;
|
||||
}
|
||||
if(checking) return true;
|
||||
|
||||
|
||||
(async () => {
|
||||
const el = els[0] as ExcalidrawImageElement;
|
||||
let ef = view.excalidrawData.getFile(el.fileId);
|
||||
if(!ef) {
|
||||
await view.forceSave();
|
||||
let ef = view.excalidrawData.getFile(el.fileId);
|
||||
new Notice("Select a single image element and try again");
|
||||
return false;
|
||||
}
|
||||
|
||||
const ea = new ExcalidrawAutomate(this,view);
|
||||
const size = await ea.getOriginalImageSize(el);
|
||||
if(size) {
|
||||
@@ -1723,7 +1817,11 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
const line = editor.getLine(cursor.line);
|
||||
const parts = REGEX_LINK.getResList(line);
|
||||
if(parts.length === 0) return false;
|
||||
const imgpath = REGEX_LINK.getLink(parts[0]);
|
||||
let imgpath = REGEX_LINK.getLink(parts[0]);
|
||||
const isWikilink = REGEX_LINK.isWikiLink(parts[0]);
|
||||
let alias = REGEX_LINK.getAliasOrLink(parts[0]);
|
||||
if(alias === imgpath) alias = null;
|
||||
imgpath = decodeURI(imgpath);
|
||||
const imagePathParts = imgpath.split("#");
|
||||
const hasRef = imagePathParts.length === 2;
|
||||
const imageFile = this.app.metadataCache.getFirstLinkpathDest(
|
||||
@@ -1747,11 +1845,169 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
const pdfLink = isFile && ref
|
||||
? "\n" + getLink(this ,{
|
||||
embed: false,
|
||||
alias: `${imageFile.basename}, ${ref.replace("="," ")}`,
|
||||
alias: alias ?? `${imageFile.basename}, ${ref.replace("="," ")}`,
|
||||
path:`${imageFile.path}#${ref}`
|
||||
})
|
||||
}, isWikilink)
|
||||
: "";
|
||||
editor.setLine(cursor.line,lineparts[0] + getLink(this ,{embed: true, path:link}) + pdfLink + lineparts[1]);
|
||||
editor.setLine(cursor.line,lineparts[0] + getLink(this ,{embed: true, path:link, alias}, isWikilink) + pdfLink + lineparts[1]);
|
||||
}
|
||||
carveout(isFile, markdownView.file, imageFile, imagepath, replacer, ref);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.addCommand({
|
||||
id: "annotate-image",
|
||||
name: t("ANNOTATE_IMAGE"),
|
||||
checkCallback: (checking:boolean) => {
|
||||
const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
const canvasView:any = this.app.workspace.activeLeaf?.view;
|
||||
const isCanvas = canvasView && canvasView.getViewType() === "canvas";
|
||||
if(!markdownView && !isCanvas) return false;
|
||||
|
||||
const carveout = async (isFile: boolean, sourceFile: TFile, imageFile: TFile, imageURL: string, replacer: Function, ref?: string) => {
|
||||
const ea = getEA() as ExcalidrawAutomate;
|
||||
const imageID = await ea.addImage(
|
||||
0, 0,
|
||||
isFile
|
||||
? ((isFile && imageFile.extension === "pdf" && ref) ? `${imageFile.path}#${ref}` : imageFile)
|
||||
: imageURL,
|
||||
false, false
|
||||
);
|
||||
if(!imageID) {
|
||||
new Notice(`Can't load image\n\n${imageURL}`);
|
||||
return;
|
||||
}
|
||||
const el = ea.getElement(imageID) as Mutable<ExcalidrawImageElement>;
|
||||
el.locked = true;
|
||||
const size = this.settings.annotatePreserveSize
|
||||
? await getImageSize(ea.imagesDict[el.fileId].dataURL)
|
||||
: null;
|
||||
let fnBase = "";
|
||||
let imageLink = "";
|
||||
if(isFile) {
|
||||
fnBase = imageFile.basename;
|
||||
imageLink = ref
|
||||
? `[[${imageFile.path}#${ref}]]`
|
||||
: `[[${imageFile.path}]]`;
|
||||
} else {
|
||||
imageLink = imageURL;
|
||||
const imagename = imageURL.match(/^.*\/([^?]*)\??.*$/)?.[1];
|
||||
fnBase = imagename.substring(0,imagename.lastIndexOf("."));
|
||||
}
|
||||
|
||||
let template:TFile;
|
||||
const templates = getListOfTemplateFiles(this);
|
||||
if(templates) {
|
||||
template = await templatePromt(templates, this.app);
|
||||
}
|
||||
|
||||
const {folderpath, filename} = await getAnnotationFileNameAndFolder(this,sourceFile.path,fnBase)
|
||||
const newPath = await ea.create ({
|
||||
templatePath: template?.path,
|
||||
filename,
|
||||
foldername: folderpath,
|
||||
onNewPane: true,
|
||||
frontmatterKeys: {
|
||||
...this.settings.matchTheme ? {"excalidraw-export-dark": isObsidianThemeDark()} : {},
|
||||
...(imageFile.extension === "pdf") ? {"cssclasses": "excalidraw-cropped-pdfpage"} : {},
|
||||
}
|
||||
});
|
||||
|
||||
//wait for file to be created/indexed by Obsidian
|
||||
let newFile = this.app.vault.getAbstractFileByPath(newPath);
|
||||
let counter = 0;
|
||||
while((!newFile || !this.isExcalidrawFile(newFile as TFile)) && counter < 50) {
|
||||
await sleep(100);
|
||||
newFile = this.app.vault.getAbstractFileByPath(newPath);
|
||||
counter++;
|
||||
}
|
||||
//console.log({counter, file});
|
||||
if(!newFile || !(newFile instanceof TFile)) {
|
||||
new Notice("File not found. NewExcalidraw Drawing is taking too long to create. Please try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!newFile) return;
|
||||
const link = this.app.metadataCache.fileToLinktext(newFile,sourceFile.path, true);
|
||||
replacer(link, newFile, size ? `${size.width}` : null);
|
||||
}
|
||||
|
||||
if(isCanvas) {
|
||||
const selectedNodes:any = [];
|
||||
canvasView.canvas.nodes.forEach((node:any) => {
|
||||
if(node.nodeEl.hasClass("is-focused")) selectedNodes.push(node);
|
||||
})
|
||||
if(selectedNodes.length !== 1) return false;
|
||||
const node = selectedNodes[0];
|
||||
let extension = "";
|
||||
let isExcalidraw = false;
|
||||
if(node.file) {
|
||||
extension = node.file.extension;
|
||||
isExcalidraw = this.isExcalidrawFile(node.file);
|
||||
}
|
||||
if(node.url) {
|
||||
extension = getURLImageExtension(node.url);
|
||||
}
|
||||
const page = extension === "pdf" ? getActivePDFPageNumberFromPDFView(node?.child) : undefined;
|
||||
if(!page && !IMAGE_TYPES.contains(extension) && !isExcalidraw) return false;
|
||||
if(checking) return true;
|
||||
|
||||
const replacer = (link:string, file: TFile) => {
|
||||
if(node.file) {
|
||||
(node.file.extension === "pdf")
|
||||
? node.canvas.createFileNode({pos:{x:node.x + node.width + 10,y: node.y}, file})
|
||||
: node.setFile(file);
|
||||
}
|
||||
if(node.url) {
|
||||
node.canvas.createFileNode({pos:{x:node.x + 20,y: node.y+20}, file});
|
||||
}
|
||||
}
|
||||
carveout(Boolean(node.file), canvasView.file, node.file, node.url, replacer, page ? `page=${page}` : undefined);
|
||||
}
|
||||
|
||||
if (markdownView) {
|
||||
const editor = markdownView.editor;
|
||||
const cursor = editor.getCursor();
|
||||
const line = editor.getLine(cursor.line);
|
||||
const parts = REGEX_LINK.getResList(line);
|
||||
if(parts.length === 0) return false;
|
||||
let imgpath = REGEX_LINK.getLink(parts[0]);
|
||||
const isWikilink = REGEX_LINK.isWikiLink(parts[0]);
|
||||
let alias = REGEX_LINK.getAliasOrLink(parts[0]);
|
||||
if(alias === imgpath) alias = null;
|
||||
imgpath = decodeURI(imgpath);
|
||||
const imagePathParts = imgpath.split("#");
|
||||
const hasRef = imagePathParts.length === 2;
|
||||
const imageFile = this.app.metadataCache.getFirstLinkpathDest(
|
||||
hasRef ? imagePathParts[0] : imgpath,
|
||||
markdownView.file.path
|
||||
);
|
||||
const isFile = (imageFile && imageFile instanceof TFile);
|
||||
const isExcalidraw = isFile ? this.isExcalidrawFile(imageFile) : false;
|
||||
let imagepath = isFile ? imageFile.path : "";
|
||||
let extension = isFile ? imageFile.extension : "";
|
||||
if(imgpath.match(/^https?|file/)) {
|
||||
imagepath = imgpath;
|
||||
extension = getURLImageExtension(imgpath);
|
||||
}
|
||||
if(imagepath === "") return false;
|
||||
if(extension !== "pdf" && !IMAGE_TYPES.contains(extension) && !isExcalidraw) return false;
|
||||
if(checking) return true;
|
||||
const ref = imagePathParts[1];
|
||||
const replacer = (link:string, _:TFile, size:string) => {
|
||||
const lineparts = line.split(parts[0].value[0])
|
||||
const pdfLink = isFile && ref
|
||||
? "\n" + getLink(this ,{
|
||||
embed: false,
|
||||
alias: getAliasWithSize(alias ?? `${imageFile.basename}, ${ref.replace("="," ")}`,size),
|
||||
path:`${imageFile.path}#${ref}`
|
||||
}, isWikilink)
|
||||
: "";
|
||||
editor.setLine(
|
||||
cursor.line,
|
||||
lineparts[0] + getLink(this ,{embed: true, path:link, alias: getAliasWithSize(alias,size)}, isWikilink) + pdfLink + lineparts[1]
|
||||
);
|
||||
}
|
||||
carveout(isFile, markdownView.file, imageFile, imagepath, replacer, ref);
|
||||
}
|
||||
@@ -1876,6 +2132,22 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "universal-card",
|
||||
name: t("INSERT_CARD"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
return Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView))
|
||||
}
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (view) {
|
||||
view.insertBackOfTheNoteCard();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "insert-LaTeX-symbol",
|
||||
name: t("INSERT_LATEX"),
|
||||
@@ -1920,10 +2192,13 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView)
|
||||
if (markdownView && fileIsExcalidraw) {
|
||||
const activeLeaf = markdownView.leaf;
|
||||
this.excalidrawFileModes[(activeLeaf as any).id || activeFile.path] =
|
||||
VIEW_TYPE_EXCALIDRAW;
|
||||
this.setExcalidrawView(activeLeaf);
|
||||
(async()=>{
|
||||
await markdownView.save();
|
||||
const activeLeaf = markdownView.leaf;
|
||||
this.excalidrawFileModes[(activeLeaf as any).id || activeFile.path] =
|
||||
VIEW_TYPE_EXCALIDRAW;
|
||||
this.setExcalidrawView(activeLeaf);
|
||||
})()
|
||||
return;
|
||||
}
|
||||
},
|
||||
@@ -1940,21 +2215,25 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isFileEmpty = activeFile.stat.size === 0;
|
||||
|
||||
if (checking) {
|
||||
return isFileEmpty;
|
||||
if(this.isExcalidrawFile(activeFile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isFileEmpty) {
|
||||
(async () => {
|
||||
await this.app.vault.modify(
|
||||
activeFile,
|
||||
await this.getBlankDrawing(),
|
||||
);
|
||||
this.setExcalidrawView(activeView.leaf);
|
||||
})();
|
||||
if(checking) {
|
||||
return true;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await activeView.save();
|
||||
const template = await this.getBlankDrawing();
|
||||
const target = await this.app.vault.read(activeFile);
|
||||
const mergedTarget = mergeMarkdownFiles(template, target);
|
||||
await this.app.vault.modify(
|
||||
activeFile,
|
||||
mergedTarget,
|
||||
);
|
||||
this.setExcalidrawView(activeView.leaf);
|
||||
})();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2027,12 +2306,13 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
private registerMonkeyPatches() {
|
||||
const key = "https://github.com/zsviczian/obsidian-excalidraw-plugin/issues";
|
||||
|
||||
this.register(
|
||||
around(Workspace.prototype, {
|
||||
getActiveViewOfType(old) {
|
||||
return dedupe(key, old, function(...args) {
|
||||
const result = old && old.apply(this, args);
|
||||
const maybeEAView = app?.workspace?.activeLeaf?.view;
|
||||
const maybeEAView = self.app?.workspace?.activeLeaf?.view;
|
||||
if(!maybeEAView || !(maybeEAView instanceof ExcalidrawView)) return result;
|
||||
const error = new Error();
|
||||
const stackTrace = error.stack;
|
||||
@@ -2073,7 +2353,8 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
.setTitle(t("OPEN_AS_EXCALIDRAW"))
|
||||
.setIcon(ICON_NAME)
|
||||
.setSection("excalidraw")
|
||||
.onClick(() => {
|
||||
.onClick(async () => {
|
||||
await view.save();
|
||||
//@ts-ignore
|
||||
this.excalidrawFileModes[leaf.id || file.path] = VIEW_TYPE_EXCALIDRAW;
|
||||
this.setExcalidrawView(leaf);
|
||||
@@ -2084,7 +2365,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
this.registerEvent(
|
||||
app.workspace.on("file-menu", (menu, file, source, leaf) => {
|
||||
if (!leaf || !(leaf.view instanceof MarkdownView)) return;
|
||||
if (!leaf) return;
|
||||
const view = leaf.view;
|
||||
if(!view || !(view instanceof MarkdownView)) return;
|
||||
if (!(file instanceof TFile)) return;
|
||||
const cache = this.app.metadataCache.getFileCache(file);
|
||||
if (!cache?.frontmatter || !cache.frontmatter[FRONTMATTER_KEYS["plugin"].name]) return;
|
||||
@@ -2094,7 +2377,8 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
.setTitle(t("OPEN_AS_EXCALIDRAW"))
|
||||
.setIcon(ICON_NAME)
|
||||
.setSection("pane")
|
||||
.onClick(() => {
|
||||
.onClick(async () => {
|
||||
await view.save();
|
||||
//@ts-ignore
|
||||
this.excalidrawFileModes[leaf.id || file.path] = VIEW_TYPE_EXCALIDRAW;
|
||||
this.setExcalidrawView(leaf);
|
||||
@@ -2128,31 +2412,38 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
setViewState(next) {
|
||||
return function (state: ViewState, ...rest: any[]) {
|
||||
const markdownViewLoaded =
|
||||
self._loaded && // Don't force excalidraw mode during shutdown
|
||||
state.type === "markdown" && // If we have a markdown file
|
||||
state.state?.file;
|
||||
if (
|
||||
// Don't force excalidraw mode during shutdown
|
||||
self._loaded &&
|
||||
// If we have a markdown file
|
||||
state.type === "markdown" &&
|
||||
state.state?.file &&
|
||||
// And the current mode of the file is not set to markdown
|
||||
self.excalidrawFileModes[this.id || state.state.file] !==
|
||||
"markdown"
|
||||
markdownViewLoaded &&
|
||||
self.excalidrawFileModes[this.id || state.state.file] !== "markdown"
|
||||
) {
|
||||
// Then check for the excalidraw frontMatterKey
|
||||
const cache = app.metadataCache.getCache(state.state.file);
|
||||
|
||||
if (cache?.frontmatter && cache.frontmatter[FRONTMATTER_KEYS["plugin"].name]) {
|
||||
const file = state.state.file;
|
||||
if ((self.forceToOpenInMarkdownFilepath !== file) && fileShouldDefaultAsExcalidraw(file,this.app)) {
|
||||
// If we have it, force the view type to excalidraw
|
||||
const newState = {
|
||||
...state,
|
||||
type: VIEW_TYPE_EXCALIDRAW,
|
||||
};
|
||||
|
||||
self.excalidrawFileModes[state.state.file] =
|
||||
self.excalidrawFileModes[file] =
|
||||
VIEW_TYPE_EXCALIDRAW;
|
||||
|
||||
return next.apply(this, [newState, ...rest]);
|
||||
}
|
||||
self.forceToOpenInMarkdownFilepath = null;
|
||||
}
|
||||
|
||||
if(markdownViewLoaded) {
|
||||
const leaf = this;
|
||||
setTimeout(async ()=> {
|
||||
if(!leaf || !leaf.view || !(leaf.view instanceof MarkdownView) ||
|
||||
!leaf.view.file || !self.isExcalidrawFile(leaf.view.file)
|
||||
) return;
|
||||
foldExcalidrawSection(leaf.view)
|
||||
},500);
|
||||
}
|
||||
|
||||
return next.apply(this, [state, ...rest]);
|
||||
@@ -2168,6 +2459,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
const self = this;
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
debug(`ExcalidrawPlugin.runStartupScript app.workspace.onLayoutReady: ${self.settings?.startupScriptPath}`);
|
||||
const path = self.settings.startupScriptPath.endsWith(".md")
|
||||
? self.settings.startupScriptPath
|
||||
: `${self.settings.startupScriptPath}.md`;
|
||||
@@ -2190,6 +2482,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
private registerEventListeners() {
|
||||
const self: ExcalidrawPlugin = this;
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
debug("ExcalidrawPlugin.registerEventListeners app.workspace.onLayoutReady");
|
||||
const onPasteHandler = (
|
||||
evt: ClipboardEvent,
|
||||
editor: Editor,
|
||||
@@ -2218,16 +2511,16 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
if(sourceFile && imageFile && imageFile instanceof TFile) {
|
||||
path = self.app.metadataCache.fileToLinktext(imageFile,sourceFile.path);
|
||||
}
|
||||
editor.insertText(getLink(self, {path}));
|
||||
editorInsertText(editor, getLink(self, {path}));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (element.type === "text") {
|
||||
editor.insertText(element.text);
|
||||
editorInsertText(editor, element.rawText);
|
||||
return;
|
||||
}
|
||||
if (element.link) {
|
||||
editor.insertText(`${element.link}`);
|
||||
editorInsertText(editor, `${element.link}`);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -2287,8 +2580,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
return;
|
||||
}
|
||||
if(file.extension==="md") {
|
||||
if(excalidrawView.semaphores.embeddableIsEditingSelf) return;
|
||||
const inData = new ExcalidrawData(self);
|
||||
const data = await app.vault.read(file);
|
||||
const data = await this.app.vault.read(file);
|
||||
await inData.loadData(data,file,getTextMode(data));
|
||||
excalidrawView.synchronizeWithData(inData);
|
||||
if(excalidrawView.semaphores.dirty) {
|
||||
@@ -2379,14 +2673,14 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
//!Temporary hack
|
||||
//https://discord.com/channels/686053708261228577/817515900349448202/1031101635784613968
|
||||
if (this.app.isMobile && newActiveviewEV && !previouslyActiveEV) {
|
||||
if (DEVICE.isMobile && newActiveviewEV && !previouslyActiveEV) {
|
||||
const navbar = document.querySelector("body>.app-container>.mobile-navbar");
|
||||
if(navbar && navbar instanceof HTMLDivElement) {
|
||||
navbar.style.position="relative";
|
||||
}
|
||||
}
|
||||
|
||||
if (this.app.isMobile && !newActiveviewEV && previouslyActiveEV) {
|
||||
if (DEVICE.isMobile && !newActiveviewEV && previouslyActiveEV) {
|
||||
const navbar = document.querySelector("body>.app-container>.mobile-navbar");
|
||||
if(navbar && navbar instanceof HTMLDivElement) {
|
||||
navbar.style.position="";
|
||||
@@ -2455,7 +2749,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
const scope = self.app.keymap.getRootScope();
|
||||
const handler_ctrlEnter = scope.register(["Mod"], "Enter", () => true);
|
||||
scope.keys.unshift(scope.keys.pop()); // Force our handler to the front of the list
|
||||
const handler_ctrlK = scope.register(["Mod"], "k", () => {return true});
|
||||
const handler_ctrlK = scope.register(["Mod"], "k", () => true);
|
||||
scope.keys.unshift(scope.keys.pop()); // Force our handler to the front of the list
|
||||
const handler_ctrlF = scope.register(["Mod"], "f", () => {
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
@@ -2485,8 +2779,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
}
|
||||
};
|
||||
this.activeLeafChangeEventHandler = activeLeafChangeEventHandler;
|
||||
self.registerEvent(
|
||||
app.workspace.on(
|
||||
this.app.workspace.on(
|
||||
"active-leaf-change",
|
||||
activeLeafChangeEventHandler,
|
||||
),
|
||||
@@ -2494,7 +2789,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
self.addFileSaveTriggerEventHandlers();
|
||||
|
||||
const metaCache: MetadataCache = app.metadataCache;
|
||||
const metaCache: MetadataCache = this.app.metadataCache;
|
||||
//@ts-ignore
|
||||
metaCache.getCachedFiles().forEach((filename: string) => {
|
||||
const fm = metaCache.getCache(filename)?.frontmatter;
|
||||
@@ -2503,7 +2798,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
filename.match(/\.excalidraw$/)
|
||||
) {
|
||||
self.updateFileCache(
|
||||
app.vault.getAbstractFileByPath(filename) as TFile,
|
||||
this.app.vault.getAbstractFileByPath(filename) as TFile,
|
||||
fm,
|
||||
);
|
||||
}
|
||||
@@ -2576,14 +2871,14 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
};
|
||||
|
||||
if (leftWorkspaceDrawer) {
|
||||
this.workspaceDrawerLeftObserver = isDebugMode
|
||||
this.workspaceDrawerLeftObserver = this.settings.isDebugMode
|
||||
? new CustomMutationObserver(action, "slidingDrawerLeftObserver")
|
||||
: new MutationObserver(action);
|
||||
this.workspaceDrawerLeftObserver.observe(leftWorkspaceDrawer, options);
|
||||
}
|
||||
|
||||
if (rightWorkspaceDrawer) {
|
||||
this.workspaceDrawerRightObserver = isDebugMode
|
||||
this.workspaceDrawerRightObserver = this.settings.isDebugMode
|
||||
? new CustomMutationObserver(action, "slidingDrawerRightObserver")
|
||||
: new MutationObserver(action);
|
||||
this.workspaceDrawerRightObserver.observe(
|
||||
@@ -2617,7 +2912,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
this.activeExcalidrawView.save();
|
||||
};
|
||||
|
||||
this.modalContainerObserver = isDebugMode
|
||||
this.modalContainerObserver = this.settings.isDebugMode
|
||||
? new CustomMutationObserver(modalContainerObserverFn, "modalContainerObserver")
|
||||
: new MutationObserver(modalContainerObserverFn);
|
||||
this.activeViewDoc = this.activeExcalidrawView.ownerDocument;
|
||||
@@ -2790,7 +3085,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
if(opts.applyLefthandedMode) setLeftHandedMode(this.settings.isLeftHanded);
|
||||
if(opts.reEnableAutosave) this.settings.autosave = true;
|
||||
this.settings.autosaveInterval = app.isMobile
|
||||
this.settings.autosaveInterval = DEVICE.isMobile
|
||||
? this.settings.autosaveIntervalMobile
|
||||
: this.settings.autosaveIntervalDesktop;
|
||||
}
|
||||
@@ -2817,7 +3112,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
public triggerEmbedUpdates(filepath?: string) {
|
||||
const visitedDocs = new Set<Document>();
|
||||
app.workspace.iterateAllLeaves((leaf)=>{
|
||||
const ownerDocument = app.isMobile?document:leaf.view.containerEl.ownerDocument;
|
||||
const ownerDocument = DEVICE.isMobile?document:leaf.view.containerEl.ownerDocument;
|
||||
if(!ownerDocument) return;
|
||||
if(visitedDocs.has(ownerDocument)) return;
|
||||
visitedDocs.add(ownerDocument);
|
||||
@@ -2840,29 +3135,37 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
subpath?: string,
|
||||
justCreated: boolean = false
|
||||
) {
|
||||
if(location === "md-properties") {
|
||||
location = "new-tab";
|
||||
}
|
||||
let leaf: WorkspaceLeaf;
|
||||
if(location === "popout-window") {
|
||||
leaf = app.workspace.openPopoutLeaf();
|
||||
}
|
||||
if(location === "new-tab") {
|
||||
leaf = app.workspace.getLeaf('tab');
|
||||
}
|
||||
if(!leaf) {
|
||||
leaf = this.app.workspace.getLeaf(false);
|
||||
if ((leaf.view.getViewType() !== 'empty') && (location === "new-pane")) {
|
||||
leaf = getNewOrAdjacentLeaf(this, leaf)
|
||||
|
||||
const fnGetLeaf = ():WorkspaceLeaf => {
|
||||
if(location === "md-properties") {
|
||||
location = "new-tab";
|
||||
}
|
||||
let leaf: WorkspaceLeaf;
|
||||
if(location === "popout-window") {
|
||||
leaf = this.app.workspace.openPopoutLeaf();
|
||||
}
|
||||
if(location === "new-tab") {
|
||||
leaf = this.app.workspace.getLeaf('tab');
|
||||
}
|
||||
if(!leaf) {
|
||||
leaf = this.app.workspace.getLeaf(false);
|
||||
if ((leaf.view.getViewType() !== 'empty') && (location === "new-pane")) {
|
||||
leaf = getNewOrAdjacentLeaf(this, leaf)
|
||||
}
|
||||
}
|
||||
return leaf;
|
||||
}
|
||||
|
||||
leaf.openFile(
|
||||
drawingFile,
|
||||
!subpath || subpath === ""
|
||||
const {leaf, promise} = openLeaf({
|
||||
plugin: this,
|
||||
fnGetLeaf: () => fnGetLeaf(),
|
||||
file: drawingFile,
|
||||
openState:!subpath || subpath === ""
|
||||
? {active}
|
||||
: { active, eState: { subpath } }
|
||||
).then(()=>{
|
||||
});
|
||||
|
||||
promise.then(()=>{
|
||||
if(justCreated && this.ea.onFileCreateHook) {
|
||||
try {
|
||||
this.ea.onFileCreateHook({
|
||||
@@ -2878,20 +3181,20 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
|
||||
public async getBlankDrawing(): Promise<string> {
|
||||
const template = this.app.metadataCache.getFirstLinkpathDest(
|
||||
normalizePath(this.settings.templateFilePath),
|
||||
"",
|
||||
);
|
||||
if (template && template instanceof TFile) {
|
||||
if (
|
||||
(template.extension == "md" && !this.settings.compatibilityMode) ||
|
||||
(template.extension == "excalidraw" && this.settings.compatibilityMode)
|
||||
) {
|
||||
const data = await this.app.vault.read(template);
|
||||
if (data) {
|
||||
return this.settings.matchTheme
|
||||
? changeThemeOfExcalidrawMD(data)
|
||||
: data;
|
||||
const templates = getListOfTemplateFiles(this);
|
||||
if(templates) {
|
||||
const template = await templatePromt(templates, this.app);
|
||||
if (template && template instanceof TFile) {
|
||||
if (
|
||||
(template.extension == "md" && !this.settings.compatibilityMode) ||
|
||||
(template.extension == "excalidraw" && this.settings.compatibilityMode)
|
||||
) {
|
||||
const data = await this.app.vault.read(template);
|
||||
if (data) {
|
||||
return this.settings.matchTheme
|
||||
? changeThemeOfExcalidrawMD(data)
|
||||
: data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2923,7 +3226,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
const textElements = excalidrawData.elements?.filter(
|
||||
(el: any) => el.type == "text",
|
||||
);
|
||||
let outString = "# Text Elements\n";
|
||||
let outString = `# Excalidraw Data\n## Text Elements\n`;
|
||||
let id: string;
|
||||
for (const te of textElements) {
|
||||
id = te.id;
|
||||
@@ -2932,7 +3235,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
//also Excalidraw IDs are inconveniently long
|
||||
if (te.id.length > 8) {
|
||||
id = nanoid();
|
||||
data = data.replaceAll(te.id, id); //brute force approach to replace all occurances.
|
||||
data = data.replaceAll(te.id, id); //brute force approach to replace all occurrences.
|
||||
}
|
||||
outString += `${te.originalText ?? te.text} ^${id}\n\n`;
|
||||
}
|
||||
@@ -2984,7 +3287,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
return file.path;
|
||||
}
|
||||
|
||||
public async setMarkdownView(leaf: WorkspaceLeaf) {
|
||||
public async setMarkdownView(leaf: WorkspaceLeaf, eState?: any) {
|
||||
const state = leaf.view.getState();
|
||||
|
||||
//Note v2.0.19: I have absolutely no idea why I thought this is necessary. Removing this.
|
||||
@@ -3000,8 +3303,14 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
state,
|
||||
popstate: true,
|
||||
} as ViewState,
|
||||
{ focus: true },
|
||||
eState ? eState : { focus: true },
|
||||
);
|
||||
|
||||
const mdView = leaf.view;
|
||||
if(mdView instanceof MarkdownView) {
|
||||
foldExcalidrawSection(mdView);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async setExcalidrawView(leaf: WorkspaceLeaf) {
|
||||
@@ -3022,7 +3331,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
|
||||
public async exportLibrary() {
|
||||
if (this.app.isMobile) {
|
||||
if (DEVICE.isMobile) {
|
||||
const prompt = new Prompt(
|
||||
this.app,
|
||||
"Please provide a filename",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Copy, Crop, Globe, RotateCcw, Scan, Settings } from "lucide-react";
|
||||
import { Copy, Crop, Globe, RotateCcw, Scan, Settings, TextSelect } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { PenStyle } from "src/PenTypes";
|
||||
|
||||
@@ -26,6 +26,7 @@ export const ICONS = {
|
||||
</g>
|
||||
</svg>
|
||||
),
|
||||
BackOfNote: (<TextSelect />),
|
||||
Reload: (<RotateCcw />),
|
||||
Copy: (<Copy /> ),
|
||||
Globe: (<Globe />),
|
||||
|
||||
@@ -7,12 +7,14 @@ 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 {
|
||||
|
||||
@@ -34,10 +36,13 @@ 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;
|
||||
//mutateElement (element,{link});
|
||||
//view.setDirty(99);
|
||||
view.excalidrawData.elementLinks.set(element.id, link);
|
||||
view.setDirty(99);
|
||||
view.updateScene({appState: {activeEmbeddable: null}});
|
||||
ea.addElementsToView(false, true, true);
|
||||
}
|
||||
|
||||
private menuFadeTimeout: number = 0;
|
||||
@@ -99,6 +104,7 @@ export class EmbeddableMenu {
|
||||
const { subpath, file } = processLinkText(link, view);
|
||||
if(!file) return;
|
||||
const isMD = file.extension==="md";
|
||||
const isExcalidrawFile = view.plugin.isExcalidrawFile(file);
|
||||
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`;
|
||||
@@ -128,15 +134,23 @@ export class EmbeddableMenu {
|
||||
key={"MarkdownSection"}
|
||||
title={t("NARROW_TO_HEADING")}
|
||||
action={async () => {
|
||||
view.updateScene({appState: {activeEmbeddable: null}});
|
||||
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)
|
||||
);
|
||||
.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"
|
||||
);
|
||||
@@ -149,18 +163,21 @@ export class EmbeddableMenu {
|
||||
view={view}
|
||||
/>
|
||||
)}
|
||||
{isMD && (
|
||||
{isMD && !isExcalidrawFile && (
|
||||
<ActionButton
|
||||
key={"MarkdownBlock"}
|
||||
title={t("NARROW_TO_BLOCK")}
|
||||
action={async () => {
|
||||
if(!file) return;
|
||||
const paragrphs = (await app.metadataCache.blockCache
|
||||
view.updateScene({appState: {activeEmbeddable: null}});
|
||||
const paragraphs = (await app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "paragraph");
|
||||
const values = ["entire-file"].concat(paragrphs);
|
||||
.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(
|
||||
paragrphs.map((b: any) => `${b.node?.id ? `#^${b.node.id}: ` : ``}${b.display.trim()}`));
|
||||
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"
|
||||
|
||||
@@ -15,6 +15,7 @@ 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";
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
const dark = '<svg style="stroke:#ced4da;#212529;color:#ced4da;fill:#ced4da" ';
|
||||
@@ -380,7 +381,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
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));
|
||||
this.props.view.plugin.taskbone.getTextForView(this.props.view, {forceReScan: isWinCTRLorMacCMD(e)});
|
||||
}}
|
||||
icon={ICONS.ocr}
|
||||
view={this.props.view}
|
||||
@@ -461,11 +462,37 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
icon={ICONS.switchToMarkdown}
|
||||
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}
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Insert actions</legend>
|
||||
<div className="buttonList buttonListIcon">
|
||||
<ActionButton
|
||||
key={"anyfile"}
|
||||
title={t("UNIVERSAL_ADD_FILE")}
|
||||
action={() => {
|
||||
this.props.centerPointer();
|
||||
const insertFileModal = new UniversalInsertFileModal(this.props.view.plugin, this.props.view);
|
||||
insertFileModal.open();
|
||||
}}
|
||||
icon={ICONS["add-file"]}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"image"}
|
||||
title={t("INSERT_IMAGE")}
|
||||
@@ -501,6 +528,16 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
icon={ICONS.insertMD}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"insertBackOfNote"}
|
||||
title={t("INSERT_CARD")}
|
||||
action={() => {
|
||||
this.props.centerPointer();
|
||||
this.props.view.insertBackOfTheNoteCard();
|
||||
}}
|
||||
icon={ICONS.BackOfNote}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"latex"}
|
||||
title={t("INSERT_LATEX")}
|
||||
@@ -528,21 +565,6 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
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")}
|
||||
|
||||
@@ -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";
|
||||
@@ -39,23 +40,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,26 +66,52 @@ 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);
|
||||
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);
|
||||
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)
|
||||
return;
|
||||
}
|
||||
|
||||
const text = await this.getTextForElements(viewElements, ea);
|
||||
if(text) {
|
||||
fe.setKey("taskbone-ocr",text);
|
||||
view.data = fe.data;
|
||||
view.save(false);
|
||||
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 in the drawing's frontmatter and onto the system clipboard.");
|
||||
new Notice(`I placed the recognized text onto the system clipboard${addToFrontmatter?" and to document properties":""}.`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +127,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 +148,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");
|
||||
|
||||
318
src/settings.ts
318
src/settings.ts
@@ -8,7 +8,7 @@ import {
|
||||
TextComponent,
|
||||
TFile,
|
||||
} from "obsidian";
|
||||
import { GITHUB_RELEASES, VIEW_TYPE_EXCALIDRAW } from "./constants/constants";
|
||||
import { DEVICE, GITHUB_RELEASES, VIEW_TYPE_EXCALIDRAW } from "./constants/constants";
|
||||
import ExcalidrawView from "./ExcalidrawView";
|
||||
import { t } from "./lang/helpers";
|
||||
import type ExcalidrawPlugin from "./main";
|
||||
@@ -33,15 +33,18 @@ 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";
|
||||
|
||||
export interface ExcalidrawSettings {
|
||||
folder: string;
|
||||
cropFolder: string;
|
||||
annotateFolder: string;
|
||||
embedUseExcalidrawFolder: boolean;
|
||||
templateFilePath: string;
|
||||
scriptFolderPath: string;
|
||||
compress: boolean;
|
||||
decompressForMDView: boolean;
|
||||
autosave: boolean;
|
||||
autosaveInterval: number;
|
||||
autosaveIntervalDesktop: number;
|
||||
@@ -52,12 +55,15 @@ 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;
|
||||
displayExportedImageIfAvailable: boolean;
|
||||
previewMatchObsidianTheme: boolean;
|
||||
width: string;
|
||||
height: string;
|
||||
dynamicStyling: DynamicStyle;
|
||||
isLeftHanded: boolean;
|
||||
iframeMatchExcalidrawTheme: boolean;
|
||||
@@ -66,12 +72,17 @@ export interface ExcalidrawSettings {
|
||||
matchThemeTrigger: boolean;
|
||||
defaultMode: string;
|
||||
defaultPenMode: "never" | "mobile" | "always";
|
||||
penModeCrosshairVisible: boolean;
|
||||
renderImageInMarkdownReadingMode: 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 +102,7 @@ export interface ExcalidrawSettings {
|
||||
exportWithTheme: boolean;
|
||||
exportWithBackground: boolean;
|
||||
exportPaddingSVG: number;
|
||||
exportEmbedScene: boolean;
|
||||
keepInSync: boolean;
|
||||
autoexportSVG: boolean;
|
||||
autoexportPNG: boolean;
|
||||
@@ -104,6 +116,7 @@ export interface ExcalidrawSettings {
|
||||
experimentalFileType: boolean;
|
||||
experimentalFileTag: string;
|
||||
experimentalLivePreview: boolean;
|
||||
fadeOutExcalidrawMarkup: boolean;
|
||||
experimentalEnableFourthFont: boolean;
|
||||
experimantalFourthFont: string;
|
||||
fieldSuggester: boolean;
|
||||
@@ -174,6 +187,9 @@ export interface ExcalidrawSettings {
|
||||
},
|
||||
slidingPanesSupport: boolean;
|
||||
areaZoomLimit: number;
|
||||
longPressDesktop: number;
|
||||
longPressMobile: number;
|
||||
isDebugMode: boolean;
|
||||
}
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
@@ -181,10 +197,12 @@ 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,
|
||||
decompressForMDView: true,
|
||||
autosave: true,
|
||||
autosaveInterval: 15000,
|
||||
autosaveIntervalDesktop: 15000,
|
||||
@@ -195,12 +213,15 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
drawingFilenameDateTime: "YYYY-MM-DD HH.mm.ss",
|
||||
useExcalidrawExtension: true,
|
||||
cropPrefix: CROPPED_PREFIX,
|
||||
annotatePrefix: ANNOTATED_PREFIX,
|
||||
annotatePreserveSize: false,
|
||||
displaySVGInPreview: undefined,
|
||||
previewImageType: undefined,
|
||||
allowImageCache: true,
|
||||
displayExportedImageIfAvailable: false,
|
||||
previewMatchObsidianTheme: false,
|
||||
width: "400",
|
||||
height: "",
|
||||
dynamicStyling: "colorful",
|
||||
isLeftHanded: false,
|
||||
iframeMatchExcalidrawTheme: true,
|
||||
@@ -209,6 +230,9 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
matchThemeTrigger: false,
|
||||
defaultMode: "normal",
|
||||
defaultPenMode: "never",
|
||||
penModeCrosshairVisible: true,
|
||||
renderImageInMarkdownReadingMode: false,
|
||||
renderImageInMarkdownToPDF: false,
|
||||
allowPinchZoom: false,
|
||||
allowWheelZoom: false,
|
||||
zoomToFitOnOpen: true,
|
||||
@@ -222,6 +246,8 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
hoverPreviewWithoutCTRL: false,
|
||||
linkOpacity: 1,
|
||||
openInAdjacentPane: false,
|
||||
showSecondOrderLinks: true,
|
||||
focusOnFileTab: false,
|
||||
openInMainWorkspace: true,
|
||||
showLinkBrackets: true,
|
||||
allowCtrlClick: true,
|
||||
@@ -234,6 +260,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,6 +273,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
experimentalFileType: false,
|
||||
experimentalFileTag: "✏️",
|
||||
experimentalLivePreview: true,
|
||||
fadeOutExcalidrawMarkup: false,
|
||||
experimentalEnableFourthFont: false,
|
||||
experimantalFourthFont: "Virgil",
|
||||
fieldSuggester: true,
|
||||
@@ -409,6 +437,9 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
},
|
||||
slidingPanesSupport: false,
|
||||
areaZoomLimit: 1,
|
||||
longPressDesktop: 500,
|
||||
longPressMobile: 500,
|
||||
isDebugMode: false,
|
||||
};
|
||||
|
||||
export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
@@ -570,6 +601,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,6 +666,18 @@ 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")))
|
||||
@@ -634,7 +690,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
.setValue(this.plugin.settings.autosaveIntervalDesktop.toString())
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.autosaveIntervalDesktop = parseInt(value);
|
||||
this.plugin.settings.autosaveInterval = app.isMobile
|
||||
this.plugin.settings.autosaveInterval = DEVICE.isMobile
|
||||
? this.plugin.settings.autosaveIntervalMobile
|
||||
: this.plugin.settings.autosaveIntervalDesktop;
|
||||
this.applySettingsUpdate();
|
||||
@@ -653,7 +709,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
.setValue(this.plugin.settings.autosaveIntervalMobile.toString())
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.autosaveIntervalMobile = parseInt(value);
|
||||
this.plugin.settings.autosaveInterval = app.isMobile
|
||||
this.plugin.settings.autosaveInterval = DEVICE.isMobile
|
||||
? this.plugin.settings.autosaveIntervalMobile
|
||||
: this.plugin.settings.autosaveIntervalDesktop;
|
||||
this.applySettingsUpdate();
|
||||
@@ -766,7 +822,6 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("CROP_PREFIX_NAME"))
|
||||
.setDesc(fragWithHTML(t("CROP_PREFIX_DESC")))
|
||||
@@ -783,6 +838,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 +972,30 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
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();
|
||||
}),
|
||||
);
|
||||
|
||||
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();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("LEFTHANDED_MODE_NAME"))
|
||||
.setDesc(fragWithHTML(t("LEFTHANDED_MODE_DESC")))
|
||||
@@ -902,43 +1011,43 @@ 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")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.iframeMatchExcalidrawTheme)
|
||||
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 +1254,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 +1319,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 +1342,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"))
|
||||
@@ -1583,12 +1757,36 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
cls: "excalidraw-setting-h3",
|
||||
});
|
||||
addIframe(detailsEl, "wTtaXmRJ7wg",171);
|
||||
|
||||
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();
|
||||
}),
|
||||
);
|
||||
|
||||
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 +1801,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)
|
||||
@@ -2124,6 +2336,19 @@ 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();
|
||||
}),
|
||||
);
|
||||
|
||||
detailsEl = experimentalDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
text: t("TASKBONE_HEAD"),
|
||||
@@ -2252,6 +2477,19 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
cls: "excalidraw-setting-h1",
|
||||
});
|
||||
|
||||
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;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("SLIDING_PANES_NAME"))
|
||||
.setDesc(fragWithHTML(t("SLIDING_PANES_DESC")))
|
||||
|
||||
@@ -4,37 +4,46 @@ 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();
|
||||
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,
|
||||
};
|
||||
};
|
||||
|
||||
18
src/types.d.ts
vendored
18
src/types.d.ts
vendored
@@ -58,9 +58,23 @@ declare module "obsidian" {
|
||||
},
|
||||
basePath: string;
|
||||
}
|
||||
interface Editor {
|
||||
insertText(data: string): void;
|
||||
interface FoldPosition {
|
||||
from: number;
|
||||
to: number;
|
||||
}
|
||||
|
||||
interface FoldInfo {
|
||||
folds: FoldPosition[];
|
||||
lines: number;
|
||||
}
|
||||
|
||||
interface MarkdownSubView {
|
||||
applyFoldInfo(foldInfo: FoldInfo): void;
|
||||
getFoldInfo(): FoldInfo | null;
|
||||
}
|
||||
/*interface Editor {
|
||||
insertText(data: string): void;
|
||||
}*/
|
||||
interface MetadataCache {
|
||||
getBacklinksForFile(file: TFile): any;
|
||||
getLinks(): { [id: string]: Array<{ link: string; displayText: string; original: string; position: any }> };
|
||||
|
||||
@@ -80,8 +80,11 @@ export class CanvasNodeFactory {
|
||||
return node;
|
||||
}
|
||||
|
||||
public startEditing(node: ObsidianCanvasNode, theme: string) {
|
||||
public async startEditing(node: ObsidianCanvasNode, theme: string) {
|
||||
if (!this.initialized || !node) return;
|
||||
if (node.file === this.view.file) {
|
||||
await this.view.setEmbeddableIsEditingSelf();
|
||||
}
|
||||
node.startEditing();
|
||||
|
||||
const obsidianTheme = isObsidianThemeDark() ? "theme-dark" : "theme-light";
|
||||
@@ -107,7 +110,7 @@ export class CanvasNodeFactory {
|
||||
}
|
||||
}
|
||||
};
|
||||
const observer = isDebugMode
|
||||
const observer = isDebugMode()
|
||||
? new CustomMutationObserver(nodeObserverFn, "CanvasNodeFactory")
|
||||
: new MutationObserver(nodeObserverFn);
|
||||
|
||||
@@ -118,6 +121,9 @@ export class CanvasNodeFactory {
|
||||
public stopEditing(node: ObsidianCanvasNode) {
|
||||
if(!this.initialized || !node) return;
|
||||
if(!node.child.editMode) return;
|
||||
if(node.file === this.view.file) {
|
||||
this.view.clearEmbeddableIsEditingSelf();
|
||||
}
|
||||
node.child.showPreview();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,11 @@ import { ExcalidrawEmbeddableElement, ExcalidrawFrameElement, ExcalidrawImageEle
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import { getEA } from "src";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { getCropFileNameAndFolder, splitFolderAndFilename } from "./FileUtils";
|
||||
import { getCropFileNameAndFolder, getListOfTemplateFiles, splitFolderAndFilename } from "./FileUtils";
|
||||
import { Notice, TFile } from "obsidian";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
|
||||
export const CROPPED_PREFIX = "cropped_";
|
||||
export const ANNOTATED_PREFIX = "annotated_";
|
||||
|
||||
export const carveOutImage = async (sourceEA: ExcalidrawAutomate, viewImageEl: ExcalidrawImageElement) => {
|
||||
if(!viewImageEl?.fileId) return;
|
||||
@@ -70,7 +69,7 @@ export const carveOutPDF = async (sourceEA: ExcalidrawAutomate, embeddableEl: Ex
|
||||
|
||||
const targetEA = getEA(sourceEA.targetView) as ExcalidrawAutomate;
|
||||
|
||||
const {height, width} = embeddableEl;
|
||||
let {height, width} = embeddableEl;
|
||||
|
||||
if(!height || !width || height === 0 || width === 0) return;
|
||||
|
||||
@@ -78,8 +77,6 @@ export const carveOutPDF = async (sourceEA: ExcalidrawAutomate, embeddableEl: Ex
|
||||
const newImage = targetEA.getElement(imageId) as Mutable<ExcalidrawImageElement>;
|
||||
newImage.x = 0;
|
||||
newImage.y = 0;
|
||||
newImage.width = width;
|
||||
newImage.height = height;
|
||||
const angle = embeddableEl.angle;
|
||||
|
||||
const fname = pdfFile.basename;
|
||||
@@ -133,7 +130,8 @@ export const createImageCropperFile = async (targetEA: ExcalidrawAutomate, image
|
||||
targetEA.canvas.theme = "light";
|
||||
targetEA.canvas.viewBackgroundColor = isPDF ? "#5d5d5d" : "#3d3d3d";
|
||||
|
||||
const templateFile = app.vault.getAbstractFileByPath(targetEA.plugin.settings.templateFilePath);
|
||||
const templates = getListOfTemplateFiles(targetEA.plugin);
|
||||
const templateFile = templates && templates.length > 0 ? templates[0] : null;
|
||||
if(templateFile && templateFile instanceof TFile) {
|
||||
const {appState} = await targetEA.getSceneFromFile(templateFile);
|
||||
if(appState) {
|
||||
|
||||
@@ -97,16 +97,12 @@ export class CropImage {
|
||||
withTheme: false,
|
||||
isMask: false,
|
||||
}
|
||||
const isRotated = this.imageEA.getElements().some(el=>el.type === "image" && el.angle !== 0);
|
||||
const images = Object.values(this.imageEA.imagesDict);
|
||||
if(images.length === 1) {
|
||||
if(!isRotated && (images.length === 1)) {
|
||||
return images[0].dataURL;
|
||||
}
|
||||
return await this.imageEA.createPNGBase64(null,1,exportSettings,null,null,0);
|
||||
const imageSVG = await this.imageEA.createSVG(null,false,exportSettings,null,null,0);
|
||||
const svgData = new XMLSerializer().serializeToString(imageSVG);
|
||||
return `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svgData)))}`;
|
||||
// const blob = new Blob([svgString], { type: 'image/svg+xml' });
|
||||
// return `data:image/svg+xml;base64,${await blobToBase64(blob)}`;
|
||||
}
|
||||
|
||||
private async buildSVG(): Promise<SVGSVGElement> {
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
export const isDebugMode = false;
|
||||
import { EXCALIDRAW_PLUGIN } from "src/constants/constants";
|
||||
|
||||
|
||||
export const durationTreshold = 0; //0.05; //ms
|
||||
export const isDebugMode = () => EXCALIDRAW_PLUGIN && EXCALIDRAW_PLUGIN.settings?.isDebugMode;
|
||||
|
||||
export const log = console.log.bind(window.console);
|
||||
export const debug = (...messages: unknown[]) => {
|
||||
if(isDebugMode()) {
|
||||
console.log(...messages);
|
||||
}
|
||||
};
|
||||
|
||||
export class CustomMutationObserver {
|
||||
private originalCallback: MutationCallback;
|
||||
|
||||
@@ -32,6 +32,10 @@ export const setDynamicStyle = (
|
||||
view?.excalidrawAPI?.getAppState?.()?.theme === "light" ||
|
||||
view?.excalidrawData?.scene?.appState?.theme === "light";
|
||||
|
||||
if (color==="transparent") {
|
||||
color = "#ffffff";
|
||||
}
|
||||
|
||||
const darker = "#101010";
|
||||
const lighter = "#f0f0f0";
|
||||
const step = 10;
|
||||
@@ -98,7 +102,6 @@ export const setDynamicStyle = (
|
||||
[`--color-gray-50`]: str(text), //frame
|
||||
[`--color-surface-highlight`]: str(gray1()),
|
||||
//[`--color-gray-30`]: str(gray1),
|
||||
[`--color-gray-80`]: str(isDark?text.darkerBy(40):text.lighterBy(40)), //frame
|
||||
[`--sidebar-border-color`]: str(gray1()),
|
||||
[`--color-primary-light`]: str(accent().lighterBy(step)),
|
||||
[`--button-hover-bg`]: str(gray1()),
|
||||
@@ -111,8 +114,10 @@ export const setDynamicStyle = (
|
||||
[`--h2-color`]: str(text),
|
||||
[`--h3-color`]: str(text),
|
||||
[`--h4-color`]: str(text),
|
||||
[`color`]: str(text),
|
||||
[`color`]: str(text),
|
||||
['--excalidraw-caret-color']: str(text),
|
||||
[`--select-highlight-color`]: str(gray1()),
|
||||
[`--color-gray-80`]: str(isDark?text.darkerBy(40):text.lighterBy(40)), //frame
|
||||
};
|
||||
|
||||
const styleString = Object.keys(styleObject)
|
||||
|
||||
@@ -4,7 +4,7 @@ import { App, TFile } from "obsidian";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { REGEX_LINK, REG_LINKINDEX_HYPERLINK } from "src/ExcalidrawData";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import { ExcalidrawElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ExcalidrawElement, ExcalidrawFrameElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { getLinkParts } from "./Utils";
|
||||
|
||||
export const insertImageToView = async (
|
||||
@@ -101,7 +101,7 @@ export const openExternalLink = (link:string, app: App, element?: ExcalidrawElem
|
||||
return false;
|
||||
}
|
||||
|
||||
export const getExcalidrawFileForwardLinks = (app: App, excalidrawFile: TFile):string => {
|
||||
export const getExcalidrawFileForwardLinks = (app: App, excalidrawFile: TFile, secondOrderLinksSet: Set<string>):string => {
|
||||
let secondOrderLinks = "";
|
||||
const forwardLinks = app.metadataCache.getLinks()[excalidrawFile.path];
|
||||
if(forwardLinks && forwardLinks.length > 0) {
|
||||
@@ -110,10 +110,23 @@ export const getExcalidrawFileForwardLinks = (app: App, excalidrawFile: TFile):s
|
||||
const linkparts = getLinkParts(link.link);
|
||||
const f = app.metadataCache.getFirstLinkpathDest(linkparts.path, excalidrawFile.path);
|
||||
if(f && f.path !== excalidrawFile.path) {
|
||||
if(secondOrderLinksSet.has(f.path)) return;
|
||||
secondOrderLinksSet.add(f.path);
|
||||
linkset.add(`[[${f.path}${linkparts.ref?"#"+linkparts.ref:""}|Second Order Link: ${f.basename}]]`);
|
||||
}
|
||||
});
|
||||
secondOrderLinks = [...linkset].join(" ");
|
||||
}
|
||||
return secondOrderLinks;
|
||||
}
|
||||
|
||||
export const getFrameBasedOnFrameNameOrId = (frameName: string, elements: ExcalidrawElement[]): ExcalidrawFrameElement | null => {
|
||||
const frames = elements
|
||||
.filter((el: ExcalidrawElement)=>el.type==="frame")
|
||||
.map((el: ExcalidrawFrameElement, idx: number)=>{
|
||||
return {el: el, id: el.id, name: el.name ?? `Frame ${String(idx+1).padStart(2,"0")}`};
|
||||
})
|
||||
.filter((item:any) => item.id === frameName || item.name === frameName)
|
||||
.map((item:any)=>item.el as ExcalidrawFrameElement);
|
||||
return frames.length === 1 ? frames[0] : null;
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
import { DataURL } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { loadPdfJs, normalizePath, Notice, requestUrl, RequestUrlResponse, TAbstractFile, TFile, TFolder, Vault } from "obsidian";
|
||||
import { DEVICE, URLFETCHTIMEOUT } from "src/constants/constants";
|
||||
import { App, loadPdfJs, normalizePath, Notice, requestUrl, RequestUrlResponse, TAbstractFile, TFile, TFolder, Vault } from "obsidian";
|
||||
import { DEVICE, FRONTMATTER_KEYS, URLFETCHTIMEOUT } from "src/constants/constants";
|
||||
import { IMAGE_MIME_TYPES, MimeType } from "src/EmbeddedFileLoader";
|
||||
import { ExcalidrawSettings } from "src/settings";
|
||||
import { errorlog, getDataURL } from "./Utils";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import { CROPPED_PREFIX } from "./CarveOut";
|
||||
import { ANNOTATED_PREFIX, CROPPED_PREFIX } from "./CarveOut";
|
||||
import { getAttachmentsFolderAndFilePath } from "./ObsidianUtils";
|
||||
|
||||
/**
|
||||
@@ -368,13 +367,24 @@ export const getInternalLinkOrFileURLLink = (
|
||||
*/
|
||||
export const getLink = (
|
||||
plugin: ExcalidrawPlugin,
|
||||
{ embed = true, path, alias }: { embed?: boolean; path: string; alias?: string }
|
||||
{ embed = true, path, alias }: { embed?: boolean; path: string; alias?: string },
|
||||
wikilinkOverride?: boolean
|
||||
):string => {
|
||||
return plugin.settings.embedWikiLink
|
||||
const isWikiLink = (typeof wikilinkOverride !== "undefined")
|
||||
? wikilinkOverride
|
||||
: plugin.settings.embedWikiLink;
|
||||
return isWikiLink
|
||||
? `${embed ? "!" : ""}[[${path}${alias ? `|${alias}` : ""}]]`
|
||||
: `${embed ? "!" : ""}[${alias ?? ""}](${encodeURI(path)})`
|
||||
}
|
||||
|
||||
export const getAliasWithSize = (alias: string, size: string): string => {
|
||||
if(alias && alias !== "") {
|
||||
return `${alias}${size?`|${size}`:""}`;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
export const getCropFileNameAndFolder = async (plugin: ExcalidrawPlugin, hostPath: string, baseNewFileName: string):Promise<{folderpath: string, filename: string}> => {
|
||||
let prefix = plugin.settings.cropPrefix;
|
||||
if(!prefix || prefix.trim() === "") prefix = CROPPED_PREFIX;
|
||||
@@ -386,4 +396,82 @@ export const getCropFileNameAndFolder = async (plugin: ExcalidrawPlugin, hostPat
|
||||
const folderpath = normalizePath(plugin.settings.cropFolder);
|
||||
await checkAndCreateFolder(folderpath);
|
||||
return {folderpath, filename};
|
||||
}
|
||||
}
|
||||
|
||||
export const getAnnotationFileNameAndFolder = async (plugin: ExcalidrawPlugin, hostPath: string, baseNewFileName: string):Promise<{folderpath: string, filename: string}> => {
|
||||
let prefix = plugin.settings.annotatePrefix;
|
||||
if(!prefix || prefix.trim() === "") prefix = ANNOTATED_PREFIX;
|
||||
const filename = prefix + baseNewFileName + ".md";
|
||||
if(!plugin.settings.annotateFolder || plugin.settings.annotateFolder.trim() === "") {
|
||||
const folderpath = (await getAttachmentsFolderAndFilePath(plugin.app, hostPath, filename)).folder;
|
||||
return {folderpath, filename};
|
||||
}
|
||||
const folderpath = normalizePath(plugin.settings.annotateFolder);
|
||||
await checkAndCreateFolder(folderpath);
|
||||
return {folderpath, filename};
|
||||
}
|
||||
|
||||
export const getListOfTemplateFiles = (plugin: ExcalidrawPlugin):TFile[] | null => {
|
||||
const normalizedTemplatePath = normalizePath(plugin.settings.templateFilePath);
|
||||
const template = plugin.app.vault.getAbstractFileByPath(normalizedTemplatePath);
|
||||
if(template && template instanceof TFolder) {
|
||||
return plugin.app.vault.getFiles()
|
||||
.filter(f=>f.path.startsWith(template.path))
|
||||
.filter(f=>plugin.isExcalidrawFile(f))
|
||||
.sort((a,b)=>a.path.localeCompare(b.path))
|
||||
}
|
||||
if(template && template instanceof TFile) {
|
||||
return [template];
|
||||
}
|
||||
const templateFile = plugin.app.metadataCache.getFirstLinkpathDest(
|
||||
normalizedTemplatePath,
|
||||
"",
|
||||
);
|
||||
if(templateFile) {
|
||||
return [templateFile];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export const fileShouldDefaultAsExcalidraw = (path:string, app:App):boolean => {
|
||||
if(!path) return false;
|
||||
const cache = app.metadataCache.getCache(path);
|
||||
return cache?.frontmatter &&
|
||||
cache.frontmatter[FRONTMATTER_KEYS["plugin"].name] &&
|
||||
!Boolean(cache.frontmatter[FRONTMATTER_KEYS["open-as-markdown"].name]);
|
||||
}
|
||||
|
||||
export const getExcalidrawEmbeddedFilesFiletree = (sourceFile: TFile, plugin: ExcalidrawPlugin):TFile[] => {
|
||||
if(!sourceFile || !plugin.isExcalidrawFile(sourceFile)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const fileList = new Set<TFile>();
|
||||
const app = plugin.app;
|
||||
|
||||
const addAttachedImages = (f:TFile) => Object
|
||||
.keys(app.metadataCache.resolvedLinks[f.path])
|
||||
.forEach(path => {
|
||||
const file = app.vault.getAbstractFileByPath(path);
|
||||
if (!file || !(file instanceof TFile)) return;
|
||||
const isExcalidraw = plugin.isExcalidrawFile(file);
|
||||
if (
|
||||
(file.extension === "md" && !isExcalidraw) ||
|
||||
fileList.has(file) //avoid infinite loops
|
||||
) {
|
||||
return;
|
||||
}
|
||||
fileList.add(file);
|
||||
if (isExcalidraw) {
|
||||
addAttachedImages(file);
|
||||
}
|
||||
});
|
||||
|
||||
addAttachedImages(sourceFile);
|
||||
return Array.from(fileList);
|
||||
}
|
||||
|
||||
export const hasExcalidrawEmbeddedImagesTreeChanged = (sourceFile: TFile, mtime:number, plugin: ExcalidrawPlugin):boolean => {
|
||||
const fileList = getExcalidrawEmbeddedFilesFiletree(sourceFile, plugin);
|
||||
return fileList.some(f=>f.stat.mtime > mtime);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export const getElementsAtPointer = (
|
||||
y <= pointer.y &&
|
||||
y + h >= pointer.y
|
||||
);
|
||||
});
|
||||
}).reverse();
|
||||
};
|
||||
|
||||
export const getTextElementAtPointer = (pointer: any, view: ExcalidrawView) => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { App, Notice, TFile } from "obsidian";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { convertSVGStringToElement } from "./Utils";
|
||||
import { FILENAMEPARTS, PreviewImageType } from "./UtilTypes";
|
||||
import { hasExcalidrawEmbeddedImagesTreeChanged } from "./FileUtils";
|
||||
|
||||
//@ts-ignore
|
||||
const DB_NAME = "Excalidraw " + app.appId;
|
||||
@@ -19,6 +20,7 @@ export type ImageKey = {
|
||||
isDark: boolean;
|
||||
previewImageType: PreviewImageType;
|
||||
scale: number;
|
||||
isTransparent: boolean;
|
||||
} & FILENAMEPARTS;
|
||||
|
||||
const getKey = (key: ImageKey): string =>
|
||||
@@ -29,7 +31,7 @@ const getKey = (key: ImageKey): string =>
|
||||
: key.previewImageType === PreviewImageType.PNG
|
||||
? 0
|
||||
: 2
|
||||
}#${key.scale}`; //key.isSVG ? 1 : 0
|
||||
}#${key.scale}${key.isTransparent?"#t":""}`; //key.isSVG ? 1 : 0
|
||||
|
||||
class ImageCache {
|
||||
private dbName: string;
|
||||
@@ -291,7 +293,10 @@ class ImageCache {
|
||||
|
||||
const file = this.app.vault.getAbstractFileByPath(key_.filepath.split("#")[0]);
|
||||
if (!file || !(file instanceof TFile)) return undefined;
|
||||
if (cachedData && cachedData.mtime === file.stat.mtime) {
|
||||
if (cachedData && cachedData.mtime >= file.stat.mtime) {
|
||||
if(hasExcalidrawEmbeddedImagesTreeChanged(file, cachedData.mtime, this.plugin)) {
|
||||
return undefined;
|
||||
}
|
||||
if(cachedData.svg) {
|
||||
return convertSVGStringToElement(cachedData.svg);
|
||||
}
|
||||
@@ -332,7 +337,7 @@ class ImageCache {
|
||||
} else {
|
||||
blob = image as Blob;
|
||||
}
|
||||
const data: FileCacheData = { mtime: file.stat.mtime, blob, svg};
|
||||
const data: FileCacheData = { mtime: Date.now(), blob, svg};
|
||||
const transaction = this.db.transaction(this.cacheStoreName, "readwrite");
|
||||
const store = transaction.objectStore(this.cacheStoreName);
|
||||
const key = getKey(key_);
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import {
|
||||
App,
|
||||
normalizePath, parseFrontMatterEntry, TFile, View, Workspace, WorkspaceLeaf, WorkspaceSplit
|
||||
Editor,
|
||||
FrontMatterCache,
|
||||
MarkdownView,
|
||||
normalizePath, OpenViewState, parseFrontMatterEntry, TFile, View, Workspace, WorkspaceLeaf, WorkspaceSplit
|
||||
} from "obsidian";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { checkAndCreateFolder, splitFolderAndFilename } from "./FileUtils";
|
||||
import { linkClickModifierType, ModifierKeys } from "./ModifierkeyHelper";
|
||||
import { REG_BLOCK_REF_CLEAN, REG_SECTION_REF_CLEAN } from "src/constants/constants";
|
||||
import yaml, { Mark } from "js-yaml";
|
||||
|
||||
export const getParentOfClass = (element: Element, cssClass: string):HTMLElement | null => {
|
||||
let parent = element.parentElement;
|
||||
@@ -257,4 +261,110 @@ export const getFileCSSClasses = (
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
export const getActivePDFPageNumberFromPDFView = (view: View): number => view?.viewer?.child?.pdfViewer?.page;
|
||||
export const getActivePDFPageNumberFromPDFView = (view: View): number => view?.viewer?.child?.pdfViewer?.page;
|
||||
|
||||
export const openLeaf = ({
|
||||
plugin,
|
||||
fnGetLeaf,
|
||||
file,
|
||||
openState
|
||||
}:{
|
||||
plugin: ExcalidrawPlugin,
|
||||
fnGetLeaf: ()=>WorkspaceLeaf,
|
||||
file: TFile,
|
||||
openState?: OpenViewState
|
||||
}) : {
|
||||
leaf: WorkspaceLeaf
|
||||
promise: Promise<void>
|
||||
} => {
|
||||
let leaf:WorkspaceLeaf = null;
|
||||
if (plugin.settings.focusOnFileTab) {
|
||||
plugin.app.workspace.iterateAllLeaves((l) => {
|
||||
if(leaf) return;
|
||||
//@ts-ignore
|
||||
if (l?.view?.file === file) {
|
||||
plugin.app.workspace.setActiveLeaf(l,{focus: true});
|
||||
leaf = l;
|
||||
}
|
||||
});
|
||||
if(leaf) {
|
||||
if(openState) {
|
||||
const promise = leaf.openFile(file, openState);
|
||||
return {leaf, promise};
|
||||
}
|
||||
return {leaf, promise: Promise.resolve()};
|
||||
}
|
||||
}
|
||||
leaf = fnGetLeaf();
|
||||
const promise = leaf.openFile(file, openState);
|
||||
return {leaf, promise};
|
||||
}
|
||||
|
||||
export const mergeMarkdownFiles = (template: string, target: string): string => {
|
||||
// Extract frontmatter from the template
|
||||
const templateFrontmatterEnd = template.indexOf('---', 4); // Find end of frontmatter
|
||||
const templateFrontmatter = template.substring(4, templateFrontmatterEnd).trim();
|
||||
const templateContent = template.substring(templateFrontmatterEnd + 3); // Skip frontmatter and ---
|
||||
|
||||
// Parse template frontmatter
|
||||
const templateFrontmatterObj: FrontMatterCache = yaml.load(templateFrontmatter) || {};
|
||||
|
||||
// Extract frontmatter from the target if it exists
|
||||
let targetFrontmatterObj: FrontMatterCache = {};
|
||||
let targetContent = '';
|
||||
if (target.includes('---')) {
|
||||
const targetFrontmatterEnd = target.indexOf('---', 4); // Find end of frontmatter
|
||||
const targetFrontmatter = target.substring(4, targetFrontmatterEnd).trim();
|
||||
targetContent = target.substring(targetFrontmatterEnd + 3); // Skip frontmatter and ---
|
||||
|
||||
// Parse target frontmatter
|
||||
targetFrontmatterObj = yaml.load(targetFrontmatter) || {};
|
||||
} else {
|
||||
// If target doesn't have frontmatter, consider the entire content as target content
|
||||
targetContent = target.trim();
|
||||
}
|
||||
|
||||
// Merge frontmatter with target values taking precedence
|
||||
const mergedFrontmatter: FrontMatterCache = { ...templateFrontmatterObj };
|
||||
|
||||
// Merge arrays by combining and removing duplicates
|
||||
for (const key in targetFrontmatterObj) {
|
||||
if (Array.isArray(targetFrontmatterObj[key]) && Array.isArray(mergedFrontmatter[key])) {
|
||||
const combinedArray = [...new Set([...mergedFrontmatter[key], ...targetFrontmatterObj[key]])];
|
||||
mergedFrontmatter[key] = combinedArray;
|
||||
} else {
|
||||
mergedFrontmatter[key] = targetFrontmatterObj[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Convert merged frontmatter back to YAML
|
||||
const mergedFrontmatterYaml = yaml.dump(mergedFrontmatter);
|
||||
|
||||
// Concatenate frontmatter and content
|
||||
const mergedMarkdown = `---\n${mergedFrontmatterYaml}---\n${targetContent}\n\n${templateContent.trim()}\n`;
|
||||
|
||||
return mergedMarkdown;
|
||||
};
|
||||
|
||||
export const editorInsertText = (editor: Editor, text: string)=> {
|
||||
const cursor = editor.getCursor();
|
||||
const line = editor.getLine(cursor.line);
|
||||
const updatedLine = line.slice(0, cursor.ch) + text + line.slice(cursor.ch);
|
||||
editor.setLine(cursor.line, updatedLine);
|
||||
}
|
||||
|
||||
export const foldExcalidrawSection = (view: MarkdownView) => {
|
||||
if(!view || !(view instanceof MarkdownView)) return;
|
||||
const existingFolds = view.currentMode.getFoldInfo();
|
||||
const lineCount = view.editor.lineCount();
|
||||
let i = -1;
|
||||
while(i++<lineCount && view.editor.getLine(i) !== "# Excalidraw Data");
|
||||
|
||||
if(i>-1 && i<lineCount) {
|
||||
const foldPositions = [
|
||||
...(existingFolds?.folds ?? []),
|
||||
...[{from: i, to: lineCount-1}],
|
||||
];
|
||||
view.currentMode.applyFoldInfo({folds: foldPositions, lines:lineCount});
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { WorkspaceWindow } from "obsidian";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { getAllWindowDocuments } from "./ObsidianUtils";
|
||||
import { debug } from "./DebugHelper";
|
||||
|
||||
const STYLE_VARIABLES = ["--background-modifier-cover","--background-primary-alt","--background-secondary","--background-secondary-alt","--background-modifier-border","--text-normal","--text-muted","--text-accent","--text-accent-hover","--text-faint","--text-highlight-bg","--text-highlight-bg-active","--text-selection","--interactive-normal","--interactive-hover","--interactive-accent","--interactive-accent-hover","--scrollbar-bg","--scrollbar-thumb-bg","--scrollbar-active-thumb-bg"];
|
||||
const EXCALIDRAW_CONTAINER_CLASS = "excalidraw__embeddable__outer";
|
||||
@@ -16,6 +17,7 @@ export class StylesManager {
|
||||
constructor(plugin: ExcalidrawPlugin) {
|
||||
this.plugin = plugin;
|
||||
plugin.app.workspace.onLayoutReady(async () => {
|
||||
debug("StylesManager.constructor app.workspace.onLayoutReady");
|
||||
await this.harvestStyles();
|
||||
getAllWindowDocuments(plugin.app).forEach(doc => {
|
||||
this.copyPropertiesToTheme(doc);
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
request,
|
||||
requestUrl,
|
||||
TFile,
|
||||
TFolder,
|
||||
} from "obsidian";
|
||||
import { Random } from "roughjs/bin/math";
|
||||
import { BinaryFileData, DataURL} from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
@@ -17,13 +18,16 @@ import {
|
||||
exportToBlob,
|
||||
IMAGE_TYPES,
|
||||
FRONTMATTER_KEYS,
|
||||
EXCALIDRAW_PLUGIN,
|
||||
getCommonBoundingBox,
|
||||
DEVICE,
|
||||
getContainerElement,
|
||||
} from "../constants/constants";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { ExcalidrawElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ExcalidrawElement, ExcalidrawTextElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ExportSettings } from "../ExcalidrawView";
|
||||
import { getDataURLFromURL, getIMGFilename, getMimeType, getURLImageExtension } from "./FileUtils";
|
||||
import { generateEmbeddableLink } from "./CustomEmbeddableUtils";
|
||||
import ExcalidrawScene from "src/svgToExcalidraw/elements/ExcalidrawScene";
|
||||
import { FILENAMEPARTS } from "./UtilTypes";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import { cleanBlockRef, cleanSectionHeading, getFileCSSClasses } from "./ObsidianUtils";
|
||||
@@ -153,6 +157,10 @@ const rotate = (
|
||||
export const rotatedDimensions = (
|
||||
element: ExcalidrawElement,
|
||||
): [number, number, number, number] => {
|
||||
const bb = getCommonBoundingBox([element]);
|
||||
return [bb.minX, bb.minY, bb.maxX - bb.minX, bb.maxY - bb.minY];
|
||||
|
||||
//removed with 2.1.5... will delete later
|
||||
if (element.angle === 0) {
|
||||
return [element.x, element.y, element.width, element.height];
|
||||
}
|
||||
@@ -282,11 +290,11 @@ export const getSVG = async (
|
||||
svg = await cropObject.getCroppedSVG();
|
||||
} else {
|
||||
svg = await exportToSvg({
|
||||
elements,
|
||||
elements: elements.filter((el:ExcalidrawElement)=>el.isDeleted !== true),
|
||||
appState: {
|
||||
exportBackground: exportSettings.withBackground,
|
||||
exportWithDarkMode: exportSettings.withTheme
|
||||
? scene.appState?.theme != "light"
|
||||
? scene.appState?.theme !== "light"
|
||||
: false,
|
||||
...scene.appState,
|
||||
},
|
||||
@@ -334,11 +342,11 @@ export const getPNG = async (
|
||||
}
|
||||
|
||||
return await exportToBlob({
|
||||
elements: scene.elements,
|
||||
elements: scene.elements.filter((el:ExcalidrawElement)=>el.isDeleted !== true),
|
||||
appState: {
|
||||
exportBackground: exportSettings.withBackground,
|
||||
exportWithDarkMode: exportSettings.withTheme
|
||||
? scene.appState?.theme != "light"
|
||||
? scene.appState?.theme !== "light"
|
||||
: false,
|
||||
...scene.appState,
|
||||
},
|
||||
@@ -385,13 +393,13 @@ export const embedFontsInSVG = (
|
||||
): SVGSVGElement => {
|
||||
//replace font references with base64 fonts)
|
||||
const includesVirgil = !localOnly &&
|
||||
svg.querySelector("text[font-family^='Virgil']") != null;
|
||||
svg.querySelector("text[font-family^='Virgil']") !== null;
|
||||
const includesCascadia = !localOnly &&
|
||||
svg.querySelector("text[font-family^='Cascadia']") != null;
|
||||
svg.querySelector("text[font-family^='Cascadia']") !== null;
|
||||
const includesAssistant = !localOnly &&
|
||||
svg.querySelector("text[font-family^='Assistant']") != null;
|
||||
svg.querySelector("text[font-family^='Assistant']") !== null;
|
||||
const includesLocalFont =
|
||||
svg.querySelector("text[font-family^='LocalFont']") != null;
|
||||
svg.querySelector("text[font-family^='LocalFont']") !== null;
|
||||
const defs = svg.querySelector("defs");
|
||||
if (defs && (includesCascadia || includesVirgil || includesLocalFont || includesAssistant)) {
|
||||
let style = defs.querySelector("style");
|
||||
@@ -433,13 +441,21 @@ export const addAppendUpdateCustomData = (el: Mutable<ExcalidrawElement>, newDat
|
||||
|
||||
export const scaleLoadedImage = (
|
||||
scene: any,
|
||||
files: any,
|
||||
files: any
|
||||
): { dirty: boolean; scene: any } => {
|
||||
let dirty = false;
|
||||
if (!files || !scene) {
|
||||
return { dirty, scene };
|
||||
}
|
||||
for (const f of files) {
|
||||
|
||||
for (const f of files.filter((f:any)=>{
|
||||
if(!Boolean(EXCALIDRAW_PLUGIN)) return true; //this should never happen
|
||||
const ef = EXCALIDRAW_PLUGIN.filesMaster.get(f.id);
|
||||
if(!ef) return true; //mermaid SVG or equation
|
||||
const file = EXCALIDRAW_PLUGIN.app.vault.getAbstractFileByPath(ef.path.replace(/#.*$/,"").replace(/\|.*$/,""));
|
||||
if(!file || (file instanceof TFolder)) return false;
|
||||
return (file as TFile).extension==="md" || EXCALIDRAW_PLUGIN.isExcalidrawFile(file as TFile)
|
||||
})) {
|
||||
const [w_image, h_image] = [f.size.width, f.size.height];
|
||||
const imageAspectRatio = f.size.width / f.size.height;
|
||||
scene.elements
|
||||
@@ -452,7 +468,7 @@ export const scaleLoadedImage = (
|
||||
}
|
||||
if(f.shouldScale) {
|
||||
const elementAspectRatio = w_old / h_old;
|
||||
if (imageAspectRatio != elementAspectRatio) {
|
||||
if (imageAspectRatio !== elementAspectRatio) {
|
||||
dirty = true;
|
||||
const h_new = Math.sqrt((w_old * h_old * h_image) / w_image);
|
||||
const w_new = Math.sqrt((w_old * h_old * w_image) / h_image);
|
||||
@@ -491,7 +507,7 @@ export const setDocLeftHandedMode = (isLeftHanded: boolean, ownerDocument:Docume
|
||||
export const setLeftHandedMode = (isLeftHanded: boolean) => {
|
||||
const visitedDocs = new Set<Document>();
|
||||
app.workspace.iterateAllLeaves((leaf) => {
|
||||
const ownerDocument = app.isMobile?document:leaf.view.containerEl.ownerDocument;
|
||||
const ownerDocument = DEVICE.isMobile?document:leaf.view.containerEl.ownerDocument;
|
||||
if(!ownerDocument) return;
|
||||
if(visitedDocs.has(ownerDocument)) return;
|
||||
visitedDocs.add(ownerDocument);
|
||||
@@ -528,7 +544,7 @@ export const getLinkParts = (fname: string, file?: TFile): LinkParts => {
|
||||
};
|
||||
|
||||
export const compress = (data: string): string => {
|
||||
return LZString.compressToBase64(data).replace(/(.{64})/g, "$1\n\n");
|
||||
return LZString.compressToBase64(data).replace(/(.{256})/g, "$1\n\n");
|
||||
};
|
||||
|
||||
export const decompress = (data: string): string => {
|
||||
@@ -543,7 +559,8 @@ export const isMaskFile = (
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["mask"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["mask"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["mask"].name] !== "undefined")
|
||||
) {
|
||||
return Boolean(fileCache.frontmatter[FRONTMATTER_KEYS["mask"].name]);
|
||||
}
|
||||
@@ -559,7 +576,8 @@ export const hasExportTheme = (
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-dark"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-dark"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["export-dark"].name] !== "undefined")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -576,7 +594,8 @@ export const getExportTheme = (
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-dark"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-dark"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["export-dark"].name] !== "undefined")
|
||||
) {
|
||||
return fileCache.frontmatter[FRONTMATTER_KEYS["export-dark"].name]
|
||||
? "dark"
|
||||
@@ -586,6 +605,23 @@ export const getExportTheme = (
|
||||
return plugin.settings.exportWithTheme ? theme : "light";
|
||||
};
|
||||
|
||||
export const shouldEmbedScene = (
|
||||
plugin: ExcalidrawPlugin,
|
||||
file: TFile
|
||||
): boolean => {
|
||||
if (file) {
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-embed-scene"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["export-embed-scene"].name] !== "undefined")
|
||||
) {
|
||||
return fileCache.frontmatter[FRONTMATTER_KEYS["export-embed-scene"].name];
|
||||
}
|
||||
}
|
||||
return plugin.settings.exportEmbedScene;
|
||||
};
|
||||
|
||||
export const hasExportBackground = (
|
||||
plugin: ExcalidrawPlugin,
|
||||
file: TFile,
|
||||
@@ -594,7 +630,8 @@ export const hasExportBackground = (
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-transparent"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-transparent"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["export-transparent"].name] !== "undefined")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -610,7 +647,8 @@ export const getWithBackground = (
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-transparent"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-transparent"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["export-transparent"].name] !== "undefined")
|
||||
) {
|
||||
return !fileCache.frontmatter[FRONTMATTER_KEYS["export-transparent"].name];
|
||||
}
|
||||
@@ -626,7 +664,10 @@ export const getExportPadding = (
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if(!fileCache?.frontmatter) return plugin.settings.exportPaddingSVG;
|
||||
|
||||
if (fileCache.frontmatter[FRONTMATTER_KEYS["export-padding"].name] != null) {
|
||||
if (
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-padding"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["export-padding"].name] !== "undefined")
|
||||
) {
|
||||
const val = parseInt(
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-padding"].name],
|
||||
);
|
||||
@@ -635,8 +676,11 @@ export const getExportPadding = (
|
||||
}
|
||||
}
|
||||
|
||||
//depricated. Retained for backward compatibility
|
||||
if (fileCache.frontmatter[FRONTMATTER_KEYS["export-svgpadding"].name] != null) {
|
||||
//deprecated. Retained for backward compatibility
|
||||
if (
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-svgpadding"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["export-svgpadding"].name] !== "undefined")
|
||||
) {
|
||||
const val = parseInt(
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-svgpadding"].name],
|
||||
);
|
||||
@@ -654,7 +698,8 @@ export const getPNGScale = (plugin: ExcalidrawPlugin, file: TFile): number => {
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-pngscale"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-pngscale"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["export-pngscale"].name] !== "undefined")
|
||||
) {
|
||||
const val = parseFloat(
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-pngscale"].name],
|
||||
@@ -730,28 +775,43 @@ export const sleep = async (ms: number) => new Promise((resolve) => setTimeout(r
|
||||
export const awaitNextAnimationFrame = async () => new Promise(requestAnimationFrame);
|
||||
*/
|
||||
|
||||
export const log = console.log.bind(window.console);
|
||||
export const debug = console.log.bind(window.console);
|
||||
//export const debug = function(){};
|
||||
|
||||
|
||||
export const getContainerElement = (
|
||||
export const _getContainerElement = (
|
||||
element:
|
||||
| (ExcalidrawElement & { containerId: ExcalidrawElement["id"] | null })
|
||||
| null,
|
||||
scene: ExcalidrawScene,
|
||||
scene: any,
|
||||
) => {
|
||||
if (!element) {
|
||||
if (!element || !scene?.elements || element.type !== "text") {
|
||||
return null;
|
||||
}
|
||||
if (element.containerId) {
|
||||
return scene.elements.filter(el=>el.id === element.containerId)[0] ?? null;
|
||||
return getContainerElement(element as ExcalidrawTextElement, arrayToMap(scene.elements))
|
||||
//return scene.elements.find((el:ExcalidrawElement)=>el.id === element.containerId) ?? null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const updateFrontmatterInString = (data:string, keyValuePairs: [string,string][]):string => {
|
||||
if(!data) return data;
|
||||
/**
|
||||
* Transforms array of objects containing `id` attribute,
|
||||
* or array of ids (strings), into a Map, keyd by `id`.
|
||||
*/
|
||||
export const arrayToMap = <T extends { id: string } | string>(
|
||||
items: readonly T[] | Map<string, T>,
|
||||
) => {
|
||||
if (items instanceof Map) {
|
||||
return items;
|
||||
}
|
||||
return items.reduce((acc: Map<string, T>, element) => {
|
||||
acc.set(typeof element === "string" ? element : element.id, element);
|
||||
return acc;
|
||||
}, new Map());
|
||||
};
|
||||
|
||||
export const updateFrontmatterInString = (data:string, keyValuePairs?: [string,string][]):string => {
|
||||
if(!data || !keyValuePairs) return data;
|
||||
for(const kvp of keyValuePairs) {
|
||||
const r = new RegExp(`${kvp[0]}:\\s.*\\n`,"g");
|
||||
data = data.match(r)
|
||||
@@ -834,20 +894,4 @@ export const addIframe = (containerEl: HTMLElement, link:string, startAt?: numbe
|
||||
sandbox: "allow-forms allow-presentation allow-same-origin allow-scripts allow-modals",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms array of objects containing `id` attribute,
|
||||
* or array of ids (strings), into a Map, keyd by `id`.
|
||||
*/
|
||||
export const arrayToMap = <T extends { id: string } | string>(
|
||||
items: readonly T[] | Map<string, T>,
|
||||
) => {
|
||||
if (items instanceof Map) {
|
||||
return items;
|
||||
}
|
||||
return items.reduce((acc: Map<string, T>, element) => {
|
||||
acc.set(typeof element === "string" ? element : element.id, element);
|
||||
return acc;
|
||||
}, new Map());
|
||||
};
|
||||
}
|
||||
38
styles.css
38
styles.css
@@ -485,6 +485,8 @@ hr.excalidraw-setting-hr {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1456
|
||||
.canvas-node:not(.is-editing):has(.excalidraw-canvas-immersive) {
|
||||
::-webkit-scrollbar,
|
||||
::-webkit-scrollbar-horizontal {
|
||||
@@ -497,6 +499,7 @@ hr.excalidraw-setting-hr {
|
||||
border: unset;
|
||||
box-shadow: unset;
|
||||
}
|
||||
*/
|
||||
|
||||
.excalidraw .canvas-node .ex-md-font-hand-drawn {
|
||||
--font-text: "Virgil";
|
||||
@@ -556,4 +559,39 @@ img.excalidraw-cropped-pdfpage,
|
||||
.excalidraw .pdf-toolbar,
|
||||
.excalidraw .pdf-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ex-opacity-30 {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.ex-opacity-15 {
|
||||
opacity: 0.15;
|
||||
}
|
||||
|
||||
.ex-opacity-8 {
|
||||
opacity: 0.08;
|
||||
}
|
||||
|
||||
.ex-opacity-5 {
|
||||
opacity: 0.05;
|
||||
}
|
||||
|
||||
.ex-opacity-0 {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.popover .excalidraw-svg {
|
||||
width: 100%;
|
||||
max-width: inherit;
|
||||
height: 100%;
|
||||
max-height: inherit;
|
||||
}
|
||||
|
||||
root {
|
||||
--excalidraw-caret-color: initial;
|
||||
}
|
||||
|
||||
textarea.excalidraw-wysiwyg, .excalidraw input {
|
||||
caret-color: var(--excalidraw-caret-color);
|
||||
}
|
||||
Reference in New Issue
Block a user