mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
50 Commits
test1.0.4
...
1.0.8-test
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7860db0f0 | ||
|
|
0ec6acbeed | ||
|
|
1da434c550 | ||
|
|
e10ebf94c6 | ||
|
|
833c2588c1 | ||
|
|
4454598786 | ||
|
|
5a5eb3964b | ||
|
|
bb961c517b | ||
|
|
5be0152583 | ||
|
|
e2bae8e80d | ||
|
|
4e7fcf4360 | ||
|
|
21374f8eb6 | ||
|
|
bfd3faa79d | ||
|
|
cd0d7f192d | ||
|
|
f0ef04ed3e | ||
|
|
8760f72a13 | ||
|
|
09602e142c | ||
|
|
c7500e9ee7 | ||
|
|
92d3363b5b | ||
|
|
30682e1b40 | ||
|
|
d89431bbde | ||
|
|
1c707db3a7 | ||
|
|
a56fda222d | ||
|
|
9fcbe5b7d7 | ||
|
|
3c6dbcc8bb | ||
|
|
25e2f3d8bb | ||
|
|
1c35e86118 | ||
|
|
61b716d8f6 | ||
|
|
2a0404fe18 | ||
|
|
4f4a80b317 | ||
|
|
0259dc579f | ||
|
|
fe84c607a6 | ||
|
|
b8178ac07c | ||
|
|
a65c6afed2 | ||
|
|
6e207350d6 | ||
|
|
d64c00f2dd | ||
|
|
25a998fc01 | ||
|
|
a212136323 | ||
|
|
e1a92695d5 | ||
|
|
370e35182b | ||
|
|
634bbc2165 | ||
|
|
a2dd13049e | ||
|
|
a64c6e5335 | ||
|
|
d57a28c36b | ||
|
|
2d32b4b71a | ||
|
|
5be455d368 | ||
|
|
c4acf24bca | ||
|
|
a13c8e0127 | ||
|
|
b048dd0ee7 | ||
|
|
f6a832b2bc |
3
.babelrc
Normal file
3
.babelrc
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"presets": ["@babel/preset-env", "@babel/preset-react"]
|
||||
}
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -8,4 +8,7 @@ package-lock.json
|
||||
|
||||
# build
|
||||
main.js
|
||||
*.js.map
|
||||
*.js.map
|
||||
stats.html
|
||||
hot-reload.bat
|
||||
data.json
|
||||
|
||||
301
AutomateHowTo.md
Normal file
301
AutomateHowTo.md
Normal file
@@ -0,0 +1,301 @@
|
||||
# Excalidraw Automate How To
|
||||
|
||||
Excalidraw Automate allows you to create Excalidraw drawings using the [Templater](https://github.com/SilentVoid13/Templater) plugin.
|
||||
|
||||
With a little work, using Excalidraw Automate you can generate simple mindmaps, fill out SVG forms, create customized charts, etc. based on documents in your vault.
|
||||
|
||||
You can access Excalidraw Automate via the ExcalidrawAutomate object. I recommend staring your Automate scripts with the following code.
|
||||
|
||||
```javascript
|
||||
const ea = ExcalidrawAutomate;
|
||||
ea.reset();
|
||||
```
|
||||
|
||||
The first line creates a practical constant so you can avoid writing ExcalidrawAutomate 100x times.
|
||||
|
||||
The second line resets ExcalidrawAutomate to defaults. This is important as you will not know which template you executed before, thus you won't know what state you left Excalidraw in.
|
||||
|
||||
## Basic logic of using Excalidraw Automate
|
||||
1. Set the styling of the elements you want to draw
|
||||
2. Add elements. As you add elements, each new element is added one layer above the previous, thus in case of overlapping objects the later one will be on the top of the prior one.
|
||||
3. Call create to instantiate the drawing
|
||||
|
||||
You can change styling between adding different elements. My logic for separating element styling and creation is based on the assumption that you will probably set a stroke color, stroke style, stroke roughness, etc. and draw most of your elements using this. There would be no point in setting all these parameters each time you add an element.
|
||||
|
||||
### Before we dive deeper, here's a simple example script
|
||||
```javascript
|
||||
<%*
|
||||
const ea = ExcalidrawAutomate;
|
||||
ea.reset();
|
||||
ea.addRect(-150,-50,450,300);
|
||||
await ea.addText(-100,70,"Left to right");
|
||||
ea.addArrow([[-100,100],[100,100]]);
|
||||
|
||||
ea.style.strokeColor = "red";
|
||||
await ea.addText(100,-30,"top to bottom",200,null,"center");
|
||||
ea.addArrow([[200,0],[200,200]]);
|
||||
await ea.create();
|
||||
%>
|
||||
```
|
||||
The script will generate the following drawing:
|
||||
![[./images/FristDemo.png]]
|
||||
|
||||
## Attributes and functions at a glance
|
||||
Here's the interface implemented by ExcalidrawAutomate:
|
||||
|
||||
```typescript
|
||||
ExcalidrawAutomate: {
|
||||
style: {
|
||||
strokeColor: string;
|
||||
backgroundColor: string;
|
||||
angle: number;
|
||||
fillStyle: FillStyle;
|
||||
strokeWidth: number;
|
||||
storkeStyle: StrokeStyle;
|
||||
roughness: number;
|
||||
opacity: number;
|
||||
strokeSharpness: StrokeSharpness;
|
||||
fontFamily: FontFamily;
|
||||
fontSize: number;
|
||||
textAlign: string;
|
||||
verticalAlign: string;
|
||||
startArrowHead: string;
|
||||
endArrowHead: string;
|
||||
}
|
||||
canvas: {theme: string, viewBackgroundColor: string};
|
||||
setFillStyle: Function;
|
||||
setStrokeStyle: Function;
|
||||
setStrokeSharpness: Function;
|
||||
setFontFamily: Function;
|
||||
setTheme: Function;
|
||||
create: Function;
|
||||
addRect: Function;
|
||||
addDiamond: Function;
|
||||
addEllipse: Function;
|
||||
addText: Function;
|
||||
addLine: Function;
|
||||
addArrow: Function;
|
||||
connectObjects: Function;
|
||||
clear: Function;
|
||||
reset: Function;
|
||||
};
|
||||
```
|
||||
|
||||
## Element Style
|
||||
As you will notice, some styles have setter functions. This is to help you navigate the allowed values for the property. You do not need to use the setter function however, you can use set the value directly as well.
|
||||
|
||||
### strokeColor
|
||||
String. The color of the line.
|
||||
|
||||
Allowed values are [HTML color names](https://www.w3schools.com/colors/colors_names.asp) or hexadecimal RGB strings e.g. `#FF0000` for red.
|
||||
|
||||
### backgroundColor
|
||||
String. This is the fill color of an object.
|
||||
|
||||
Allowed values are [HTML color names](https://www.w3schools.com/colors/colors_names.asp), hexadecimal RGB strings e.g. `#FF0000` for red, or `transparent`.
|
||||
|
||||
### angle
|
||||
Number. Rotation in radian. 90° == `Math.PI/2`.
|
||||
|
||||
### fillStyle, setFillStyle()
|
||||
```typescript
|
||||
type FillStyle = "hachure" | "cross-hatch" | "solid";
|
||||
setFillStyle (val:number);
|
||||
```
|
||||
fillStyle is a string.
|
||||
|
||||
`setFillStyle()` accepts a number:
|
||||
- 0: "hachure"
|
||||
- 1: "cross-hatch"
|
||||
- any other number: "solid"
|
||||
|
||||
### strokeWidth
|
||||
Number, sets the width of the stroke.
|
||||
|
||||
### strokeStyle, setStrokeStyle()
|
||||
```typescript
|
||||
type StrokeStyle = "solid" | "dashed" | "dotted";
|
||||
setStrokeStyle (val:number);
|
||||
```
|
||||
strokeStyle is a string.
|
||||
|
||||
`setStrokeStyle()` accepts a number:
|
||||
- 0: "solid"
|
||||
- 1: "dashed"
|
||||
- any other number: "dotted"
|
||||
|
||||
### roughness
|
||||
Number. Called sloppiness in Excalidraw. Three values are accepted:
|
||||
- 0: Architect
|
||||
- 1: Artist
|
||||
- 2: Cartoonist
|
||||
|
||||
### opacity
|
||||
Number between 0 and 100. The opacity of an object, both stroke and fill.
|
||||
|
||||
### strokeSharpness, setStrokeSharpness()
|
||||
```typescript
|
||||
type StrokeSharpness = "round" | "sharp";
|
||||
setStrokeSharpness(val:nmuber);
|
||||
```
|
||||
strokeSharpness is a string.
|
||||
|
||||
"round" lines are curvey, "sharp" lines break at the turning point.
|
||||
|
||||
`setStrokeSharpness()` accepts a number:
|
||||
- 0: "round"
|
||||
- any other number: "sharp"
|
||||
|
||||
### fontFamily, setFontFamily()
|
||||
Number. Valid values are 1,2 and 3.
|
||||
|
||||
`setFontFamily()` will also accept a number and return the name of the font.
|
||||
- 1: "Virgil, Segoe UI Emoji"
|
||||
- 2: "Helvetica, Segoe UI Emoji"
|
||||
- 3: "Cascadia, Segoe UI Emoji"
|
||||
|
||||
### fontSize
|
||||
Number. Default value is 20 px
|
||||
|
||||
### textAlign
|
||||
String. Alignment of the text horizontally. Valid values are "left", "center", "right".
|
||||
|
||||
This is relevant when setting a fix width using the `addText()` function.
|
||||
|
||||
### verticalAlign
|
||||
String. Alignment of the text vertically. Valid values are "top" and "middle".
|
||||
|
||||
This is relevant when setting a fix height using the `addText()` function.
|
||||
|
||||
### startArroHead, 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.
|
||||
|
||||
## canvas
|
||||
Sets the properties of the canvas.
|
||||
|
||||
### theme, setTheme()
|
||||
String. Valid values are "light" and "dark".
|
||||
|
||||
`setTheme()` accepts a number:
|
||||
- 0: "light"
|
||||
- any other number: "dark"
|
||||
|
||||
### viewBackgroundColor
|
||||
String. This is the fill color of an object.
|
||||
|
||||
Allowed values are [HTML color names](https://www.w3schools.com/colors/colors_names.asp), hexadecimal RGB strings e.g. `#FF0000` for red, or `transparent`.
|
||||
|
||||
## Adding objects
|
||||
These functions will add objects to your drawing. The canvas is infinite, and it accepts negative and positive X and Y values. The top left corner x values increase left to right, y values increase top to bottom.
|
||||
![[./images/coordinates.png]]
|
||||
|
||||
### addRect(), addDiamond, addEllipse
|
||||
```typescript
|
||||
addRect(topX:number, topY:number, width:number, height:number):string
|
||||
addDiamond(topX:number, topY:number, width:number, height:number):string
|
||||
addEllipse(topX:number, topY:number, width:number, height:number):string
|
||||
```
|
||||
Returns the `id` of the object. The `id` is required when connecting objects with lines. See later.
|
||||
|
||||
### addText
|
||||
```typescript
|
||||
async addText(topX:number, topY:number, text:string, width?:number, height?:number,textAlign?: string, verticalAlign?:string):Promise<string>
|
||||
```
|
||||
|
||||
Adds text to the drawing. If `width` and `height` are not specified, the function will calculate the width and height based on the fontFamily, the fontSize and the text provided.
|
||||
|
||||
In case you want to position a text in the center compared to other elements on the drawing, you can provide a fixed height and width, and you can also specify `textAlign` and `verticalAlign` as described above.
|
||||
|
||||
Returns the `id` of the object. The `id` is required when connecting objects with lines. See later.
|
||||
|
||||
This is an asynchronous function. It must be called with an `await` from the Templater script, otherwise the text will not appear. See code example above.
|
||||
|
||||
### addLine()
|
||||
```typescript
|
||||
addLine(points: [[x:number,y:number]]):void
|
||||
```
|
||||
Adds a line following the points provided. Must include at least two points `points.length >= 2`. If more than 2 points are provided the interim points will be added as breakpoints. The line will break with angles if `strokeSharpness` is set to "sharp" and will be curvey if it is set to "round".
|
||||
|
||||
### addArrow()
|
||||
```typescript
|
||||
addArrow(points: [[x:number,y:number]],startArrowHead?:string,endArrowHead?:string,startBinding?:string,endBinding?:string):void
|
||||
```
|
||||
|
||||
Adds an arrow following the points provided. Must include at least two points `points.length >= 2`. If more than 2 points are provided the interim points will be added as breakpoints. The line will break with angles if `strokeSharpness` is set to "sharp" and will be curvey if it is set to "round".
|
||||
|
||||
`startArrowHead` and `endArrowHead` specify the type of arrow head to use, as described above. Valid values are "none", "arrow", "dot", and "bar".
|
||||
|
||||
`startBinding` and `endBinding` are the object id's of connected objects. Do not use directly with this function. Use `connectObjects` instead.
|
||||
|
||||
### connectObjects()
|
||||
```typescript
|
||||
declare type ConnectionPoint = "top"|"bottom"|"left"|"right";
|
||||
|
||||
connectObjects(objectA: string, connectionA: ConnectionPoint, objectB: string, connectionB: ConnectionPoint, numberOfPoints: number = 1,startArrowHead?:string,endArrowHead?:string):void
|
||||
```
|
||||
Connects two objects with an arrow.
|
||||
|
||||
`objectA` and `objectB` are strings. These are the ids of the objects to connect. IDs are returned by addRect(), addDiamond(), addEllipse() and addText() when creating these objects.
|
||||
|
||||
`connectionA` and `connectionB` specify where to connect on the object. Valid values are: "top", "bottom", "left", and "right".
|
||||
|
||||
`numberOfPoints` set the number of interim break points for the line. Default value is one, meaning there will be 1 breakpoint between the start and the end points of the arrow. When moving objects on the drawing, these breakpoints will influence how the line is rerouted by Excalidraw.
|
||||
|
||||
`startArrowHead` and `endArrowHead` function as described for `addArrow()` above.
|
||||
|
||||
## Utility functions
|
||||
### clear()
|
||||
`clear()` will clear objects from cache, but will retain element style settings.
|
||||
|
||||
### reset()
|
||||
`reset()` will first call `clear()` and then reset element style to defaults.
|
||||
|
||||
### create()
|
||||
```typescript
|
||||
async create(filename?: string, foldername?:string, templatePath?:string, onNewPane: boolean = false)
|
||||
```
|
||||
Creates the drawing and opens it.
|
||||
|
||||
`filename` is the filename without extension of the drawing to be created. If `null`, then Excalidraw will generate a filename.
|
||||
|
||||
`foldername` is the folder where the file should be created. If `null` then the default folder for new drawings will be used according to Excalidraw settings.
|
||||
|
||||
`templatePath` the filename including full path and extension for a template file to use. This template file will be added as the base layer, all additional objects added via ExcalidrawAutomate will appear on top of elements in the template. If `null` then no template will be used, i.e. an empty white drawing will be the base for adding objects.
|
||||
|
||||
`onNewPane` defines where the new drawing should be created. `false` will open the drawing on the current active leaf. `true` will open the drawing by vertically splitting the current leaf.
|
||||
|
||||
## Examples
|
||||
### Connect objects
|
||||
```javascript
|
||||
<%*
|
||||
const ea = ExcalidrawAutomate;
|
||||
ea.reset();
|
||||
await ea.addText(-130,-100,"Connecting two objects");
|
||||
const a = ea.addRect(-100,-100,100,100);
|
||||
const b = ea.addEllipse(200,200,100,100);
|
||||
ea.connectObjects(a,"bottom",b,"left",2); //see how the line breaks differently when moving objects around
|
||||
ea.style.strokeColor = "red";
|
||||
ea.connectObjects(a,"right",b,"top",1);
|
||||
await ea.create();
|
||||
%>
|
||||
```
|
||||
### Using a template
|
||||
This example is similar to the first one, but rotated 90°, and using a template, plus specifying a filename and folder to save the drawing, and opening the new drawing in a new pane.
|
||||
```javascript
|
||||
<%*
|
||||
const ea = ExcalidrawAutomate;
|
||||
ea.reset();
|
||||
ea.style.angle = Math.PI/2;
|
||||
ea.style.strokeWidth = 3.5;
|
||||
ea.addRect(-150,-50,450,300);
|
||||
await ea.addText(-100,70,"Left to right");
|
||||
ea.addArrow([[-100,100],[100,100]]);
|
||||
|
||||
ea.style.strokeColor = "red";
|
||||
await ea.addText(100,-30,"top to bottom",200,null,"center");
|
||||
ea.addArrow([[200,0],[200,200]]);
|
||||
await ea.create("My Drawing","myfolder/fordemo/","Excalidraw/Template2.excalidraw",true);
|
||||
%>
|
||||
```
|
||||
115
README.md
115
README.md
@@ -1,34 +1,103 @@
|
||||
## Obsidian Excalidraw Plugin
|
||||
# Obsidian Excalidraw Plugin
|
||||
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 and you can transclude drawings into your documents. For a showcase of Excalidraw features, please read my blog post [here](https://www.zsolt.blog/2021/03/showcasing-excalidraw.html).
|
||||
|
||||

|
||||
**See details of the 1.0.6 and 1.0.7 release including a short video, further below**
|
||||
|
||||
### Key features
|
||||
- The plugin adds 3 commands to the command palette. 1) To create a new drawing. 2) To find and edit existing drawings in your vault, and 3) to transclude a drawing into a document.
|
||||
- You can also use the file explorer in your vault to open Excalidraw files.
|
||||
- You can set up a default folder for saving new drawings in Settings.
|
||||
- You can set up a Template by creating a drawing, customizing it the way you like it, and specifying the file as the template in settings.
|
||||
- The plugin saves drawings to your vault as a file with the .excalidraw file-extension.
|
||||
- You can set the size of embedded image using the [[image.excalidraw|100]] or [[image.excalidraw|100x100]] format.
|
||||

|
||||
|
||||
### How to?
|
||||
- Add a library: Click [browse libraries](https://libraries.excalidraw.com/?target=_excalidraw&sort=default) in Excalidraw. Download the preferred library and close browser tab. Click Load library in Excalidraw-Obsidian to load your locally saved library.
|
||||
## Key features
|
||||
- The plugin adds the following actions to the **command palette**:
|
||||
- To create a new drawing
|
||||
- To find and edit existing drawings in your vault,
|
||||
- To embed (transclude) a drawing into a document, and
|
||||
- To export a drawing as PNG or SVG.
|
||||
- You can also use the **file explorer** in your vault to open Excalidraw files.
|
||||
- Use the **ribbon button** to create a new drawing, do CTRL+Click to open on a new page.
|
||||
- Open settings to set up a **default folder** for new drawings.
|
||||
- Set up a **Template** by creating a drawing, customizing it the way you like it, and specifying the file as the template in settings.
|
||||
- The plugin saves drawings to your vault as a file with the *.excalidraw* file extension.
|
||||
- You can customize the **size and position of the embedded image** using the `[[image.excalidraw|100]]`, `[[image.excalidraw|100x100]]`, `[[image.excalidraw|100|left]]`, `[[image.excalidraw|right-wrap]]`, formatting options. `[[<filename.excalidraw>|<width>x<height>|<alignment>]]`. You can add your custom alignment via css. Any text that appears in `<alignment>` will be added as style to the SVG element and the wrapper DIV element. Check below and styles.css for more insight.
|
||||
- You can setup Excalidraw to **automatically export SVG and/or PNG** files for your drawings, and to keep those in sync with your drawing.
|
||||
|
||||
### Known issues
|
||||
- When inserting images from the library, sometimes these images appear out of view. You’ll need to select “scroll to view” at the bottom of the screen. This is a bug in Excalidraw. The Excalidraw development team has successfully reproduced the bug and promised to resolve it in the next update of Excalidraw.
|
||||
- On iPad: As you draw left to right it opens left sidebar. Draw right to left, opens right sidebar. Draw down, opens commands palette. So seems open is emulating the gestures, even when drawing towards the center. I have raised the problem with the Obsidian.md team, and Licat promised to resolve this issue in the next Obsidian release.
|
||||
## How to?
|
||||
Part 1: Intro to Obsidian-Excalidraw - Start a new drawing (3:12)
|
||||
|
||||
### Excalidraw in Obsidian
|
||||
https://user-images.githubusercontent.com/14358394/115386872-3fc8d180-a1da-11eb-9366-16d0e064932a.mp4
|
||||
[](https://youtu.be/i-hIfY-Ecjg)
|
||||
|
||||
### Contributing
|
||||
Feel free to contribute.
|
||||
Part 2: Intro to Obsidian-Excalidraw - Basic features (6:06)
|
||||
|
||||
By clicking [here](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues) you can create an issue to report a bug, suggest an improvement for this plugin, ask a question, etc.
|
||||
[](https://youtu.be/-dk7pvdl-H0)
|
||||
|
||||
### Support
|
||||
If you want to support me and my work, you can donate me a little something.
|
||||
Part 3: Intro to Obsidian-Excalidraw - Advanced features (3:26)
|
||||
|
||||
[<img src="https://user-images.githubusercontent.com/14358394/115450238-f39e8100-a21b-11eb-89d0-fa4b82cdbce8.png" width="200">](https://ko-fi.com/zsolt)
|
||||
[](https://youtu.be/2cKlEwo8WU0)
|
||||
|
||||
[https://ko-fi/zsolt](https://ko-fi.com/zsolt)
|
||||
Part 4: Intro to Obsidian-Excalidraw - Setting up a template (1:45)
|
||||
|
||||
[](https://youtu.be/oNPYZEpmuJ8)
|
||||
|
||||
Part 5: Intro to Obsidian-Excalidraw - Stencil Library (3:16)
|
||||
|
||||
[](https://youtu.be/rLx-9FvlzgI)
|
||||
|
||||
Part 6: Intro to Obsidian-Excalidraw: Embedding drawings (2:08)
|
||||
|
||||
[](https://youtu.be/JQeJ-Hh-xAI)
|
||||
|
||||
## 1.0.6 and 1.0.7 update
|
||||
[](https://youtu.be/ipZPbcP2B0M)
|
||||
|
||||
### SVG styling when embedding using a code block
|
||||
- 1.0.7 adds further flexibility to styling
|
||||
- new formatting option for the code block embedding
|
||||
- Valid values: `left`, `right`, `left-wrap`, `right-wrap`... but anything after the last `|` character will be added to the class of the SVG element and the wrapper DIV element.
|
||||
Here is the corresponding CSS:
|
||||
```
|
||||
svg.excalidraw-svg-right-wrap {
|
||||
float: right;
|
||||
margin: 0px 0px 20px 20px;
|
||||
}
|
||||
|
||||
svg.excalidraw-svg-left-wrap {
|
||||
float: left;
|
||||
margin: 0px 35px 20px 0px;
|
||||
}
|
||||
|
||||
div.excalidraw-svg-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
div.excalidraw-svg-left {
|
||||
text-align: left;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Known issues
|
||||
- On mobile (iOS and Android): As you draw left to right it opens left sidebar. Draw right to left, opens right sidebar. Draw down, opens commands palette. So seems open is emulating the gestures, even when drawing towards the center. I understand that the issue will be resolved in the next release of Obsidian mobile.
|
||||
- I have seen two cases when adding a stencil library did not work. In both cases, the end solution was a reinstall of Obsidian. The root cause is not clear, but maybe because of the incremental updates of Obsidian from an early version.
|
||||
- Sync does not support .excalidraw files. This issue will be addressed in a later release of Obsidian sync. Until then, here are two hacks you can play with:
|
||||
- You have the option to use OneDrive, Google Drive, iCloud, DropBox, etc. to sync your vault between devices.
|
||||
- You can also use Obsidian Sync in conjunction with "Obsidian Git" (find it in community plugins). Be sure to set up git to ignore all files except for .excalidraw by adding the following to `.gitignore`. Obsidian Git does not work on mobile, but on Android you can use an app like MGIT to sync your `.excalidraw` files from/to the git repository.
|
||||
```
|
||||
#ignore all kind of files
|
||||
*.*
|
||||
#except excalidraw files
|
||||
!*.excalidraw
|
||||
```
|
||||
## Tips and tricks
|
||||
- If you want to sketch in fullscreen, I recommend installing the [Fullscreen Focus Mode](https://github.com/razumihin/obsidian-fullscreen-plugin) plugin.
|
||||
|
||||
## Feedback, questions, ideas, problems
|
||||
Join the conversation about the Excalidraw plugin on [forum.obsidian.md](https://forum.obsidian.md/t/excalidraw-full-featured-sketching-plugin-in-obsidian)
|
||||
|
||||
Please head over to [GitHub](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues) to report a bug or request an enhancement.
|
||||
|
||||
## Say Thank You
|
||||
If you are enjoying Excalidraw then please support my work and enthusiasm by buying me a coffee on [https://ko-fi/zsolt](https://ko-fi.com/zsolt).
|
||||
|
||||
Please also help spread the word by sharing about the Obsidian Excalidraw Plugin on Twitter, Reddit, or any other social media platform you regularly use.
|
||||
|
||||
You can find me on Twitter [@zsviczian](https://twitter.com/zsviczian), and on my blog [zsolt.blog](https://zsolt.blog).
|
||||
|
||||
[<img style="float:left" src="https://user-images.githubusercontent.com/14358394/115450238-f39e8100-a21b-11eb-89d0-fa4b82cdbce8.png" width="200">](https://ko-fi.com/zsolt)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{"openFile":"Blog/attachements/security through obscurity.excalidraw","settings":{"folder":"excalidraw","templateFilePath":"excalidraw/Template.excalidraw"}}
|
||||
@@ -1 +0,0 @@
|
||||
{"folder":"excalidraw","templateFilePath":"excalidraw/Template.excalidra","width":"400","openFile":"excalidraw/new file.excalidraw","settings":{"folder":"excalidraw","templateFilePath":""}}
|
||||
10
dist/manifest.json
vendored
10
dist/manifest.json
vendored
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "1.0.2",
|
||||
"minAppVersion": "0.11.13",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
"authorUrl": "https://zsolt.blog",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
18
dist/styles.css
vendored
18
dist/styles.css
vendored
@@ -1,18 +0,0 @@
|
||||
.App {
|
||||
font-family: sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.excalidraw-wrapper {
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.context-menu-option__shortcut {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.block-language-excalidraw {
|
||||
text-align:center;
|
||||
}
|
||||
3
esbuild.config.json
Normal file
3
esbuild.config.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"minify": true
|
||||
}
|
||||
BIN
images/FristDemo.png
Normal file
BIN
images/FristDemo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
images/coordinates.png
Normal file
BIN
images/coordinates.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.7",
|
||||
"minAppVersion": "0.11.13",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
"authorUrl": "https://zsolt.blog",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
}
|
||||
|
||||
16
package.json
16
package.json
@@ -11,25 +11,33 @@
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@excalidraw/excalidraw": "0.6.0",
|
||||
"react": "17.0.0",
|
||||
"react-dom": "17.0.0",
|
||||
"@excalidraw/excalidraw": "0.7.0",
|
||||
"aakansha-excalidraw": "0.7.0-draft",
|
||||
"react": "17.0.1",
|
||||
"react-dom": "17.0.1",
|
||||
"react-scripts": "4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.3.3",
|
||||
"@babel/preset-env": "^7.3.1",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@rollup/plugin-babel": "5.3.0",
|
||||
"@rollup/plugin-commonjs": "^15.1.0",
|
||||
"@rollup/plugin-node-resolve": "^9.0.0",
|
||||
"@rollup/plugin-typescript": "^6.0.0",
|
||||
"@types/node": "^14.14.2",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"cross-env": "7.0.3",
|
||||
"nanoid": "3.1.22",
|
||||
"obsidian": "https://github.com/obsidianmd/obsidian-api/tarball/master",
|
||||
"postcss": "^8.2.6",
|
||||
"rollup": "2.45.2",
|
||||
"rollup-plugin-copy": "3.4.0",
|
||||
"rollup-plugin-minify": "1.0.3",
|
||||
"rollup-plugin-postcss": "^4.0.0",
|
||||
"rollup-plugin-visualizer": "^5.4.1",
|
||||
"tslib": "^2.0.3",
|
||||
"typescript": "^4.0.3"
|
||||
"typescript": "^4.0.3",
|
||||
"webpack-bundle-analyzer": "^4.4.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import postcss from 'rollup-plugin-postcss';
|
||||
import copy from 'rollup-plugin-copy';
|
||||
//import uglify from 'rollup-plugin-uglify';
|
||||
//import minify from "rollup-plugin-minify"
|
||||
//import copy from 'rollup-plugin-copy';
|
||||
import { env } from "process";
|
||||
import babel from '@rollup/plugin-babel';
|
||||
import replace from "@rollup/plugin-replace";
|
||||
import visualizer from "rollup-plugin-visualizer";
|
||||
|
||||
const isProd = (process.env.NODE_ENV === "production");
|
||||
|
||||
console.log(process.env.NODE_ENV);
|
||||
console.log("Is production", isProd);
|
||||
|
||||
export default {
|
||||
input: 'src/main.ts',
|
||||
output: {
|
||||
dir: isProd ? './dist' : '.',
|
||||
dir: '.',
|
||||
sourcemap: 'inline',
|
||||
format: 'cjs',
|
||||
exports: 'default'
|
||||
@@ -29,15 +26,10 @@ export default {
|
||||
preventAssignment: true,
|
||||
"process.env.NODE_ENV": JSON.stringify(env.NODE_ENV),
|
||||
}),
|
||||
babel({
|
||||
exclude: "node_modules/**"
|
||||
}),
|
||||
commonjs(),
|
||||
postcss({
|
||||
plugins: []
|
||||
}),
|
||||
copy({
|
||||
targets: [
|
||||
{ src: ['manifest.json', 'styles.css'], dest: './dist' }
|
||||
], flatten: true
|
||||
}),
|
||||
//process.env.NODE_ENV === 'production' && minify(),
|
||||
visualizer(),
|
||||
]
|
||||
};
|
||||
388
src/ExcalidrawTemplate.ts
Normal file
388
src/ExcalidrawTemplate.ts
Normal file
@@ -0,0 +1,388 @@
|
||||
import ExcalidrawPlugin from "./main";
|
||||
import {
|
||||
ExcalidrawElement,
|
||||
FillStyle,
|
||||
StrokeStyle,
|
||||
StrokeSharpness,
|
||||
FontFamily,
|
||||
} from "@excalidraw/excalidraw/types/element/types";
|
||||
import {nanoid} from "nanoid";
|
||||
import {
|
||||
normalizePath,
|
||||
parseFrontMatterAliases,
|
||||
TFile
|
||||
} from "obsidian"
|
||||
|
||||
declare type ConnectionPoint = "top"|"bottom"|"left"|"right";
|
||||
|
||||
export interface ExcalidrawAutomate extends Window {
|
||||
ExcalidrawAutomate: {
|
||||
plugin: ExcalidrawPlugin;
|
||||
elementIds: [];
|
||||
elementsDict: {},
|
||||
style: {
|
||||
strokeColor: string;
|
||||
backgroundColor: string;
|
||||
angle: number;
|
||||
fillStyle: FillStyle;
|
||||
strokeWidth: number;
|
||||
storkeStyle: StrokeStyle;
|
||||
roughness: number;
|
||||
opacity: number;
|
||||
strokeSharpness: StrokeSharpness;
|
||||
fontFamily: FontFamily;
|
||||
fontSize: number;
|
||||
textAlign: string;
|
||||
verticalAlign: string;
|
||||
startArrowHead: string;
|
||||
endArrowHead: string;
|
||||
}
|
||||
canvas: {theme: string, viewBackgroundColor: string};
|
||||
setFillStyle: Function;
|
||||
setStrokeStyle: Function;
|
||||
setStrokeSharpness: Function;
|
||||
setFontFamily: Function;
|
||||
setTheme: Function;
|
||||
create: Function;
|
||||
addRect: Function;
|
||||
addDiamond: Function;
|
||||
addEllipse: Function;
|
||||
addText: Function;
|
||||
addLine: Function;
|
||||
addArrow: Function;
|
||||
connectObjects: Function;
|
||||
clear: Function;
|
||||
reset: Function;
|
||||
};
|
||||
}
|
||||
|
||||
declare let window: ExcalidrawAutomate;
|
||||
|
||||
export function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
|
||||
window.ExcalidrawAutomate = {
|
||||
plugin: plugin,
|
||||
elementIds: [],
|
||||
elementsDict: {},
|
||||
style: {
|
||||
strokeColor: "#000000",
|
||||
backgroundColor: "transparent",
|
||||
angle: 0,
|
||||
fillStyle: "hachure",
|
||||
strokeWidth:1,
|
||||
storkeStyle: "solid",
|
||||
roughness: 1,
|
||||
opacity: 100,
|
||||
strokeSharpness: "sharp",
|
||||
fontFamily: 1,
|
||||
fontSize: 20,
|
||||
textAlign: "left",
|
||||
verticalAlign: "top",
|
||||
startArrowHead: null,
|
||||
endArrowHead: "arrow"
|
||||
},
|
||||
canvas: {theme: "light", viewBackgroundColor: "#FFFFFF"},
|
||||
setFillStyle (val:number) {
|
||||
switch(val) {
|
||||
case 0:
|
||||
this.style.fillStyle = "hachure";
|
||||
return "hachure";
|
||||
case 1:
|
||||
this.style.fillStyle = "cross-hatch";
|
||||
return "cross-hatch";
|
||||
default:
|
||||
this.style.fillStyle = "solid";
|
||||
return "solid";
|
||||
}
|
||||
},
|
||||
setStrokeStyle (val:number) {
|
||||
switch(val) {
|
||||
case 0:
|
||||
this.style.strokeStyle = "solid";
|
||||
return "solid";
|
||||
case 1:
|
||||
this.style.strokeStyle = "dashed";
|
||||
return "dashed";
|
||||
default:
|
||||
this.style.strokeStyle = "dotted";
|
||||
return "dotted";
|
||||
}
|
||||
},
|
||||
setStrokeSharpness (val:number) {
|
||||
switch(val) {
|
||||
case 0:
|
||||
this.style.strokeSharpness = "round";
|
||||
return "round";
|
||||
default:
|
||||
this.style.strokeSharpness = "sharp";
|
||||
return "sharp";
|
||||
}
|
||||
},
|
||||
setFontFamily (val:number) {
|
||||
switch(val) {
|
||||
case 1:
|
||||
this.style.fontFamily = 1;
|
||||
return getFontFamily(1);
|
||||
case 2:
|
||||
this.style.fontFamily = 2;
|
||||
return getFontFamily(2);
|
||||
default:
|
||||
this.style.strokeSharpness = 3;
|
||||
return getFontFamily(3);
|
||||
}
|
||||
},
|
||||
setTheme (val:number) {
|
||||
switch(val) {
|
||||
case 0:
|
||||
this.canvas.theme = "light";
|
||||
return "light";
|
||||
default:
|
||||
this.canvas = "dark";
|
||||
return "dark";
|
||||
}
|
||||
},
|
||||
async create(filename?: string, foldername?:string, templatePath?:string, onNewPane: boolean = false) {
|
||||
let elements = templatePath ? (await getTemplate(templatePath)).elements : [];
|
||||
for (let i=0;i<this.elementIds.length;i++) {
|
||||
elements.push(this.elementsDict[this.elementIds[i]]);
|
||||
}
|
||||
plugin.createDrawing(
|
||||
filename ? filename + '.excalidraw' : this.plugin.getNextDefaultFilename(),
|
||||
onNewPane,
|
||||
foldername ? foldername : this.plugin.settings.folder,
|
||||
(()=>{
|
||||
return JSON.stringify({
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "https://excalidraw.com",
|
||||
"elements": elements,
|
||||
"appState": {
|
||||
"theme": this.canvas.theme,
|
||||
"viewBackgroundColor": this.canvas.viewBackgroundColor
|
||||
}
|
||||
});
|
||||
})());
|
||||
},
|
||||
addRect(topX:number, topY:number, width:number, height:number):string {
|
||||
const id = nanoid();
|
||||
this.elementIds.push(id);
|
||||
this.elementsDict[id] = boxedElement(id,"rectangle",topX,topY,width,height);
|
||||
return id;
|
||||
},
|
||||
addDiamond(topX:number, topY:number, width:number, height:number):string {
|
||||
const id = nanoid();
|
||||
this.elementIds.push(id);
|
||||
this.elementsDict[id] = boxedElement(id,"diamond",topX,topY,width,height);
|
||||
return id;
|
||||
},
|
||||
addEllipse(topX:number, topY:number, width:number, height:number):string {
|
||||
const id = nanoid();
|
||||
this.elementIds.push(id);
|
||||
this.elementsDict[id] = boxedElement(id,"ellipse",topX,topY,width,height);
|
||||
return id;
|
||||
},
|
||||
async addText(topX:number, topY:number, text:string, width?:number, height?:number,textAlign?: string, verticalAlign?:string):Promise<string> {
|
||||
const id = nanoid();
|
||||
const {w, h, baseline} = await measureText(text);
|
||||
this.elementIds.push(id);
|
||||
this.elementsDict[id] = {
|
||||
text: text,
|
||||
fontSize: window.ExcalidrawAutomate.style.fontSize,
|
||||
fontFamily: window.ExcalidrawAutomate.style.fontFamily,
|
||||
textAlign: textAlign ? textAlign : window.ExcalidrawAutomate.style.textAlign,
|
||||
verticalAlign: verticalAlign ? verticalAlign : window.ExcalidrawAutomate.style.verticalAlign,
|
||||
baseline: baseline,
|
||||
... boxedElement(id,"text",topX,topY,width ? width:w, height ? height:h)
|
||||
};
|
||||
return id;
|
||||
},
|
||||
addLine(points: [[x:number,y:number]]):void {
|
||||
const box = getLineBox(points);
|
||||
const id = nanoid();
|
||||
this.elementIds.push(id);
|
||||
this.elementsDict[id] = {
|
||||
points: normalizeLinePoints(points),
|
||||
lastCommittedPoint: null,
|
||||
startBinding: null,
|
||||
endBinding: null,
|
||||
startArrowhead: null,
|
||||
endArrowhead: null,
|
||||
... boxedElement(id,"line",box.x,box.y,box.w,box.h)
|
||||
};
|
||||
},
|
||||
addArrow(points: [[x:number,y:number]],startArrowHead?:string,endArrowHead?:string,startBinding?:string,endBinding?:string):void {
|
||||
const box = getLineBox(points);
|
||||
const id = nanoid();
|
||||
this.elementIds.push(id);
|
||||
this.elementsDict[id] = {
|
||||
points: normalizeLinePoints(points),
|
||||
lastCommittedPoint: null,
|
||||
startBinding: {elementId:startBinding,focus:0.1,gap:4},
|
||||
endBinding: {elementId:endBinding,focus:0.1,gap:4},
|
||||
startArrowhead: startArrowHead ? startArrowHead : this.style.startArrowHead,
|
||||
endArrowhead: endArrowHead ? endArrowHead : this.style.endArrowHead,
|
||||
... boxedElement(id,"arrow",box.x,box.y,box.w,box.h)
|
||||
};
|
||||
if(startBinding) this.elementsDict[startBinding].boundElementIds.push(id);
|
||||
if(endBinding) this.elementsDict[endBinding].boundElementIds.push(id);
|
||||
},
|
||||
connectObjects(objectA: string, connectionA: ConnectionPoint, objectB: string, connectionB: ConnectionPoint, numberOfPoints: number = 1,startArrowHead?:string,endArrowHead?:string):void {
|
||||
if(!(this.elementsDict[objectA] && this.elementsDict[objectB])) {
|
||||
return;
|
||||
}
|
||||
const getSidePoints = (side:string, el:any) => {
|
||||
switch(side) {
|
||||
case "bottom":
|
||||
return [((el.x) + (el.x+el.width))/2, el.y+el.height];
|
||||
case "left":
|
||||
return [el.x, ((el.y) + (el.y+el.height))/2];
|
||||
case "right":
|
||||
return [el.x+el.width, ((el.y) + (el.y+el.height))/2];
|
||||
default: //"top"
|
||||
return [((el.x) + (el.x+el.width))/2, el.y];
|
||||
}
|
||||
}
|
||||
const [aX, aY] = getSidePoints(connectionA,this.elementsDict[objectA]);
|
||||
const [bX, bY] = getSidePoints(connectionB,this.elementsDict[objectB]);
|
||||
const numAP = numberOfPoints+2; //number of break points plus the beginning and the end
|
||||
let points = [];
|
||||
for(let i=0;i<numAP;i++)
|
||||
points.push([aX+i*(bX-aX)/(numAP-1), aY+i*(bY-aY)/(numAP-1)]);
|
||||
this.addArrow(points,startArrowHead,endArrowHead,objectA,objectB);
|
||||
},
|
||||
clear() {
|
||||
this.elementIds = [];
|
||||
this.elementsDict = {};
|
||||
},
|
||||
reset() {
|
||||
this.clear();
|
||||
this.style.strokeColor= "#000000";
|
||||
this.style.backgroundColor= "transparent";
|
||||
this.style.angle= 0;
|
||||
this.style.fillStyle= "hachure";
|
||||
this.style.strokeWidth= 1;
|
||||
this.style.storkeStyle= "solid";
|
||||
this.style.roughness= 1;
|
||||
this.style.opacity= 100;
|
||||
this.style.strokeSharpness= "sharp";
|
||||
this.style.fontFamily= 1;
|
||||
this.style.fontSize= 20;
|
||||
this.style.textAlign= "left";
|
||||
this.style.verticalAlign= "top";
|
||||
this.style.startArrowHead= null;
|
||||
this.style.endArrowHead= "arrow";
|
||||
this.canvas.theme = "light";
|
||||
this.canvas.viewBackgroundColor="#FFFFFF";
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function destroyExcalidrawAutomate() {
|
||||
delete window.ExcalidrawAutomate;
|
||||
}
|
||||
|
||||
function normalizeLinePoints(points:[[x:number,y:number]]) {
|
||||
let p = [];
|
||||
for(let i=0;i<points.length;i++) {
|
||||
p.push([points[i][0]-points[0][0], points[i][1]-points[0][1]]);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
function boxedElement(id:string,eltype:any,x:number,y:number,w:number,h:number) {
|
||||
return {
|
||||
id: id,
|
||||
type: eltype,
|
||||
x: x,
|
||||
y: y,
|
||||
width: w,
|
||||
height: h,
|
||||
angle: window.ExcalidrawAutomate.style.angle,
|
||||
strokeColor: window.ExcalidrawAutomate.style.strokeColor,
|
||||
backgroundColor: window.ExcalidrawAutomate.style.backgroundColor,
|
||||
fillStyle: window.ExcalidrawAutomate.style.fillStyle,
|
||||
strokeWidth: window.ExcalidrawAutomate.style.strokeWidth,
|
||||
storkeStyle: window.ExcalidrawAutomate.style.storkeStyle,
|
||||
roughness: window.ExcalidrawAutomate.style.roughness,
|
||||
opacity: window.ExcalidrawAutomate.style.opacity,
|
||||
strokeSharpness: window.ExcalidrawAutomate.style.strokeSharpness,
|
||||
seed: Math.floor(Math.random() * 100000),
|
||||
version: 1,
|
||||
versionNounce: 1,
|
||||
isDeleted: false,
|
||||
groupIds: [] as any,
|
||||
boundElementIds: [] as any,
|
||||
};
|
||||
}
|
||||
|
||||
function getLineBox(points: [[x:number,y:number]]) {
|
||||
let leftX:number,rightX:number = points[0][0];
|
||||
let topY:number,bottomY:number = points[0][1];
|
||||
for (let i=0;i<points.length;i++) {
|
||||
leftX = (leftX < points[i][0]) ? leftX : points[i][0];
|
||||
topY = (topY < points[i][1]) ? topY : points[i][1];
|
||||
rightX = (rightX > points[i][0]) ? rightX : points[i][0];
|
||||
bottomY = (bottomY > points[i][1]) ? bottomY : points[i][1];
|
||||
}
|
||||
return {
|
||||
x: leftX,
|
||||
y: topY,
|
||||
w: rightX-leftX,
|
||||
h: bottomY-topY
|
||||
};
|
||||
}
|
||||
|
||||
function getFontFamily(id:number) {
|
||||
switch (id) {
|
||||
case 1: return "Virgil, Segoe UI Emoji";
|
||||
case 2: return "Helvetica, Segoe UI Emoji";
|
||||
case 3: return "Cascadia, Segoe UI Emoji";
|
||||
}
|
||||
}
|
||||
|
||||
async function measureText (newText:string) {
|
||||
const line = document.createElement("div");
|
||||
const body = document.body;
|
||||
line.style.position = "absolute";
|
||||
line.style.whiteSpace = "pre";
|
||||
line.style.font = window.ExcalidrawAutomate.style.fontSize.toString()+'px ' +
|
||||
getFontFamily(window.ExcalidrawAutomate.style.fontFamily);
|
||||
await (document as any).fonts.load(line.style.font);
|
||||
body.appendChild(line);
|
||||
line.innerText = newText
|
||||
.split("\n")
|
||||
// replace empty lines with single space because leading/trailing empty
|
||||
// lines would be stripped from computation
|
||||
.map((x) => x || " ")
|
||||
.join("\n");
|
||||
const width = line.offsetWidth;
|
||||
const height = line.offsetHeight;
|
||||
// Now creating 1px sized item that will be aligned to baseline
|
||||
// to calculate baseline shift
|
||||
const span = document.createElement("span");
|
||||
span.style.display = "inline-block";
|
||||
span.style.overflow = "hidden";
|
||||
span.style.width = "1px";
|
||||
span.style.height = "1px";
|
||||
line.appendChild(span);
|
||||
// Baseline is important for positioning text on canvas
|
||||
const baseline = span.offsetTop + span.offsetHeight;
|
||||
document.body.removeChild(line);
|
||||
return {w: width, h: height, baseline: baseline };
|
||||
};
|
||||
|
||||
async function getTemplate(fileWithPath: string):Promise<{elements: any,appState: any}> {
|
||||
const vault = window.ExcalidrawAutomate.plugin.app.vault;
|
||||
const file = vault.getAbstractFileByPath(normalizePath(fileWithPath));
|
||||
if(file && file instanceof TFile) {
|
||||
const data = await vault.read(file);
|
||||
const excalidrawData = JSON.parse(data);
|
||||
return {
|
||||
elements: excalidrawData.elements,
|
||||
appState: excalidrawData.appState,
|
||||
};
|
||||
};
|
||||
return {
|
||||
elements: [],
|
||||
appState: {},
|
||||
}
|
||||
}
|
||||
@@ -1,38 +1,116 @@
|
||||
import { TextFileView, WorkspaceLeaf } from "obsidian";
|
||||
import {
|
||||
TextFileView,
|
||||
WorkspaceLeaf,
|
||||
normalizePath,
|
||||
TFile,
|
||||
Menu,
|
||||
} from "obsidian";
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import Excalidraw, {exportToSvg} from "@excalidraw/excalidraw";
|
||||
import { ExcalidrawElement } from "@excalidraw/excalidraw/types/element/types";
|
||||
import { AppState } from "@excalidraw/excalidraw/types/types";
|
||||
import {VIEW_TYPE_EXCALIDRAW, EXCALIDRAW_FILE_EXTENSION, ICON_NAME} from './constants';
|
||||
import {
|
||||
AppState,
|
||||
LibraryItems
|
||||
} from "@excalidraw/excalidraw/types/types";
|
||||
import {
|
||||
VIEW_TYPE_EXCALIDRAW,
|
||||
EXCALIDRAW_FILE_EXTENSION,
|
||||
ICON_NAME,
|
||||
EXCALIDRAW_LIB_HEADER,
|
||||
VIRGIL_FONT,
|
||||
CASCADIA_FONT,
|
||||
DISK_ICON_NAME,
|
||||
PNG_ICON_NAME,
|
||||
SVG_ICON_NAME
|
||||
} from './constants';
|
||||
import ExcalidrawPlugin from './main';
|
||||
|
||||
export interface ExportSettings {
|
||||
withBackground: boolean,
|
||||
withTheme: boolean
|
||||
}
|
||||
|
||||
export default class ExcalidrawView extends TextFileView {
|
||||
private getScene: any;
|
||||
private excalidrawRef: React.MutableRefObject<any>;
|
||||
private justLoaded: boolean;
|
||||
private plugin: ExcalidrawPlugin;
|
||||
|
||||
constructor(leaf: WorkspaceLeaf) {
|
||||
constructor(leaf: WorkspaceLeaf, plugin: ExcalidrawPlugin) {
|
||||
super(leaf);
|
||||
this.getScene = null;
|
||||
this.excalidrawRef = null;
|
||||
this.plugin = plugin;
|
||||
this.justLoaded = false;
|
||||
}
|
||||
|
||||
async onClose() {
|
||||
this.requestSave();
|
||||
public async saveSVG(data?: string) {
|
||||
if(!data) {
|
||||
if (!this.getScene) return false;
|
||||
data = this.getScene();
|
||||
}
|
||||
const filepath = this.file.path.substring(0,this.file.path.lastIndexOf('.'+EXCALIDRAW_FILE_EXTENSION)) + '.svg';
|
||||
const file = this.app.vault.getAbstractFileByPath(normalizePath(filepath));
|
||||
const exportSettings: ExportSettings = {
|
||||
withBackground: this.plugin.settings.exportWithBackground,
|
||||
withTheme: this.plugin.settings.exportWithTheme
|
||||
}
|
||||
const svg = ExcalidrawView.getSVG(data,exportSettings);
|
||||
if(!svg) return;
|
||||
//replace font references with base64 fonts
|
||||
const includesVirgil = svg.querySelector("text[font-family^='Virgil']") != null;
|
||||
const includesCascadia = svg.querySelector("text[font-family^='Cascadia']") != null;
|
||||
const defs = svg.querySelector("defs");
|
||||
if (defs && (includesCascadia || includesVirgil)) {
|
||||
defs.innerHTML = "<style>" + (includesVirgil ? VIRGIL_FONT : "") + (includesCascadia ? CASCADIA_FONT : "")+"</style>";
|
||||
}
|
||||
const svgString = svg.outerHTML;
|
||||
if(file && file instanceof TFile) await this.app.vault.modify(file,svgString);
|
||||
else await this.app.vault.create(filepath,svgString);
|
||||
}
|
||||
|
||||
// clear the view content
|
||||
clear() {
|
||||
ReactDOM.unmountComponentAtNode(this.contentEl);
|
||||
this.getScene = null;
|
||||
|
||||
public async savePNG(data?: string) {
|
||||
if(!data) {
|
||||
if (!this.getScene) return false;
|
||||
data = this.getScene();
|
||||
}
|
||||
const filepath = this.file.path.substring(0,this.file.path.lastIndexOf('.'+EXCALIDRAW_FILE_EXTENSION)) + '.png';
|
||||
const file = this.app.vault.getAbstractFileByPath(normalizePath(filepath));
|
||||
const exportSettings: ExportSettings = {
|
||||
withBackground: this.plugin.settings.exportWithBackground,
|
||||
withTheme: this.plugin.settings.exportWithTheme
|
||||
}
|
||||
const png = await ExcalidrawView.getPNG(data,exportSettings);
|
||||
if(!png) return;
|
||||
if(file && file instanceof TFile) await this.app.vault.modifyBinary(file,await png.arrayBuffer());
|
||||
else await this.app.vault.createBinary(filepath,await png.arrayBuffer());
|
||||
}
|
||||
|
||||
// get the new file content
|
||||
getViewData () {
|
||||
if(this.getScene) return this.getScene();
|
||||
else return '';
|
||||
if(this.getScene) {
|
||||
const scene = this.getScene();
|
||||
if(this.plugin.settings.autoexportSVG) this.saveSVG(scene);
|
||||
if(this.plugin.settings.autoexportPNG) this.savePNG(scene);
|
||||
return scene;
|
||||
}
|
||||
else return this.data;
|
||||
}
|
||||
|
||||
|
||||
async onload() {
|
||||
this.addAction(DISK_ICON_NAME,"Save drawing",async (ev)=> {
|
||||
await this.save();
|
||||
this.plugin.triggerEmbedUpdates();
|
||||
});
|
||||
this.addAction(PNG_ICON_NAME,"Export as PNG",async (ev)=>this.savePNG());
|
||||
this.addAction(SVG_ICON_NAME,"Export as SVG",async (ev)=>this.saveSVG());
|
||||
}
|
||||
|
||||
async onunload() {
|
||||
if(this.excalidrawRef) await this.save();
|
||||
}
|
||||
|
||||
setViewData (data: string, clear: boolean) {
|
||||
if (this.app.workspace.layoutReady) {
|
||||
this.loadDrawing(data,clear);
|
||||
@@ -41,21 +119,38 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
}
|
||||
|
||||
private loadDrawing (data:string, clear:boolean) :void {
|
||||
// clear the view content
|
||||
clear() {
|
||||
if(this.excalidrawRef) {
|
||||
this.excalidrawRef = null;
|
||||
this.getScene = null;
|
||||
ReactDOM.unmountComponentAtNode(this.contentEl);
|
||||
}
|
||||
}
|
||||
|
||||
private async loadDrawing (data:string, clear:boolean) {
|
||||
if(clear) this.clear();
|
||||
this.justLoaded = true; //a flag to trigger zoom to fit after the drawing has been loaded
|
||||
const excalidrawData = JSON.parse(data);
|
||||
this.instantiateExcalidraw({
|
||||
elements: excalidrawData.elements,
|
||||
appState: excalidrawData.appState,
|
||||
scrollToContent: true,
|
||||
});
|
||||
if(this.excalidrawRef) {
|
||||
this.excalidrawRef.current.updateScene({
|
||||
elements: excalidrawData.elements,
|
||||
appState: excalidrawData.appState,
|
||||
});
|
||||
} else {
|
||||
this.instantiateExcalidraw({
|
||||
elements: excalidrawData.elements,
|
||||
appState: excalidrawData.appState,
|
||||
scrollToContent: true,
|
||||
libraryItems: await this.getLibrary(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// gets the title of the document
|
||||
getDisplayText() {
|
||||
if(this.file) return this.file.basename;
|
||||
else return "Excalidraw (no file)";
|
||||
|
||||
}
|
||||
|
||||
// confirms this view can accept csv extension
|
||||
@@ -73,50 +168,58 @@ export default class ExcalidrawView extends TextFileView {
|
||||
return ICON_NAME;
|
||||
}
|
||||
|
||||
async getLibrary() {
|
||||
const data = JSON.parse(this.plugin.settings.library);
|
||||
return data?.library ? data.library : [];
|
||||
}
|
||||
|
||||
|
||||
private instantiateExcalidraw(initdata: any) {
|
||||
ReactDOM.render(React.createElement(() => {
|
||||
let previousSceneVersion = 0;
|
||||
const reactElement = React.createElement(() => {
|
||||
const excalidrawRef = React.useRef(null);
|
||||
const excalidrawWrapperRef = React.useRef(null);
|
||||
const [dimensions, setDimensions] = React.useState({
|
||||
width: undefined,
|
||||
height: undefined
|
||||
});
|
||||
|
||||
|
||||
this.excalidrawRef = excalidrawRef;
|
||||
React.useEffect(() => {
|
||||
setDimensions({
|
||||
width: this.contentEl.clientWidth,
|
||||
height: this.contentEl.clientHeight,
|
||||
});
|
||||
|
||||
const onResize = () => {
|
||||
try {
|
||||
setDimensions({
|
||||
width: this.contentEl.clientWidth,
|
||||
height: this.contentEl.clientHeight,
|
||||
});
|
||||
} catch(err) {console.log ("onResize ",err)}
|
||||
} catch(err) {console.log ("Excalidraw React-Wrapper, onResize ",err)}
|
||||
};
|
||||
window.addEventListener("resize", onResize);
|
||||
return () => window.removeEventListener("resize", onResize);
|
||||
}, [excalidrawWrapperRef]);
|
||||
|
||||
this.getScene = function() {
|
||||
|
||||
this.getScene = () => {
|
||||
if(!excalidrawRef?.current) {
|
||||
return null;
|
||||
}
|
||||
const el: ExcalidrawElement[] = excalidrawRef.current.getSceneElements();
|
||||
const st: AppState = excalidrawRef.current.getAppState();
|
||||
return JSON.stringify({
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "https://excalidraw.com",
|
||||
"elements": el.filter(e => !e.isDeleted),
|
||||
"elements": el,
|
||||
"appState": {
|
||||
"theme": st.theme,
|
||||
"viewBackgroundColor": st.viewBackgroundColor,
|
||||
"gridSize": st.gridSize,
|
||||
"zenModeEnabled": st.zenModeEnabled
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
return React.createElement(
|
||||
React.Fragment,
|
||||
null,
|
||||
@@ -124,34 +227,51 @@ export default class ExcalidrawView extends TextFileView {
|
||||
"div",
|
||||
{
|
||||
className: "excalidraw-wrapper",
|
||||
ref: excalidrawWrapperRef
|
||||
ref: excalidrawWrapperRef,
|
||||
key: "abc",
|
||||
},
|
||||
React.createElement(Excalidraw.default, {
|
||||
ref: excalidrawRef,
|
||||
key: "xyz",
|
||||
width: dimensions.width,
|
||||
height: dimensions.height,
|
||||
UIOptions: {
|
||||
canvasActions: {
|
||||
loadScene: false,
|
||||
saveScene: false,
|
||||
saveAsScene: false
|
||||
saveAsScene: false,
|
||||
export: false
|
||||
},
|
||||
},
|
||||
initialData: initdata
|
||||
initialData: initdata,
|
||||
onChange: (et:ExcalidrawElement[],st:AppState) => {
|
||||
if(this.justLoaded) {
|
||||
this.justLoaded = false;
|
||||
const e = new KeyboardEvent("keydown", {bubbles : true, cancelable : true, shiftKey : true, code:"Digit1"});
|
||||
this.contentEl.querySelector("canvas")?.dispatchEvent(e);
|
||||
}
|
||||
},
|
||||
onLibraryChange: (items:LibraryItems) => {
|
||||
(async () => {
|
||||
this.plugin.settings.library = EXCALIDRAW_LIB_HEADER+JSON.stringify(items)+'}';
|
||||
await this.plugin.saveSettings();
|
||||
})();
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
}),(this as any).contentEl);
|
||||
});
|
||||
ReactDOM.render(reactElement,(this as any).contentEl);
|
||||
}
|
||||
|
||||
public static getSVG(data:string):SVGSVGElement {
|
||||
public static getSVG(data:string, exportSettings:ExportSettings):SVGSVGElement {
|
||||
try {
|
||||
const excalidrawData = JSON.parse(data);
|
||||
return exportToSvg({
|
||||
elements: excalidrawData.elements,
|
||||
appState: {
|
||||
exportBackground: true,
|
||||
exportWithDarkMode: excalidrawData.appState?.theme=="light" ? false : true,
|
||||
exportBackground: exportSettings.withBackground,
|
||||
exportWithDarkMode: exportSettings.withTheme ? (excalidrawData.appState?.theme=="light" ? false : true) : false,
|
||||
... excalidrawData.appState,},
|
||||
exportPadding:10,
|
||||
metadata: "Generated by Excalidraw-Obsidian plugin",
|
||||
@@ -160,4 +280,22 @@ export default class ExcalidrawView extends TextFileView {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static async getPNG(data:string, exportSettings:ExportSettings) {
|
||||
try {
|
||||
const excalidrawData = JSON.parse(data);
|
||||
return await Excalidraw.exportToBlob({
|
||||
elements: excalidrawData.elements,
|
||||
appState: {
|
||||
exportBackground: exportSettings.withBackground,
|
||||
exportWithDarkMode: exportSettings.withTheme ? (excalidrawData.appState?.theme=="light" ? false : true) : false,
|
||||
... excalidrawData.appState,},
|
||||
mimeType: "image/png",
|
||||
exportWithDarkMode: "true",
|
||||
metadata: "Generated by Excalidraw-Obsidian plugin",
|
||||
});
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
325
src/main.ts
325
src/main.ts
@@ -8,6 +8,9 @@ import {
|
||||
PluginManifest,
|
||||
MarkdownView,
|
||||
normalizePath,
|
||||
MarkdownPostProcessorContext,
|
||||
Menu,
|
||||
MenuItem,
|
||||
} from 'obsidian';
|
||||
import {
|
||||
BLANK_DRAWING,
|
||||
@@ -16,8 +19,17 @@ import {
|
||||
ICON_NAME,
|
||||
EXCALIDRAW_FILE_EXTENSION,
|
||||
CODEBLOCK_EXCALIDRAW,
|
||||
DISK_ICON,
|
||||
DISK_ICON_NAME,
|
||||
PNG_ICON,
|
||||
PNG_ICON_NAME,
|
||||
SVG_ICON,
|
||||
SVG_ICON_NAME,
|
||||
RERENDER_EVENT,
|
||||
VIRGIL_FONT,
|
||||
CASCADIA_FONT
|
||||
} from './constants';
|
||||
import ExcalidrawView from './ExcalidrawView';
|
||||
import ExcalidrawView, {ExportSettings} from './ExcalidrawView';
|
||||
import {
|
||||
ExcalidrawSettings,
|
||||
DEFAULT_SETTINGS,
|
||||
@@ -27,119 +39,256 @@ import {
|
||||
openDialogAction,
|
||||
OpenFileDialog
|
||||
} from './openDrawing';
|
||||
import {
|
||||
initExcalidrawAutomate,
|
||||
destroyExcalidrawAutomate
|
||||
} from './ExcalidrawTemplate';
|
||||
import { norm } from '@excalidraw/excalidraw/types/ga';
|
||||
|
||||
export interface ExcalidrawAutomate extends Window {
|
||||
ExcalidrawAutomate: {
|
||||
theme: string;
|
||||
createNew: Function;
|
||||
};
|
||||
}
|
||||
|
||||
export default class ExcalidrawPlugin extends Plugin {
|
||||
public settings: ExcalidrawSettings;
|
||||
private openDialog: OpenFileDialog;
|
||||
|
||||
private excalidrawAutomate: ExcalidrawAutomate;
|
||||
|
||||
constructor(app: App, manifest: PluginManifest) {
|
||||
super(app, manifest);
|
||||
}
|
||||
|
||||
async onload() {
|
||||
addIcon(ICON_NAME, EXCALIDRAW_ICON);
|
||||
addIcon(DISK_ICON_NAME,DISK_ICON);
|
||||
addIcon(PNG_ICON_NAME,PNG_ICON);
|
||||
addIcon(SVG_ICON_NAME,SVG_ICON);
|
||||
|
||||
const myFonts = document.createElement('style');
|
||||
myFonts.appendChild(document.createTextNode(VIRGIL_FONT));
|
||||
myFonts.appendChild(document.createTextNode(CASCADIA_FONT));
|
||||
document.head.appendChild(myFonts);
|
||||
|
||||
initExcalidrawAutomate(this);
|
||||
|
||||
this.registerView(
|
||||
VIEW_TYPE_EXCALIDRAW,
|
||||
(leaf: WorkspaceLeaf) => new ExcalidrawView(leaf)
|
||||
(leaf: WorkspaceLeaf) => new ExcalidrawView(leaf, this)
|
||||
);
|
||||
|
||||
this.registerExtensions([EXCALIDRAW_FILE_EXTENSION],VIEW_TYPE_EXCALIDRAW);
|
||||
|
||||
this.registerMarkdownCodeBlockProcessor(CODEBLOCK_EXCALIDRAW, async (source,el,ctx) => {
|
||||
const parseError = (message: string) => {
|
||||
el.createDiv("excalidraw-error",(el)=> {
|
||||
el.createEl("p","Please provide a link to an excalidraw file: [[file."+EXCALIDRAW_FILE_EXTENSION+"]]");
|
||||
el.createEl("p",message);
|
||||
el.createEl("p",source);
|
||||
})
|
||||
}
|
||||
|
||||
const filename = source.match(/\[{2}(.*)\]{2}/m);
|
||||
const filenameWH = source.match(/\[{2}(.*)\|(\d*)x(\d*)\]{2}/m);
|
||||
const filenameW = source.match(/\[{2}(.*)\|(\d*)\]{2}/m);
|
||||
|
||||
let fname:string = '';
|
||||
let fwidth:string = this.settings.width;
|
||||
let fheight:string = null;
|
||||
|
||||
if (filenameWH) {
|
||||
fname = filenameWH[1];
|
||||
fwidth = filenameWH[2];
|
||||
fheight = filenameWH[3];
|
||||
} else if (filenameW) {
|
||||
fname = filenameW[1];
|
||||
fwidth = filenameW[2];
|
||||
} else if (filename) {
|
||||
fname = filename[1];
|
||||
}
|
||||
|
||||
if(fname == '') {
|
||||
parseError("No link to file found in codeblock.");
|
||||
return;
|
||||
}
|
||||
|
||||
const file = this.app.vault.getAbstractFileByPath(fname);
|
||||
if(!(file && file instanceof TFile)) {
|
||||
parseError("File does not exist. " + fname);
|
||||
return;
|
||||
}
|
||||
|
||||
if(file.extension != EXCALIDRAW_FILE_EXTENSION) {
|
||||
parseError("Not an excalidraw file. Must have extension " + EXCALIDRAW_FILE_EXTENSION);
|
||||
return;
|
||||
}
|
||||
|
||||
const content = await this.app.vault.read(file);
|
||||
const svg = ExcalidrawView.getSVG(content);
|
||||
if(!svg) {
|
||||
parseError("Parse error. Not a valid Excalidraw file.");
|
||||
return;
|
||||
}
|
||||
el.createDiv("excalidraw-svg",(el)=> {
|
||||
svg.removeAttribute('width');
|
||||
svg.removeAttribute('height');
|
||||
svg.style.setProperty('width',fwidth);
|
||||
if(fheight) svg.style.setProperty('height',fheight);
|
||||
el.appendChild(svg);
|
||||
el.addEventListener(RERENDER_EVENT,async (e) => {
|
||||
e.stopPropagation();
|
||||
el.empty();
|
||||
this.codeblockProcessor(source,el,ctx,this);
|
||||
});
|
||||
});
|
||||
this.codeblockProcessor(source,el,ctx,this);
|
||||
});
|
||||
|
||||
await this.loadSettings();
|
||||
this.addSettingTab(new ExcalidrawSettingTab(this.app, this));
|
||||
|
||||
this.openDialog = new OpenFileDialog(this.app, this);
|
||||
this.addRibbonIcon(ICON_NAME, 'Excalidraw', async () => {
|
||||
this.openDialog.start(openDialogAction.openFile);
|
||||
this.addRibbonIcon(ICON_NAME, 'Create a new drawing in Excalidraw', async (e) => {
|
||||
this.createDrawing(this.getNextDefaultFilename(), e.ctrlKey);
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "excalidraw-open",
|
||||
name: "Open an existing drawing or create new one",
|
||||
name: "Open an existing drawing - IN A NEW PANE",
|
||||
callback: () => {
|
||||
this.openDialog.start(openDialogAction.openFile);
|
||||
this.openDialog.start(openDialogAction.openFile, true);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
this.addCommand({
|
||||
id: "excalidraw-open-on-current",
|
||||
name: "Open an existing drawing - IN THE CURRENT ACTIVE PANE",
|
||||
callback: () => {
|
||||
this.openDialog.start(openDialogAction.openFile, false);
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "excalidraw-insert-transclusion",
|
||||
name: "Transclude an ."+EXCALIDRAW_FILE_EXTENSION+" file into a markdown document",
|
||||
callback: () => {
|
||||
this.openDialog.start(openDialogAction.insertLink);
|
||||
name: "Transclude (embed) an ."+EXCALIDRAW_FILE_EXTENSION+" drawing",
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
return this.app.workspace.activeLeaf.view.getViewType() == "markdown";
|
||||
} else {
|
||||
this.openDialog.start(openDialogAction.insertLink, false);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
this.addCommand({
|
||||
id: "excalidraw-autocreate",
|
||||
name: "Create a new drawing",
|
||||
name: "Create a new drawing - IN A NEW PANE",
|
||||
callback: () => {
|
||||
this.createDrawing(this.getNextDefaultFilename());
|
||||
this.createDrawing(this.getNextDefaultFilename(), true);
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "excalidraw-autocreate-on-current",
|
||||
name: "Create a new drawing - IN THE CURRENT ACTIVE PANE",
|
||||
callback: () => {
|
||||
this.createDrawing(this.getNextDefaultFilename(), false);
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: 'export-svg',
|
||||
name: 'Export SVG. Save it next to the current file',
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
return this.app.workspace.activeLeaf.view.getViewType() == VIEW_TYPE_EXCALIDRAW;
|
||||
} else {
|
||||
const view = this.app.workspace.activeLeaf.view;
|
||||
if(view.getViewType() == VIEW_TYPE_EXCALIDRAW) {
|
||||
(this.app.workspace.activeLeaf.view as ExcalidrawView).saveSVG();
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: 'export-png',
|
||||
name: 'Export PNG. Save it next to the current file',
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
return this.app.workspace.activeLeaf.view.getViewType() == VIEW_TYPE_EXCALIDRAW;
|
||||
} else {
|
||||
const view = this.app.workspace.activeLeaf.view;
|
||||
if(view.getViewType() == VIEW_TYPE_EXCALIDRAW) {
|
||||
(this.app.workspace.activeLeaf.view as ExcalidrawView).savePNG();
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
this.registerEvent(
|
||||
this.app.workspace.on("file-menu", (menu: Menu, file: TFile) => {
|
||||
if (file instanceof TFolder) {
|
||||
menu.addItem((item: MenuItem) => {
|
||||
item.setTitle("Create Excalidraw drawing")
|
||||
.setIcon(ICON_NAME)
|
||||
.onClick(evt => {
|
||||
this.createDrawing(file.path+this.getNextDefaultFilename(),false,file.path);
|
||||
})
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
//watch filename change to rename .svg
|
||||
this.app.vault.on('rename',async (file,oldPath) => {
|
||||
if (!(this.settings.keepInSync && file instanceof TFile)) return;
|
||||
if (file.extension != EXCALIDRAW_FILE_EXTENSION) return;
|
||||
const oldSVGpath = oldPath.substring(0,oldPath.lastIndexOf('.'+EXCALIDRAW_FILE_EXTENSION)) + '.svg';
|
||||
const svgFile = this.app.vault.getAbstractFileByPath(normalizePath(oldSVGpath));
|
||||
if(svgFile && svgFile instanceof TFile) {
|
||||
const newSVGpath = file.path.substring(0,file.path.lastIndexOf('.'+EXCALIDRAW_FILE_EXTENSION)) + '.svg';
|
||||
await this.app.vault.rename(svgFile,newSVGpath);
|
||||
}
|
||||
});
|
||||
|
||||
//watch file delete and delete corresponding .svg
|
||||
this.app.vault.on('delete',async (file:TFile) => {
|
||||
if (!(file instanceof TFile)) return;
|
||||
if (file.extension != EXCALIDRAW_FILE_EXTENSION) return;
|
||||
|
||||
const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
for (let i=0;i<leaves.length;i++) {
|
||||
if((leaves[i].view as ExcalidrawView).file.path == file.path) {
|
||||
//(leaves[i].view as ExcalidrawView).clear();
|
||||
leaves[i].setViewState({
|
||||
type: VIEW_TYPE_EXCALIDRAW,
|
||||
state: {file: null}}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.settings.keepInSync) {
|
||||
const svgPath = file.path.substring(0,file.path.lastIndexOf('.'+EXCALIDRAW_FILE_EXTENSION)) + '.svg';
|
||||
const svgFile = this.app.vault.getAbstractFileByPath(normalizePath(svgPath));
|
||||
if(svgFile && svgFile instanceof TFile) {
|
||||
await this.app.vault.delete(svgFile);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
onunload() {
|
||||
destroyExcalidrawAutomate();
|
||||
}
|
||||
|
||||
private async codeblockProcessor(source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext, plugin: ExcalidrawPlugin) {
|
||||
const parseError = (message: string) => {
|
||||
el.createDiv("excalidraw-error",(el)=> {
|
||||
el.createEl("p","Please provide a link to an excalidraw file: [[file."+EXCALIDRAW_FILE_EXTENSION+"]]");
|
||||
el.createEl("p",message);
|
||||
el.createEl("p",source);
|
||||
})
|
||||
}
|
||||
|
||||
const parts = source.match(/\[{2}([^|]*)\|?(\d*)x?(\d*)\|?(.*)\]{2}/m);
|
||||
if(!parts) {
|
||||
parseError("No link to file found in codeblock.");
|
||||
return;
|
||||
}
|
||||
const fname = parts[1];
|
||||
const fwidth = parts[2]? parts[2] : plugin.settings.width;
|
||||
const fheight = parts[3];
|
||||
const style = "excalidraw-svg" + (parts[4] ? "-" + parts[4] : "");
|
||||
|
||||
if(!fname) {
|
||||
parseError("No link to file found in codeblock.");
|
||||
return;
|
||||
}
|
||||
|
||||
const file = plugin.app.vault.getAbstractFileByPath(fname);
|
||||
if(!(file && file instanceof TFile)) {
|
||||
parseError("File does not exist. " + fname);
|
||||
return;
|
||||
}
|
||||
|
||||
if(file.extension != EXCALIDRAW_FILE_EXTENSION) {
|
||||
parseError("Not an excalidraw file. Must have extension " + EXCALIDRAW_FILE_EXTENSION);
|
||||
return;
|
||||
}
|
||||
|
||||
const content = await plugin.app.vault.read(file);
|
||||
const exportSettings: ExportSettings = {
|
||||
withBackground: plugin.settings.exportWithBackground,
|
||||
withTheme: plugin.settings.exportWithTheme
|
||||
}
|
||||
const svg = ExcalidrawView.getSVG(content,exportSettings);
|
||||
if(!svg) {
|
||||
parseError("Parse error. Not a valid Excalidraw file.");
|
||||
return;
|
||||
}
|
||||
el.createDiv(style,(el)=> {
|
||||
svg.removeAttribute('width');
|
||||
svg.removeAttribute('height');
|
||||
svg.style.setProperty('width',fwidth);
|
||||
if(fheight) svg.style.setProperty('height',fheight);
|
||||
svg.addClass(style);
|
||||
el.appendChild(svg);
|
||||
});
|
||||
}
|
||||
|
||||
public insertCodeblock(data:string) {
|
||||
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if(activeView) {
|
||||
@@ -162,12 +311,20 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
|
||||
public async openDrawing(drawingFile: TFile) {
|
||||
const leafs = this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
public triggerEmbedUpdates(){
|
||||
const e = document.createEvent("Event")
|
||||
e.initEvent(RERENDER_EVENT,true,false);
|
||||
document
|
||||
.querySelectorAll("svg[class^='excalidraw-svg']")
|
||||
.forEach((el) => el.dispatchEvent(e));
|
||||
}
|
||||
|
||||
public async openDrawing(drawingFile: TFile, onNewPane: boolean) {
|
||||
const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
let leaf:WorkspaceLeaf = null;
|
||||
|
||||
if (leafs?.length > 0) {
|
||||
leaf = leafs[0];
|
||||
if (leaves?.length > 0) {
|
||||
leaf = leaves[0];
|
||||
}
|
||||
if(!leaf) {
|
||||
leaf = this.app.workspace.activeLeaf;
|
||||
@@ -176,6 +333,10 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
if(!leaf) {
|
||||
leaf = this.app.workspace.getLeaf();
|
||||
}
|
||||
|
||||
if(onNewPane) {
|
||||
leaf = this.app.workspace.createLeafBySplit(leaf);
|
||||
}
|
||||
|
||||
leaf.setViewState({
|
||||
type: VIEW_TYPE_EXCALIDRAW,
|
||||
@@ -184,21 +345,27 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
|
||||
private getNextDefaultFilename():string {
|
||||
return this.settings.folder+'/Drawing ' + window.moment().format('YYYY-MM-DD HH.mm.ss')+'.'+EXCALIDRAW_FILE_EXTENSION;
|
||||
return 'Drawing ' + window.moment().format('YYYY-MM-DD HH.mm.ss')+'.'+EXCALIDRAW_FILE_EXTENSION;
|
||||
}
|
||||
|
||||
public async createDrawing(filename: string) {
|
||||
const folder = this.app.vault.getAbstractFileByPath(normalizePath(this.settings.folder));
|
||||
public async createDrawing(filename: string, onNewPane: boolean, foldername?: string, initData?:string) {
|
||||
const fname = foldername ? normalizePath(foldername)+'/'+filename : normalizePath(this.settings.folder) + '/' + filename;
|
||||
const folder = this.app.vault.getAbstractFileByPath(normalizePath(foldername ? foldername: this.settings.folder));
|
||||
if (!(folder && folder instanceof TFolder)) {
|
||||
await this.app.vault.createFolder(this.settings.folder);
|
||||
}
|
||||
|
||||
if(initData) {
|
||||
this.openDrawing(await this.app.vault.create(fname,initData),onNewPane);
|
||||
return;
|
||||
}
|
||||
|
||||
const file = this.app.vault.getAbstractFileByPath(normalizePath(this.settings.templateFilePath));
|
||||
if(file && file instanceof TFile) {
|
||||
const content = await this.app.vault.read(file);
|
||||
this.openDrawing(await this.app.vault.create(filename,content==''?BLANK_DRAWING:content));
|
||||
this.openDrawing(await this.app.vault.create(fname,content==''?BLANK_DRAWING:content), onNewPane);
|
||||
} else {
|
||||
this.openDrawing(await this.app.vault.create(filename,BLANK_DRAWING));
|
||||
this.openDrawing(await this.app.vault.create(fname,BLANK_DRAWING), onNewPane);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,13 @@
|
||||
import { App, FuzzySuggestModal, TFile, TFolder, normalizePath, Vault, TAbstractFile, Instruction } from "obsidian";
|
||||
import {
|
||||
App,
|
||||
FuzzySuggestModal,
|
||||
TFile
|
||||
} from "obsidian";
|
||||
import ExcalidrawPlugin from './main';
|
||||
import {EMPTY_MESSAGE,EXCALIDRAW_FILE_EXTENSION} from './constants';
|
||||
import {
|
||||
EMPTY_MESSAGE,
|
||||
EXCALIDRAW_FILE_EXTENSION
|
||||
} from './constants';
|
||||
|
||||
export enum openDialogAction {
|
||||
openFile,
|
||||
@@ -11,23 +18,23 @@ export class OpenFileDialog extends FuzzySuggestModal<TFile> {
|
||||
public app: App;
|
||||
private plugin: ExcalidrawPlugin;
|
||||
private action: openDialogAction;
|
||||
private onNewPane: boolean;
|
||||
|
||||
|
||||
constructor(app: App, plugin: ExcalidrawPlugin) {
|
||||
super(app);
|
||||
this.app = app;
|
||||
this.action = openDialogAction.openFile;
|
||||
this.plugin = plugin;
|
||||
this.onNewPane = false;
|
||||
this.setInstructions([{
|
||||
command: "Type name of drawing to select.",
|
||||
purpose: "",
|
||||
}]);
|
||||
|
||||
|
||||
this.inputEl.onkeyup = (e) => {
|
||||
if(e.key=="Enter" && this.action == openDialogAction.openFile) {
|
||||
if (this.containerEl.innerText.includes(EMPTY_MESSAGE)) {
|
||||
this.plugin.createDrawing(this.plugin.settings.folder+'/'+this.inputEl.value+'.'+EXCALIDRAW_FILE_EXTENSION);
|
||||
this.plugin.createDrawing(this.plugin.settings.folder+'/'+this.inputEl.value+'.'+EXCALIDRAW_FILE_EXTENSION, this.onNewPane);
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
@@ -46,7 +53,7 @@ export class OpenFileDialog extends FuzzySuggestModal<TFile> {
|
||||
onChooseItem(item: TFile, _evt: MouseEvent | KeyboardEvent): void {
|
||||
switch(this.action) {
|
||||
case(openDialogAction.openFile):
|
||||
this.plugin.openDrawing(item);
|
||||
this.plugin.openDrawing(item, this.onNewPane);
|
||||
break;
|
||||
case(openDialogAction.insertLink):
|
||||
this.plugin.insertCodeblock(item.path);
|
||||
@@ -54,8 +61,9 @@ export class OpenFileDialog extends FuzzySuggestModal<TFile> {
|
||||
}
|
||||
}
|
||||
|
||||
start(action:openDialogAction): void {
|
||||
start(action:openDialogAction, onNewPane: boolean): void {
|
||||
this.action = action;
|
||||
this.onNewPane = onNewPane;
|
||||
switch(action) {
|
||||
case (openDialogAction.openFile):
|
||||
this.emptyStateText = EMPTY_MESSAGE;
|
||||
|
||||
112
src/settings.ts
112
src/settings.ts
@@ -1,16 +1,33 @@
|
||||
import {App, PluginSettingTab, Setting} from 'obsidian';
|
||||
import {
|
||||
App,
|
||||
parseFrontMatterAliases,
|
||||
PluginSettingTab,
|
||||
Setting
|
||||
} from 'obsidian';
|
||||
import type ExcalidrawPlugin from "./main";
|
||||
|
||||
export interface ExcalidrawSettings {
|
||||
folder: string,
|
||||
templateFilePath: string,
|
||||
width: string,
|
||||
exportWithTheme: boolean,
|
||||
exportWithBackground: boolean,
|
||||
autoexportSVG: boolean,
|
||||
autoexportPNG: boolean,
|
||||
keepInSync: boolean,
|
||||
library: string,
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
folder: 'excalidraw',
|
||||
templateFilePath: '',
|
||||
folder: 'Excalidraw',
|
||||
templateFilePath: 'Excalidraw/Template.excalidraw',
|
||||
width: '400',
|
||||
exportWithTheme: true,
|
||||
exportWithBackground: true,
|
||||
autoexportSVG: false,
|
||||
autoexportPNG: false,
|
||||
keepInSync: false,
|
||||
library: `{"type":"excalidrawlib","version":1,"library":[]}`,
|
||||
}
|
||||
|
||||
export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
@@ -23,27 +40,26 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
|
||||
display(): void {
|
||||
let {containerEl} = this;
|
||||
|
||||
this.containerEl.empty();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Excalidraw folder')
|
||||
.setDesc('Default location for Excalidraw drawings. Leaving this empty means drawings will be saved to the Vault root.')
|
||||
.addText(text => text
|
||||
.setPlaceholder('excalidraw')
|
||||
.setValue(this.plugin.settings.folder)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.folder = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
.setName('Excalidraw folder')
|
||||
.setDesc('Default location for your Excalidraw drawings. Leaving this empty means drawings will be created in the Vault root.')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Excalidraw')
|
||||
.setValue(this.plugin.settings.folder)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.folder = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Excalidraw template file')
|
||||
.setDesc('Full path to file containing the file you want to use as the template for new Excalidraw drawings. '+
|
||||
'Note that Excalidraw files will have an extension of ".excalidraw" ' +
|
||||
'Assuming your template is in the default excalidraw folder, the setting would be: excalidraw/Template.excalidraw')
|
||||
'Note that Excalidraw files will have the extension ".excalidraw". ' +
|
||||
'Assuming your template is in the default Excalidraw folder, the setting would be: Excalidraw/Template.excalidraw')
|
||||
.addText(text => text
|
||||
.setPlaceholder('excalidraw')
|
||||
.setPlaceholder('Excalidraw/Template.excalidraw')
|
||||
.setValue(this.plugin.settings.templateFilePath)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.templateFilePath = value;
|
||||
@@ -51,7 +67,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Default width of embedded image')
|
||||
.setName('Default width of embedded (transcluded) image')
|
||||
.setDesc('The default width of an embedded drawing. You can specify a different ' +
|
||||
'width when embedding an image using the [[drawing.excalidraw|100]] or ' +
|
||||
'[[drawing.excalidraw|100x100]] format.')
|
||||
@@ -61,6 +77,68 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.width = value;
|
||||
await this.plugin.saveSettings();
|
||||
this.plugin.triggerEmbedUpdates();
|
||||
}));
|
||||
|
||||
this.containerEl.createEl('h1', {text: 'Embedded image settings'});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Export image with background')
|
||||
.setDesc('If turned off, the exported image will be transparent.')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.exportWithBackground)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.exportWithBackground = value;
|
||||
await this.plugin.saveSettings();
|
||||
this.plugin.triggerEmbedUpdates();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Export image with theme')
|
||||
.setDesc('Export the image matching the dark/light theme setting used for your drawing in Excalidraw. If turned off, ' +
|
||||
'drawings created in drak mode will appear as they would in light mode.')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.exportWithTheme)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.exportWithTheme = value;
|
||||
await this.plugin.saveSettings();
|
||||
this.plugin.triggerEmbedUpdates();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Auto-export SVG')
|
||||
.setDesc('Automatically create an SVG export of your drawing matching the title of your "my drawing.excalidraw" file. ' +
|
||||
'The plugin will save the .SVG file in the same folder as the drawing. '+
|
||||
'You can use this file ("my drawing.svg") to embed your drawing into documents in a platform independent way. ' +
|
||||
'While the auto export switch is on, this file will get updated every time you edit the excalidraw drawing with the matching name.')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.autoexportSVG)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.autoexportSVG = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Auto-export PNG')
|
||||
.setDesc('Same as the auto-export SVG, but for PNG.')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.autoexportPNG)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.autoexportPNG = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Keep the .SVG and/or .PNG filenames in sync with the .excalidraw file')
|
||||
.setDesc('When turned on, the plugin will automaticaly update the filename of the .SVG and/or .PNG files when the .excalidraw file in the same folder (and same name) is renamed. ' +
|
||||
'The plugin will also automatically delete the .SVG and/or .PNG files when the .excalidraw file in the same folder (and same name) is deleted. ')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.keepInSync)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.keepInSync = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
}
|
||||
}
|
||||
22
styles.css
22
styles.css
@@ -15,4 +15,26 @@
|
||||
|
||||
.block-language-excalidraw {
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
.excalidraw .github-corner {
|
||||
display: none;
|
||||
}
|
||||
|
||||
svg.excalidraw-svg-right-wrap {
|
||||
float: right;
|
||||
margin: 0px 0px 20px 20px;
|
||||
}
|
||||
|
||||
svg.excalidraw-svg-left-wrap {
|
||||
float: left;
|
||||
margin: 0px 35px 20px 0px;
|
||||
}
|
||||
|
||||
div.excalidraw-svg-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
div.excalidraw-svg-left {
|
||||
text-align: left;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"1.0.2": "0.11.13",
|
||||
"1.0.1": "0.11.13",
|
||||
"1.0.0": "0.11.13"
|
||||
}
|
||||
"1.0.7": "0.11.13",
|
||||
"1.0.6": "0.11.13",
|
||||
"1.0.5": "0.11.13"
|
||||
}
|
||||
|
||||
206
yarn.lock
206
yarn.lock
@@ -28,7 +28,7 @@
|
||||
"resolved" "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.13.15.tgz"
|
||||
"version" "7.13.15"
|
||||
|
||||
"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.13.0", "@babel/core@^7.4.0-0", "@babel/core@^7.7.5", "@babel/core@^7.8.4", "@babel/core@^7.9.0", "@babel/core@7 || ^7.0.0-rc.2":
|
||||
"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.13.0", "@babel/core@^7.3.3", "@babel/core@^7.4.0-0", "@babel/core@^7.7.5", "@babel/core@^7.8.4", "@babel/core@^7.9.0", "@babel/core@7 || ^7.0.0-rc.2":
|
||||
"integrity" "sha512-6GXmNYeNjS2Uz+uls5jalOemgIhnTMeaXo+yBUA72kC2uX/8VW6XyhVIo2L8/q0goKQA3EVKx0KOQpVKSeWadQ=="
|
||||
"resolved" "https://registry.npmjs.org/@babel/core/-/core-7.13.15.tgz"
|
||||
"version" "7.13.15"
|
||||
@@ -176,7 +176,7 @@
|
||||
dependencies:
|
||||
"@babel/types" "^7.13.12"
|
||||
|
||||
"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.12.1", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.13.12":
|
||||
"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.12.1", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.13.12":
|
||||
"integrity" "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA=="
|
||||
"resolved" "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz"
|
||||
"version" "7.13.12"
|
||||
@@ -914,7 +914,7 @@
|
||||
"@babel/helper-create-regexp-features-plugin" "^7.12.13"
|
||||
"@babel/helper-plugin-utils" "^7.12.13"
|
||||
|
||||
"@babel/preset-env@^7.8.4", "@babel/preset-env@^7.9.5":
|
||||
"@babel/preset-env@^7.3.1", "@babel/preset-env@^7.8.4", "@babel/preset-env@^7.9.5":
|
||||
"integrity" "sha512-D4JAPMXcxk69PKe81jRJ21/fP/uYdcTZ3hJDF5QX2HSI9bBxxYw/dumdR6dGumhjxlprHPE4XWoPaqzZUVy2MA=="
|
||||
"resolved" "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.13.15.tgz"
|
||||
"version" "7.13.15"
|
||||
@@ -1072,7 +1072,7 @@
|
||||
"@babel/types" "^7.4.4"
|
||||
"esutils" "^2.0.2"
|
||||
|
||||
"@babel/preset-react@^7.9.4":
|
||||
"@babel/preset-react@^7.0.0", "@babel/preset-react@^7.9.4":
|
||||
"integrity" "sha512-gx+tDLIE06sRjKJkVtpZ/t3mzCDOnPG+ggHZG9lffUbX8+wC739x20YQc9V35Do6ZAxaUc/HhVHIiOzz5MvDmA=="
|
||||
"resolved" "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.13.13.tgz"
|
||||
"version" "7.13.13"
|
||||
@@ -1190,10 +1190,10 @@
|
||||
"minimatch" "^3.0.4"
|
||||
"strip-json-comments" "^3.1.1"
|
||||
|
||||
"@excalidraw/excalidraw@0.6.0":
|
||||
"integrity" "sha512-JC+Sg1T3AUJOX2xKp0/pCX7d845fta4nKc3Uw1V5Y2a5bi9AWSybjEPiuYsUxtKbCaKf1o6OaBgj/IXvPPIi4Q=="
|
||||
"resolved" "https://registry.npmjs.org/@excalidraw/excalidraw/-/excalidraw-0.6.0.tgz"
|
||||
"version" "0.6.0"
|
||||
"@excalidraw/excalidraw@0.7.0":
|
||||
"integrity" "sha512-D1yMXhOjWjCJIXT0puZD2QaGQ28QN2DNrVUQTsOouhPSELwhFoM7Exg/ulSwkLBI2l2KN7m39+2tj6QVijv4Sg=="
|
||||
"resolved" "https://registry.npmjs.org/@excalidraw/excalidraw/-/excalidraw-0.7.0.tgz"
|
||||
"version" "0.7.0"
|
||||
|
||||
"@hapi/address@2.x.x":
|
||||
"integrity" "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ=="
|
||||
@@ -1455,6 +1455,19 @@
|
||||
"schema-utils" "^2.6.5"
|
||||
"source-map" "^0.7.3"
|
||||
|
||||
"@polka/url@^1.0.0-next.9":
|
||||
"integrity" "sha512-6RglhutqrGFMO1MNUXp95RBuYIuc8wTnMAV5MUhLmjTOy78ncwOw7RgeQ/HeymkKXRhZd0s2DNrM1rL7unk3MQ=="
|
||||
"resolved" "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.12.tgz"
|
||||
"version" "1.0.0-next.12"
|
||||
|
||||
"@rollup/plugin-babel@5.3.0":
|
||||
"integrity" "sha512-9uIC8HZOnVLrLHxayq/PTzw+uS25E14KPUBh5ktF+18Mjo5yK0ToMMx6epY0uEgkjwJw0aBW4x2horYXh8juWw=="
|
||||
"resolved" "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz"
|
||||
"version" "5.3.0"
|
||||
dependencies:
|
||||
"@babel/helper-module-imports" "^7.10.4"
|
||||
"@rollup/pluginutils" "^3.1.0"
|
||||
|
||||
"@rollup/plugin-commonjs@^15.1.0":
|
||||
"integrity" "sha512-xCQqz4z/o0h2syQ7d9LskIMvBSH4PX5PjYdpSSvgS+pQik3WahkQVNWg3D8XJeYjZoVWnIUQYDghuEMRGrmQYQ=="
|
||||
"resolved" "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-15.1.0.tgz"
|
||||
@@ -1646,7 +1659,7 @@
|
||||
"resolved" "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz"
|
||||
"version" "1.3.1"
|
||||
|
||||
"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7":
|
||||
"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7", "@types/babel__core@^7.1.9":
|
||||
"integrity" "sha512-zGZJzzBUVDo/eV6KgbE0f0ZI7dInEYvo12Rb70uNQDshC3SkRMb67ja0GgRHZgAX3Za6rhaWlvbDO8rrGyAb1g=="
|
||||
"resolved" "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.14.tgz"
|
||||
"version" "7.1.14"
|
||||
@@ -2143,6 +2156,11 @@
|
||||
"resolved" "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz"
|
||||
"version" "4.2.2"
|
||||
|
||||
"aakansha-excalidraw@0.7.0-draft":
|
||||
"integrity" "sha512-bafyy/qQES3E+uI7YHGuMzIh2kKlTKx+NYYpHwR5QhdAdDsA+Tru56Yglo+8cLCSDncN/KyEb1ffQlYJyBqQwA=="
|
||||
"resolved" "https://registry.npmjs.org/aakansha-excalidraw/-/aakansha-excalidraw-0.7.0-draft.tgz"
|
||||
"version" "0.7.0-draft"
|
||||
|
||||
"abab@^2.0.3", "abab@^2.0.5":
|
||||
"integrity" "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q=="
|
||||
"resolved" "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz"
|
||||
@@ -2174,6 +2192,11 @@
|
||||
"resolved" "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz"
|
||||
"version" "7.2.0"
|
||||
|
||||
"acorn-walk@^8.0.0":
|
||||
"integrity" "sha512-+bpA9MJsHdZ4bgfDcpk0ozQyhhVct7rzOmO0s1IIr0AGGgKBljss8n2zp11rRP2wid5VGeh04CgeKzgat5/25A=="
|
||||
"resolved" "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.0.2.tgz"
|
||||
"version" "8.0.2"
|
||||
|
||||
"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", "acorn@^7.1.0", "acorn@^7.1.1", "acorn@^7.4.0":
|
||||
"integrity" "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="
|
||||
"resolved" "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz"
|
||||
@@ -2184,6 +2207,11 @@
|
||||
"resolved" "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz"
|
||||
"version" "6.4.2"
|
||||
|
||||
"acorn@^8.0.4":
|
||||
"integrity" "sha512-xYiIVjNuqtKXMxlRMDc6mZUhXehod4a3gbZ1qRlM7icK4EbxUFNLhWoPblCvFtB2Y9CIqHP3CF/rdxLItaQv8g=="
|
||||
"resolved" "https://registry.npmjs.org/acorn/-/acorn-8.1.1.tgz"
|
||||
"version" "8.1.1"
|
||||
|
||||
"acorn@^8.1.0":
|
||||
"integrity" "sha512-xYiIVjNuqtKXMxlRMDc6mZUhXehod4a3gbZ1qRlM7icK4EbxUFNLhWoPblCvFtB2Y9CIqHP3CF/rdxLItaQv8g=="
|
||||
"resolved" "https://registry.npmjs.org/acorn/-/acorn-8.1.1.tgz"
|
||||
@@ -3364,6 +3392,15 @@
|
||||
"strip-ansi" "^6.0.0"
|
||||
"wrap-ansi" "^6.2.0"
|
||||
|
||||
"cliui@^7.0.2":
|
||||
"integrity" "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ=="
|
||||
"resolved" "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz"
|
||||
"version" "7.0.4"
|
||||
dependencies:
|
||||
"string-width" "^4.2.0"
|
||||
"strip-ansi" "^6.0.0"
|
||||
"wrap-ansi" "^7.0.0"
|
||||
|
||||
"clone-deep@^4.0.1":
|
||||
"integrity" "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ=="
|
||||
"resolved" "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz"
|
||||
@@ -3469,6 +3506,11 @@
|
||||
"resolved" "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz"
|
||||
"version" "4.1.1"
|
||||
|
||||
"commander@^6.2.0":
|
||||
"integrity" "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA=="
|
||||
"resolved" "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz"
|
||||
"version" "6.2.1"
|
||||
|
||||
"common-tags@^1.8.0":
|
||||
"integrity" "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw=="
|
||||
"resolved" "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz"
|
||||
@@ -3749,6 +3791,11 @@
|
||||
"resolved" "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz"
|
||||
"version" "1.0.0"
|
||||
|
||||
"cyclist@^1.0.1":
|
||||
"integrity" "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk="
|
||||
"resolved" "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz"
|
||||
"version" "1.0.1"
|
||||
|
||||
"css-blank-pseudo@^0.1.4":
|
||||
"integrity" "sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w=="
|
||||
"resolved" "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz"
|
||||
@@ -3960,11 +4007,6 @@
|
||||
"resolved" "https://registry.npmjs.org/csstype/-/csstype-3.0.6.tgz"
|
||||
"version" "3.0.6"
|
||||
|
||||
"cyclist@^1.0.1":
|
||||
"integrity" "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk="
|
||||
"resolved" "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz"
|
||||
"version" "1.0.1"
|
||||
|
||||
"d@^1.0.1", "d@1":
|
||||
"integrity" "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA=="
|
||||
"resolved" "https://registry.npmjs.org/d/-/d-1.0.1.tgz"
|
||||
@@ -4323,7 +4365,7 @@
|
||||
"resolved" "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz"
|
||||
"version" "8.2.0"
|
||||
|
||||
"duplexer@^0.1.1":
|
||||
"duplexer@^0.1.1", "duplexer@^0.1.2":
|
||||
"integrity" "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg=="
|
||||
"resolved" "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz"
|
||||
"version" "0.1.2"
|
||||
@@ -5333,7 +5375,7 @@
|
||||
"resolved" "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz"
|
||||
"version" "1.0.0-beta.2"
|
||||
|
||||
"get-caller-file@^2.0.1":
|
||||
"get-caller-file@^2.0.1", "get-caller-file@^2.0.5":
|
||||
"integrity" "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
|
||||
"resolved" "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz"
|
||||
"version" "2.0.5"
|
||||
@@ -5504,6 +5546,13 @@
|
||||
"resolved" "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz"
|
||||
"version" "1.3.0"
|
||||
|
||||
"gzip-size@^6.0.0":
|
||||
"integrity" "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q=="
|
||||
"resolved" "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz"
|
||||
"version" "6.0.0"
|
||||
dependencies:
|
||||
"duplexer" "^0.1.2"
|
||||
|
||||
"gzip-size@5.1.1":
|
||||
"integrity" "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA=="
|
||||
"resolved" "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz"
|
||||
@@ -7427,6 +7476,11 @@
|
||||
dependencies:
|
||||
"mime-db" "1.47.0"
|
||||
|
||||
"mime@^2.3.1":
|
||||
"integrity" "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg=="
|
||||
"resolved" "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz"
|
||||
"version" "2.5.2"
|
||||
|
||||
"mime@^2.4.4":
|
||||
"integrity" "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg=="
|
||||
"resolved" "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz"
|
||||
@@ -7596,10 +7650,10 @@
|
||||
"dns-packet" "^1.3.1"
|
||||
"thunky" "^1.0.2"
|
||||
|
||||
"nanoid@^3.1.20":
|
||||
"integrity" "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw=="
|
||||
"resolved" "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz"
|
||||
"version" "3.1.20"
|
||||
"nanoid@^3.1.20", "nanoid@^3.1.22", "nanoid@3.1.22":
|
||||
"integrity" "sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ=="
|
||||
"resolved" "https://registry.npmjs.org/nanoid/-/nanoid-3.1.22.tgz"
|
||||
"version" "3.1.22"
|
||||
|
||||
"nanomatch@^1.2.9":
|
||||
"integrity" "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA=="
|
||||
@@ -7932,7 +7986,7 @@
|
||||
dependencies:
|
||||
"mimic-fn" "^2.1.0"
|
||||
|
||||
"open@^7.0.2":
|
||||
"open@^7.0.2", "open@^7.4.2":
|
||||
"integrity" "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q=="
|
||||
"resolved" "https://registry.npmjs.org/open/-/open-7.4.2.tgz"
|
||||
"version" "7.4.2"
|
||||
@@ -7940,6 +7994,11 @@
|
||||
"is-docker" "^2.0.0"
|
||||
"is-wsl" "^2.1.1"
|
||||
|
||||
"opener@^1.5.2":
|
||||
"integrity" "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A=="
|
||||
"resolved" "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz"
|
||||
"version" "1.5.2"
|
||||
|
||||
"opn@^5.5.0":
|
||||
"integrity" "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA=="
|
||||
"resolved" "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz"
|
||||
@@ -9424,14 +9483,14 @@
|
||||
"strip-ansi" "6.0.0"
|
||||
"text-table" "0.2.0"
|
||||
|
||||
"react-dom@^17.0.1", "react-dom@17.0.0":
|
||||
"integrity" "sha512-OGnFbxCjI2TMAZYMVxi4hqheJiN8rCEVVrL7XIGzCB6beNc4Am8M47HtkvxODZw9QgjmAPKpLba9FTu4fC1byA=="
|
||||
"resolved" "https://registry.npmjs.org/react-dom/-/react-dom-17.0.0.tgz"
|
||||
"version" "17.0.0"
|
||||
"react-dom@^17.0.1", "react-dom@17.0.1":
|
||||
"integrity" "sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug=="
|
||||
"resolved" "https://registry.npmjs.org/react-dom/-/react-dom-17.0.1.tgz"
|
||||
"version" "17.0.1"
|
||||
dependencies:
|
||||
"loose-envify" "^1.1.0"
|
||||
"object-assign" "^4.1.1"
|
||||
"scheduler" "^0.20.0"
|
||||
"scheduler" "^0.20.1"
|
||||
|
||||
"react-error-overlay@^6.0.9":
|
||||
"integrity" "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew=="
|
||||
@@ -9519,10 +9578,10 @@
|
||||
optionalDependencies:
|
||||
"fsevents" "^2.1.3"
|
||||
|
||||
"react@^17.0.1", "react@17.0.0":
|
||||
"integrity" "sha512-rG9bqS3LMuetoSUKHN8G3fMNuQOePKDThK6+2yXFWtoeTDLVNh/QCaxT+Jr+rNf4lwNXpx+atdn3Aa0oi8/6eQ=="
|
||||
"resolved" "https://registry.npmjs.org/react/-/react-17.0.0.tgz"
|
||||
"version" "17.0.0"
|
||||
"react@^17.0.1", "react@17.0.1":
|
||||
"integrity" "sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w=="
|
||||
"resolved" "https://registry.npmjs.org/react/-/react-17.0.1.tgz"
|
||||
"version" "17.0.1"
|
||||
dependencies:
|
||||
"loose-envify" "^1.1.0"
|
||||
"object-assign" "^4.1.1"
|
||||
@@ -10088,6 +10147,16 @@
|
||||
"serialize-javascript" "^4.0.0"
|
||||
"terser" "^4.6.2"
|
||||
|
||||
"rollup-plugin-visualizer@^5.4.1":
|
||||
"integrity" "sha512-mwrUIfOamkCw3dCtLvgnn/H0rvNSDA1RAe0sO9uHBpmdf86j/xOX/2yeCrVh2Ia/gCGLG846JB00MW0chq8CHQ=="
|
||||
"resolved" "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.4.1.tgz"
|
||||
"version" "5.4.1"
|
||||
dependencies:
|
||||
"nanoid" "^3.1.22"
|
||||
"open" "^7.4.2"
|
||||
"source-map" "^0.7.3"
|
||||
"yargs" "^16.2.0"
|
||||
|
||||
"rollup-pluginutils@^2.8.1", "rollup-pluginutils@^2.8.2":
|
||||
"integrity" "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ=="
|
||||
"resolved" "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz"
|
||||
@@ -10095,7 +10164,7 @@
|
||||
dependencies:
|
||||
"estree-walker" "^0.6.1"
|
||||
|
||||
"rollup@^1.20.0 || ^2.0.0", "rollup@^1.20.0||^2.0.0", "rollup@^2.14.0", "rollup@^2.22.0", "rollup@>=0.60.0 <3", "rollup@>=0.66.0 <3", "rollup@2.45.2":
|
||||
"rollup@^1.20.0 || ^2.0.0", "rollup@^1.20.0||^2.0.0", "rollup@^2.0.0", "rollup@^2.14.0", "rollup@^2.22.0", "rollup@>=0.60.0 <3", "rollup@>=0.66.0 <3", "rollup@2.45.2":
|
||||
"integrity" "sha512-kRRU7wXzFHUzBIv0GfoFFIN3m9oteY4uAsKllIpQDId5cfnkWF2J130l+27dzDju0E6MScKiV0ZM5Bw8m4blYQ=="
|
||||
"resolved" "https://registry.npmjs.org/rollup/-/rollup-2.45.2.tgz"
|
||||
"version" "2.45.2"
|
||||
@@ -10205,7 +10274,7 @@
|
||||
dependencies:
|
||||
"xmlchars" "^2.2.0"
|
||||
|
||||
"scheduler@^0.20.0":
|
||||
"scheduler@^0.20.1":
|
||||
"integrity" "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ=="
|
||||
"resolved" "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz"
|
||||
"version" "0.20.2"
|
||||
@@ -10458,6 +10527,15 @@
|
||||
dependencies:
|
||||
"is-arrayish" "^0.3.1"
|
||||
|
||||
"sirv@^1.0.7":
|
||||
"integrity" "sha512-SR36i3/LSWja7AJNRBz4fF/Xjpn7lQFI30tZ434dIy+bitLYSP+ZEenHg36i23V2SGEz+kqjksg0uOGZ5LPiqg=="
|
||||
"resolved" "https://registry.npmjs.org/sirv/-/sirv-1.0.11.tgz"
|
||||
"version" "1.0.11"
|
||||
dependencies:
|
||||
"@polka/url" "^1.0.0-next.9"
|
||||
"mime" "^2.3.1"
|
||||
"totalist" "^1.0.0"
|
||||
|
||||
"sisteransi@^1.0.5":
|
||||
"integrity" "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="
|
||||
"resolved" "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz"
|
||||
@@ -11202,6 +11280,11 @@
|
||||
"resolved" "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz"
|
||||
"version" "1.0.0"
|
||||
|
||||
"totalist@^1.0.0":
|
||||
"integrity" "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g=="
|
||||
"resolved" "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz"
|
||||
"version" "1.1.0"
|
||||
|
||||
"tough-cookie@^2.3.3":
|
||||
"integrity" "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g=="
|
||||
"resolved" "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz"
|
||||
@@ -11271,11 +11354,6 @@
|
||||
dependencies:
|
||||
"tslib" "^1.8.1"
|
||||
|
||||
"tty-browserify@0.0.0":
|
||||
"integrity" "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY="
|
||||
"resolved" "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz"
|
||||
"version" "0.0.0"
|
||||
|
||||
"tunnel-agent@^0.6.0":
|
||||
"integrity" "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0="
|
||||
"resolved" "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz"
|
||||
@@ -11367,6 +11445,11 @@
|
||||
"resolved" "https://registry.npmjs.org/typescript/-/typescript-4.1.5.tgz"
|
||||
"version" "4.1.5"
|
||||
|
||||
"tty-browserify@0.0.0":
|
||||
"integrity" "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY="
|
||||
"resolved" "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz"
|
||||
"version" "0.0.0"
|
||||
|
||||
"uglify-js@^2.7.4":
|
||||
"integrity" "sha1-KcVzMUgFe7Th913zW3qcty5qWd0="
|
||||
"resolved" "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz"
|
||||
@@ -11690,6 +11773,21 @@
|
||||
"resolved" "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz"
|
||||
"version" "6.1.0"
|
||||
|
||||
"webpack-bundle-analyzer@^4.4.1":
|
||||
"integrity" "sha512-j5m7WgytCkiVBoOGavzNokBOqxe6Mma13X1asfVYtKWM3wxBiRRu1u1iG0Iol5+qp9WgyhkMmBAcvjEfJ2bdDw=="
|
||||
"resolved" "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.4.1.tgz"
|
||||
"version" "4.4.1"
|
||||
dependencies:
|
||||
"acorn" "^8.0.4"
|
||||
"acorn-walk" "^8.0.0"
|
||||
"chalk" "^4.1.0"
|
||||
"commander" "^6.2.0"
|
||||
"gzip-size" "^6.0.0"
|
||||
"lodash" "^4.17.20"
|
||||
"opener" "^1.5.2"
|
||||
"sirv" "^1.0.7"
|
||||
"ws" "^7.3.1"
|
||||
|
||||
"webpack-dev-middleware@^3.7.2":
|
||||
"integrity" "sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ=="
|
||||
"resolved" "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz"
|
||||
@@ -12059,6 +12157,15 @@
|
||||
"string-width" "^4.1.0"
|
||||
"strip-ansi" "^6.0.0"
|
||||
|
||||
"wrap-ansi@^7.0.0":
|
||||
"integrity" "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="
|
||||
"resolved" "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
|
||||
"version" "7.0.0"
|
||||
dependencies:
|
||||
"ansi-styles" "^4.0.0"
|
||||
"string-width" "^4.1.0"
|
||||
"strip-ansi" "^6.0.0"
|
||||
|
||||
"wrappy@1":
|
||||
"integrity" "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
"resolved" "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
|
||||
@@ -12081,7 +12188,7 @@
|
||||
dependencies:
|
||||
"async-limiter" "~1.0.0"
|
||||
|
||||
"ws@^7.4.4":
|
||||
"ws@^7.3.1", "ws@^7.4.4":
|
||||
"integrity" "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw=="
|
||||
"resolved" "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz"
|
||||
"version" "7.4.4"
|
||||
@@ -12106,6 +12213,11 @@
|
||||
"resolved" "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz"
|
||||
"version" "4.0.3"
|
||||
|
||||
"y18n@^5.0.5":
|
||||
"integrity" "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="
|
||||
"resolved" "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz"
|
||||
"version" "5.0.8"
|
||||
|
||||
"yallist@^3.0.2":
|
||||
"integrity" "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
|
||||
"resolved" "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz"
|
||||
@@ -12147,6 +12259,11 @@
|
||||
"camelcase" "^5.0.0"
|
||||
"decamelize" "^1.2.0"
|
||||
|
||||
"yargs-parser@^20.2.2":
|
||||
"integrity" "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw=="
|
||||
"resolved" "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz"
|
||||
"version" "20.2.7"
|
||||
|
||||
"yargs@^13.3.2":
|
||||
"integrity" "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw=="
|
||||
"resolved" "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz"
|
||||
@@ -12180,6 +12297,19 @@
|
||||
"y18n" "^4.0.0"
|
||||
"yargs-parser" "^18.1.2"
|
||||
|
||||
"yargs@^16.2.0":
|
||||
"integrity" "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="
|
||||
"resolved" "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz"
|
||||
"version" "16.2.0"
|
||||
dependencies:
|
||||
"cliui" "^7.0.2"
|
||||
"escalade" "^3.1.1"
|
||||
"get-caller-file" "^2.0.5"
|
||||
"require-directory" "^2.1.1"
|
||||
"string-width" "^4.2.0"
|
||||
"y18n" "^5.0.5"
|
||||
"yargs-parser" "^20.2.2"
|
||||
|
||||
"yargs@~3.10.0":
|
||||
"integrity" "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E="
|
||||
"resolved" "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz"
|
||||
|
||||
Reference in New Issue
Block a user