Compare commits

...

97 Commits

Author SHA1 Message Date
Zsolt Viczian
d15278e70e tooltip edit 2021-05-06 23:23:59 +02:00
Zsolt Viczian
4be1ff89fe Force-Save tooltip 2021-05-06 23:20:42 +02:00
Zsolt Viczian
c962168c52 solved #46, #45, #44, #41, #40, #38, #37 2021-05-06 23:15:07 +02:00
Zsolt Viczian
660f6e03b1 trying to resolve issue with slidingPanes 2021-05-06 22:44:57 +02:00
Zsolt Viczian
a26e565d04 TransclusionIndex working 2021-05-06 20:56:48 +02:00
Zsolt Viczian
0debaace4e parser 2021-05-06 09:16:16 +02:00
Zsolt Viczian
126086f9f1 Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2021-05-06 06:22:14 +02:00
Zsolt Viczian
baf2cdd5d8 save on close 2021-05-06 06:21:42 +02:00
zsviczian
793302a1f5 Update README.md 2021-05-05 22:28:33 +02:00
zsviczian
0d361340c1 Update AutomateHowTo.md 2021-05-05 20:54:00 +02:00
zsviczian
e192da8668 Update AutomateHowTo.md 2021-05-05 20:53:24 +02:00
Zsolt Viczian
2fef747a75 Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2021-05-05 20:18:03 +02:00
Zsolt Viczian
96bfbf6fca 1.0.8-test2 2021-05-05 20:17:48 +02:00
zsviczian
8037ac5bd9 Update AutomateHowTo.md 2021-05-05 20:15:57 +02:00
zsviczian
fb15f11284 Update AutomateHowTo.md 2021-05-05 20:13:14 +02:00
zsviczian
7252380ab1 Update AutomateHowTo.md 2021-05-05 20:12:01 +02:00
zsviczian
8d2d3462ed Update AutomateHowTo.md 2021-05-03 07:12:00 +02:00
zsviczian
eb45452c25 Update AutomateHowTo.md 2021-05-03 07:10:14 +02:00
zsviczian
b17cc6ea4d Update AutomateHowTo.md 2021-05-02 23:30:11 +02:00
zsviczian
85944dc10c Update AutomateHowTo.md 2021-05-02 23:28:22 +02:00
zsviczian
c5c8ba3e9d Update AutomateHowTo.md 2021-05-02 23:22:53 +02:00
zsviczian
433d5ee042 Update AutomateHowTo.md 2021-05-02 23:20:08 +02:00
zsviczian
e1177e84e7 Update AutomateHowTo.md 2021-05-02 23:19:35 +02:00
zsviczian
af2aa4d5a6 Update AutomateHowTo.md 2021-05-02 21:51:24 +02:00
zsviczian
f936fbbed5 Update AutomateHowTo.md 2021-05-02 21:50:49 +02:00
Zsolt Viczian
e7860db0f0 fix version 2021-05-02 21:36:05 +02:00
Zsolt Viczian
0ec6acbeed 1.0.8 ExcalidrawAutomate 2021-05-02 21:30:41 +02:00
zsviczian
1da434c550 Update README.md 2021-04-29 21:21:48 +02:00
zsviczian
e10ebf94c6 Update README.md 2021-04-29 20:25:28 +02:00
zsviczian
833c2588c1 Update README.md 2021-04-29 20:20:40 +02:00
zsviczian
4454598786 Update README.md 2021-04-29 20:17:56 +02:00
zsviczian
5a5eb3964b Update README.md 2021-04-29 20:06:51 +02:00
zsviczian
bb961c517b Update README.md 2021-04-29 13:47:58 +02:00
zsviczian
5be0152583 Update README.md 2021-04-29 13:47:22 +02:00
zsviczian
e2bae8e80d Update README.md 2021-04-29 09:32:53 +02:00
zsviczian
4e7fcf4360 Update README.md 2021-04-29 07:43:02 +02:00
zsviczian
21374f8eb6 Update README.md 2021-04-29 07:39:24 +02:00
zsviczian
bfd3faa79d Update README.md 2021-04-29 07:39:02 +02:00
zsviczian
cd0d7f192d Update README.md 2021-04-29 07:34:47 +02:00
Zsolt Viczian
f0ef04ed3e 1.0.7 - tweak to styles 2021-04-29 07:26:58 +02:00
zsviczian
8760f72a13 Update README.md 2021-04-28 22:59:17 +02:00
zsviczian
09602e142c Update README.md 2021-04-28 22:47:05 +02:00
zsviczian
c7500e9ee7 Update README.md 2021-04-28 22:46:27 +02:00
zsviczian
92d3363b5b Update README.md 2021-04-28 22:04:43 +02:00
zsviczian
30682e1b40 Update README.md 2021-04-28 22:04:29 +02:00
zsviczian
d89431bbde Update README.md 2021-04-28 21:51:57 +02:00
Zsolt Viczian
1c707db3a7 1.0.6 2021-04-28 21:38:22 +02:00
zsviczian
a56fda222d Update .gitignore 2021-04-28 16:08:21 +02:00
zsviczian
9fcbe5b7d7 Delete data.json 2021-04-28 16:05:25 +02:00
zsviczian
3c6dbcc8bb Delete data-ZsoltServer.json 2021-04-28 16:05:09 +02:00
zsviczian
25e2f3d8bb Update README.md 2021-04-28 15:58:45 +02:00
zsviczian
1c35e86118 Update README.md 2021-04-28 15:57:34 +02:00
zsviczian
61b716d8f6 Update manifest.json 2021-04-28 15:46:27 +02:00
zsviczian
2a0404fe18 Update versions.json 2021-04-28 15:46:04 +02:00
Zsolt Viczian
4f4a80b317 revert readme commit 2021-04-28 15:43:59 +02:00
Zsolt Viczian
0259dc579f Revert "left align images in readme.md"
This reverts commit fe84c607a6.
2021-04-28 15:43:30 +02:00
zsviczian
fe84c607a6 left align images in readme.md 2021-04-28 15:36:33 +02:00
zsviczian
b8178ac07c Update README.md 2021-04-28 13:52:35 +02:00
Zsolt Viczian
a65c6afed2 correct manifest and versions 2021-04-28 06:25:20 +02:00
Zsolt Viczian
6e207350d6 1.0.6-test 2021-04-27 23:10:29 +02:00
Zsolt Viczian
d64c00f2dd added sync SVP with Excalidraw file 2021-04-26 23:02:22 +02:00
Zsolt Viczian
25a998fc01 1.0.5 2021-04-26 06:32:40 +02:00
Zsolt Viczian
a212136323 removed unused comments and empty lines 2021-04-25 17:07:47 +02:00
Zsolt Viczian
e1a92695d5 1.0.5 2021-04-25 16:51:36 +02:00
Zsolt Viczian
370e35182b Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2021-04-25 09:34:34 +02:00
Zsolt Viczian
634bbc2165 Settings to include Excalidraw/Template.excalidraw 2021-04-25 09:34:06 +02:00
zsviczian
a2dd13049e Update README.md 2021-04-25 09:04:35 +02:00
Zsolt Viczian
a64c6e5335 cleand up unused imports 2021-04-24 22:56:36 +02:00
Zsolt Viczian
d57a28c36b 1.0.5-test2 2021-04-24 22:43:43 +02:00
Zsolt Viczian
2d32b4b71a resolved chart pos, and theme on load drawing 2021-04-24 22:18:23 +02:00
Zsolt Viczian
5be455d368 save stencil library to data.json 2021-04-24 10:50:00 +02:00
Zsolt Viczian
c4acf24bca test release with excalidraw 0.7.0-fixtext library 2021-04-24 06:40:27 +02:00
Zsolt Viczian
a13c8e0127 working with libraryItems in ex0.7.0-autoprefix1 2021-04-23 06:43:00 +02:00
Zsolt Viczian
b048dd0ee7 added rollup-plugin-visualizer 2021-04-22 11:25:21 +02:00
Zsolt Viczian
f6a832b2bc updated with Excalidraw 0.7.0-libs2 2021-04-22 10:25:48 +02:00
Zsolt Viczian
ec246cbd03 1.0.2 manifest 2021-04-21 09:39:53 +02:00
Zsolt Viczian
cbab54e848 1.0.2 2021-04-21 09:27:34 +02:00
Zsolt Viczian
39085bc962 Deleted utils.ts 2021-04-21 07:54:16 +02:00
Zsolt Viczian
87dd8b0415 1.0.1 2021-04-21 07:47:42 +02:00
Zsolt Viczian
f8b8dffb94 Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2021-04-20 21:54:01 +02:00
Zsolt Viczian
f73bd97b1d 1.0.0 2021-04-20 21:53:49 +02:00
zsviczian
c8ac7be912 Update README.md 2021-04-20 21:49:11 +02:00
zsviczian
6887d0bde8 Update README.md 2021-04-20 21:48:53 +02:00
Zsolt Viczian
721e8514d2 1.0.0 2021-04-20 21:42:03 +02:00
zsviczian
8ed2d2b3a8 Update README.md 2021-04-20 21:40:17 +02:00
zsviczian
ad7e07a253 Update README.md 2021-04-20 21:24:21 +02:00
zsviczian
f3b29aa9b8 Update README.md 2021-04-20 21:09:46 +02:00
zsviczian
9b4f4917d4 Update README.md 2021-04-20 21:09:28 +02:00
zsviczian
44f67cd3f3 Update README.md 2021-04-20 20:53:50 +02:00
Zsolt Viczian
a711987163 v0.0.4 mvp 2021-04-20 20:41:07 +02:00
Zsolt Viczian
38ec3634c6 updated SVG 2021-04-20 14:52:42 +02:00
Zsolt Viczian
adc9c17d28 removed old view.ts file 2021-04-20 14:02:19 +02:00
Zsolt Viczian
12b64710a2 Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2021-04-20 13:58:25 +02:00
Zsolt Viczian
f2012de41c MVP 2021-04-20 13:57:53 +02:00
zsviczian
f873ac3164 Update README.md 2021-04-20 13:56:02 +02:00
zsviczian
ab568abf5a Update README.md 2021-04-20 13:15:02 +02:00
zsviczian
274b1939f8 Update README.md 2021-04-20 12:03:46 +02:00
27 changed files with 2116 additions and 449 deletions

3
.babelrc Normal file
View File

@@ -0,0 +1,3 @@
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}

5
.gitignore vendored
View File

@@ -8,4 +8,7 @@ package-lock.json
# build
main.js
*.js.map
*.js.map
stats.html
hot-reload.bat
data.json

429
AutomateHowTo.md Normal file
View File

@@ -0,0 +1,429 @@
# 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 `await ea.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);
ea.addText(-100,70,"Left to right");
ea.addArrow([[-100,100],[100,100]]);
ea.style.strokeColor = "red";
ea.addText(100,-30,"top to bottom",{width:200,textAligh:"center"});
ea.addArrow([[200,0],[200,200]]);
await ea.create();
%>
```
The script will generate the following drawing:
![FristDemo](https://user-images.githubusercontent.com/14358394/116825643-6e5a8b00-ab90-11eb-9e3a-37c524620d0d.png)
## Attributes and functions at a glance
Here's the interface implemented by ExcalidrawAutomate:
```javascript
ExcalidrawAutomate: {
style: {
strokeColor: string;
backgroundColor: string;
angle: number;
fillStyle: FillStyle;
strokeWidth: number;
storkeStyle: StrokeStyle;
roughness: number;
opacity: number;
strokeSharpness: StrokeSharpness;
fontFamily: FontFamily;
fontSize: number;
textAlign: string;
verticalAlign: string;
startArrowHead: string;
endArrowHead: string;
}
canvas: {theme: string, viewBackgroundColor: string};
setFillStyle: Function;
setStrokeStyle: Function;
setStrokeSharpness: Function;
setFontFamily: Function;
setTheme: Function;
addRect: Function;
addDiamond: Function;
addEllipse: Function;
addText: Function;
addLine: Function;
addArrow: Function;
connectObjects: Function;
addToGroup: Function;
toClipboard: Function;
create: Function;
createPNG: Function;
createSVG: Function;
clear: Function;
reset: Function;
};
```
## 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. [CSS Legal Color Values](https://www.w3schools.com/cssref/css_colors_legal.asp)
Allowed values are [HTML color names](https://www.w3schools.com/colors/colors_names.asp), hexadecimal RGB strings, or e.g. `#FF0000` for red.
### backgroundColor
String. This is the fill color of an object. [CSS Legal Color Values](https://www.w3schools.com/cssref/css_colors_legal.asp)
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.
### startArrowHead, endArrowHead
String. Valid values are "arrow", "bar", "dot", and "none". Specifies the beginning and ending of an arrow.
This is relavant when using the `addArrow()` and the `connectObjects()` functions.
## 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. [CSS Legal Color Values](https://www.w3schools.com/cssref/css_colors_legal.asp)
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. X values increase left to right, Y values increase top to bottom.
![coordinates](https://user-images.githubusercontent.com/14358394/116825632-6569b980-ab90-11eb-827b-ada598e91e46.png)
### addRect(), addDiamond(), addEllipse()
```typescript
addRect(topX:number, topY:number, width:number, height:number):string
addDiamond(topX:number, topY:number, width:number, height:number):string
addEllipse(topX:number, topY:number, width:number, height:number):string
```
Returns the `id` of the object. The `id` is required when connecting objects with lines. See later.
### addText
```typescript
addText(topX:number, topY:number, text:string, formatting?:{width:number, height:number,textAlign: string, verticalAlign:string, box: boolean, boxPadding: number}):string
```
Adds text to the drawing.
Formatting parameters are optional:
- 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. e.g.: `{width:500, textAlign:"center"}`
- If you want to add a box around the text, set `{box:true}`
Returns the `id` of the object. The `id` is required when connecting objects with lines. See later. If `{box:true}` then returns the id of the enclosing box.
### 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]],formatting?:{startArrowHead:string,endArrowHead:string,startObjectId:string,endObjectId: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 element `style.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". e.g. `{startArrowHead: "dot", endArrowHead: "arrow"}`
`startObjectId` and `endObjectId` are the object id's of connected objects. I recommend using `connectObjects` instead calling addArrow() for the purpose of connecting objects.
### connectObjects()
```typescript
declare type ConnectionPoint = "top"|"bottom"|"left"|"right";
connectObjects(objectA: string, connectionA: ConnectionPoint, objectB: string, connectionB: ConnectionPoint, formatting?:{numberOfPoints: number,startArrowHead:string,endArrowHead:string, padding: number}):void
```
Connects two objects with an arrow.
`objectA` and `objectB` are strings. These are the ids of the objects to connect. These IDs are returned by addRect(), addDiamond(), addEllipse() and addText() when creating those 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 zero, meaning there will be no breakpoint in 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` work as described for `addArrow()` above.
### addToGroup()
```typescript
addToGroup(objectIds:[]):void
```
Groups objects listed in `objectIds`.
## 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.
### toClipboard()
```typescript
async toClipboard(templatePath?:string)
```
Places the generated drawing to the clipboard. Useful when you don't want to create a new drawing, but want to paste additional items onto an exising drawing.
### create()
```typescript
async create(params?:{filename: string, foldername:string, templatePath:string, onNewPane: boolean})
```
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.
Example:
```javascript
create({filename:"my drawing", foldername:"myfolder/subfolder/", templatePath: "Excalidraw/template.excalidraw", onNewPane: true});
```
### createSVG()
```typescript
async createSVG(templatePath?:string)
```
Returns an HTML SVGSVGElement containing the generated drawing.
### createPNG()
```typescript
async createPNG(templatePath?:string)
```
Returns a blob containing a PNG image of the generated drawing.
## Examples
### Connect objects
```javascript
<%*
const ea = ExcalidrawAutomate;
ea.reset();
ea.addText(-130,-100,"Connecting two objects");
const a = ea.addRect(-100,-100,100,100);
const b = ea.addEllipse(200,200,100,100);
ea.connectObjects(a,"bottom",b,"left",{numberOfPoints: 2}); //see how the line breaks differently when moving objects around
ea.style.strokeColor = "red";
ea.connectObjects(a,"right",b,"top",1);
await ea.create();
%>
```
### 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);
ea.addText(-100,70,"Left to right");
ea.addArrow([[-100,100],[100,100]]);
ea.style.strokeColor = "red";
await ea.addText(100,-30,"top to bottom",{width:200,textAlign:"center"});
ea.addArrow([[200,0],[200,200]]);
await ea.create({filename:"My Drawing",foldername:"myfolder/fordemo/",templatePath:"Excalidraw/Template2.excalidraw",onNewPane:true});
%>
```
### Generating a simple mindmap from a text outline
This is a slightly more elaborate example. This will generate an a mindmap from a tabulated outline.
![Drawing 2021-05-05 20 52 34](https://user-images.githubusercontent.com/14358394/117194124-00a69d00-ade4-11eb-8b75-5e18a9cbc3cd.png)
Example input:
```
- Test 1
- Test 1.1
- Test 2
- Test 2.1
- Test 2.2
- Test 2.2.1
- Test 2.2.2
- Test 2.2.3
- Test 2.2.3.1
- Test 3
- Test 3.1
```
The script:
```javascript
<%*
const IDX = Object.freeze({"depth":0, "text":1, "parent":2, "size":3, "children": 4, "objectId":5});
//check if an editor is the active view
const editor = this.app.workspace.activeLeaf?.view?.editor;
if(!editor) return;
//initialize the tree with the title of the document as the first element
let tree = [[0,this.app.workspace.activeLeaf?.view?.getDisplayText(),-1,0,[],0]];
const linecount = editor.lineCount();
//helper function, use regex to calculate indentation depth, and to get line text
function getLineProps (i) {
props = editor.getLine(i).match(/^(\t*)-\s+(.*)/);
return [props[1].length+1, props[2]];
}
//a vector that will hold last valid parent for each depth
let parents = [0];
//load outline into tree
for(i=0;i<linecount;i++) {
[depth,text] = getLineProps(i);
if(depth>parents.length) parents.push(i+1);
else parents[depth] = i+1;
tree.push([depth,text,parents[depth-1],1,[]]);
tree[parents[depth-1]][IDX.children].push(i+1);
}
//recursive function to crawl the tree and identify height aka. size of each node
function crawlTree(i) {
if(i>linecount) return 0;
size = 0;
if((i+1<=linecount && tree[i+1][IDX.depth] <= tree[i][IDX.depth])|| i == linecount) { //I am a leaf
tree[i][IDX.size] = 1;
return 1;
}
tree[i][IDX.children].forEach((node)=>{
size += crawlTree(node);
});
tree[i][IDX.size] = size;
return size;
}
crawlTree(0);
//Build the mindmap in Excalidraw
const width = 300;
const height = 100;
const ea = ExcalidrawAutomate;
ea.reset();
//stores position offset of branch/leaf in height units
offsets = [0];
for(i=0;i<=linecount;i++) {
depth = tree[i][IDX.depth];
if (depth == 1) ea.style.strokeColor = '#'+(Math.random()*0xFFFFFF<<0).toString(16);
tree[i][IDX.objectId] = ea.addText(depth*width,((tree[i][IDX.size]/2)+offsets[depth])*height,tree[i][IDX.text],{box:true});
//set child offset equal to parent offset
if((depth+1)>offsets.length) offsets.push(offsets[depth]);
else offsets[depth+1] = offsets[depth];
offsets[depth] += tree[i][IDX.size];
if(tree[i][IDX.parent]!=-1) {
ea.connectObjects(tree[tree[i][IDX.parent]][IDX.objectId],"right",tree[i][IDX.objectId],"left",{startArrowHead: 'dot'});
}
}
await ea.create({onNewPane: true});
%>
```

112
README.md
View File

@@ -1,21 +1,103 @@
## Obsidian-Excalidraw
# 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).
### Key features
- Use the Excalidraw command in the Command palette to open an existing drawing or to create a new one. You create a new one by typing its name and hitting enter.
- Set up a default folder for your drawings in Settings.
- You can also set up a Template, by creating a drawing, customizing it the way you like, and specifying the file as the template in settings.
- There is a known bug when inserting images from the library, in some cases these images will be placed out of view, and you'll need to select scroll to view at the bottom of the screen. The bug was reproduced by the Excalidraw dev team, so I expect in the next update of Excalidraw it will be fixed.
- Translusion of drawings into markdown documents is not yet working.
- Drawings are saved to your vault as a file with the extension .excalidraw
- If you want to see these files in your vault you need to enable the following sestting
![image](https://user-images.githubusercontent.com/14358394/115161034-a5627400-a09b-11eb-83e7-b824e9a987de.png)
**See details of the 1.0.6 and 1.0.7 release including a short video, further below**
### Excalidraw in Obsidian
![image](https://user-images.githubusercontent.com/14358394/115124849-c5733400-9fc4-11eb-8e31-8f1f2e7379ce.png)
![image](https://user-images.githubusercontent.com/14358394/115983515-d06c2c80-a5a1-11eb-8d12-c7df91d18107.png)
## 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.
## How to?
Part 1: Intro to Obsidian-Excalidraw - Start a new drawing (3:12)
[![Part 1: Intro to Obsidian-Excalidraw - Start a new drawing](https://user-images.githubusercontent.com/14358394/115983840-05797e80-a5a4-11eb-93cd-bae4b1973f72.jpg)](https://youtu.be/i-hIfY-Ecjg)
Part 2: Intro to Obsidian-Excalidraw - Basic features (6:06)
[![Part 2: Intro to Obsidian-Excalidraw - Basic features](https://user-images.githubusercontent.com/14358394/115983902-699c4280-a5a4-11eb-973d-2ba1bd7ac2db.jpg)](https://youtu.be/-dk7pvdl-H0)
Part 3: Intro to Obsidian-Excalidraw - Advanced features (3:26)
[![Part 3: Intro to Obsidian-Excalidraw - Advanced features](https://user-images.githubusercontent.com/14358394/115983916-7de03f80-a5a4-11eb-8f36-4ad516ef9e80.jpg)](https://youtu.be/2cKlEwo8WU0)
Part 4: Intro to Obsidian-Excalidraw - Setting up a template (1:45)
[![Part 4: Intro to Obsidian-Excalidraw - Setting up a template](https://user-images.githubusercontent.com/14358394/115983929-92bcd300-a5a4-11eb-9d4f-03e5cb9e3ebf.jpg)](https://youtu.be/oNPYZEpmuJ8)
Part 5: Intro to Obsidian-Excalidraw - Stencil Library (3:16)
[![Part 5: Intro to Obsidian-Excalidraw - Stencil Library](https://user-images.githubusercontent.com/14358394/115983944-a8ca9380-a5a4-11eb-8a69-e74ae00d95be.jpg)](https://youtu.be/rLx-9FvlzgI)
Part 6: Intro to Obsidian-Excalidraw: Embedding drawings (2:08)
[![Part 6: Intro to Obsidian-Excalidraw: Embedding drawings](https://user-images.githubusercontent.com/14358394/115983954-bbdd6380-a5a4-11eb-9243-f0151451afcd.jpg)](https://youtu.be/JQeJ-Hh-xAI)
## 1.0.6 and 1.0.7 update
[![1.0.6 Update](https://user-images.githubusercontent.com/14358394/116312909-58725200-a7ad-11eb-89b9-c67cb48ffebb.jpg)](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 bug registered with Excalidraw
![2021-04-18 08-04-28](https://user-images.githubusercontent.com/14358394/115161065-d773d600-a09b-11eb-9e49-783e77fd2b78.gif)
## 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. Obsidian mobile 0.18 has resolved this issue.
- 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)
https://user-images.githubusercontent.com/14358394/115161352-84028780-a09d-11eb-90ee-7d4dad82ec98.mp4
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)

View File

@@ -1 +0,0 @@
{"openFile":"Blog/attachements/security through obscurity.excalidraw","settings":{"folder":"excalidraw","templateFilePath":"excalidraw/Template.excalidraw"}}

View File

@@ -1 +0,0 @@
{"folder":"excalidraw","templateFilePath":"","openFile":"excalidraw/new file.excalidraw","settings":{"folder":"excalidraw","templateFilePath":""}}

10
dist/manifest.json vendored
View File

@@ -1,10 +0,0 @@
{
"id": "obsidian-excalidraw-plugin",
"name": "Excalidraw",
"version": "0.1.0",
"minAppVersion": "0.0.2",
"description": "An obsidian plugin to edit and view Excalidraw drawings",
"author": "Zsolt Viczian",
"authorUrl": "https://zsolt.blog",
"isDesktopOnly": false
}

13
dist/styles.css vendored
View File

@@ -1,13 +0,0 @@
.App {
font-family: sans-serif;
text-align: center;
}
.excalidraw-wrapper {
height: 100%;
margin: 0px;
}
.context-menu-option__shortcut {
background-color: transparent !important;
}

3
esbuild.config.json Normal file
View File

@@ -0,0 +1,3 @@
{
"minify": true
}

BIN
images/FristDemo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
images/coordinates.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -1,10 +1,10 @@
{
"id": "obsidian-excalidraw-plugin",
"name": "Excalidraw",
"version": "0.1.0",
"minAppVersion": "0.0.2",
"description": "An obsidian plugin to edit and view Excalidraw drawings",
"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
}
}

View File

@@ -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"
}
}

View File

@@ -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(),
]
};

470
src/ExcalidrawTemplate.ts Normal file
View File

@@ -0,0 +1,470 @@
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"
import ExcalidrawView from "./ExcalidrawView"
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;
addRect: Function;
addDiamond: Function;
addEllipse: Function;
addText: Function;
addLine: Function;
addArrow: Function;
connectObjects: Function;
addToGroup: Function;
toClipboard: Function;
create: Function;
createPNG: Function;
createSVG: Function;
clear: Function;
reset: Function;
};
}
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";
}
},
addToGroup(objectIds:[]):void {
const id = nanoid();
objectIds.forEach((objectId)=>{
this.elementsDict[objectId]?.groupIds?.push(id);
});
},
async toClipboard(templatePath?:string) {
let elements = templatePath ? (await getTemplate(templatePath)).elements : [];
for (let i=0;i<this.elementIds.length;i++) {
elements.push(this.elementsDict[this.elementIds[i]]);
}
navigator.clipboard.writeText(
JSON.stringify({
"type":"excalidraw/clipboard",
"elements": elements,
}));
},
async create(params?:{filename: string, foldername:string, templatePath:string, onNewPane: boolean}) {
let elements = params?.templatePath ? (await getTemplate(params.templatePath)).elements : [];
for (let i=0;i<this.elementIds.length;i++) {
elements.push(this.elementsDict[this.elementIds[i]]);
}
plugin.createDrawing(
params?.filename ? params.filename + '.excalidraw' : this.plugin.getNextDefaultFilename(),
params?.onNewPane ? params.onNewPane : false,
params?.foldername ? params.foldername : this.plugin.settings.folder,
JSON.stringify({
"type": "excalidraw",
"version": 2,
"source": "https://excalidraw.com",
"elements": elements,
"appState": {
"theme": this.canvas.theme,
"viewBackgroundColor": this.canvas.viewBackgroundColor
}
})
);
},
async createSVG(templatePath?:string) {
let elements = templatePath ? (await getTemplate(templatePath)).elements : [];
for (let i=0;i<this.elementIds.length;i++) {
elements.push(this.elementsDict[this.elementIds[i]]);
}
return ExcalidrawView.getSVG(
JSON.stringify({
"type": "excalidraw",
"version": 2,
"source": "https://excalidraw.com",
"elements": elements,
"appState": {
"theme": this.canvas.theme,
"viewBackgroundColor": this.canvas.viewBackgroundColor
}
}),
{
withBackground: plugin.settings.exportWithBackground,
withTheme: plugin.settings.exportWithTheme
}
)
},
async createPNG(templatePath?:string) {
let elements = templatePath ? (await getTemplate(templatePath)).elements : [];
for (let i=0;i<this.elementIds.length;i++) {
elements.push(this.elementsDict[this.elementIds[i]]);
}
return ExcalidrawView.getPNG(
JSON.stringify({
"type": "excalidraw",
"version": 2,
"source": "https://excalidraw.com",
"elements": elements,
"appState": {
"theme": this.canvas.theme,
"viewBackgroundColor": this.canvas.viewBackgroundColor
}
}),
{
withBackground: plugin.settings.exportWithBackground,
withTheme: plugin.settings.exportWithTheme
}
)
},
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;
},
addText(topX:number, topY:number, text:string, formatting?:{width:number, height:number,textAlign: string, verticalAlign:string, box: boolean, boxPadding: number}):string {
const id = nanoid();
const {w, h, baseline} = measureText(text);
const width = formatting?.width ? formatting.width : w;
const height = formatting?.height ? formatting.height : h;
this.elementIds.push(id);
this.elementsDict[id] = {
text: text,
fontSize: window.ExcalidrawAutomate.style.fontSize,
fontFamily: window.ExcalidrawAutomate.style.fontFamily,
textAlign: formatting?.textAlign ? formatting.textAlign : window.ExcalidrawAutomate.style.textAlign,
verticalAlign: formatting?.verticalAlign ? formatting.verticalAlign : window.ExcalidrawAutomate.style.verticalAlign,
baseline: baseline,
... boxedElement(id,"text",topX,topY,width,height)
};
if(formatting?.box) {
const boxPadding = formatting?.boxPadding ? formatting.boxPadding : 10;
const boxId = this.addRect(topX-boxPadding,topY-boxPadding,width+2*boxPadding,height+2*boxPadding);
this.addToGroup([id,boxId])
return boxId;
}
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]],formatting?:{startArrowHead:string,endArrowHead:string,startObjectId:string,endObjectId:string}):void {
const box = getLineBox(points);
const id = nanoid();
this.elementIds.push(id);
this.elementsDict[id] = {
points: normalizeLinePoints(points),
lastCommittedPoint: null,
startBinding: {elementId:formatting?.startObjectId,focus:0.1,gap:4},
endBinding: {elementId:formatting?.endObjectId,focus:0.1,gap:4},
startArrowhead: formatting?.startArrowHead ? formatting.startArrowHead : this.style.startArrowHead,
endArrowhead: formatting?.endArrowHead ? formatting.endArrowHead : this.style.endArrowHead,
... boxedElement(id,"arrow",box.x,box.y,box.w,box.h)
};
if(formatting?.startObjectId) this.elementsDict[formatting.startObjectId].boundElementIds.push(id);
if(formatting?.endObjectId) this.elementsDict[formatting.endObjectId].boundElementIds.push(id);
},
connectObjects(objectA: string, connectionA: ConnectionPoint, objectB: string, connectionB: ConnectionPoint, formatting?:{numberOfPoints: number,startArrowHead:string,endArrowHead:string, padding: number}):void {
if(!(this.elementsDict[objectA] && this.elementsDict[objectB])) {
return;
}
const padding = formatting?.padding ? formatting.padding : 10;
const numberOfPoints = formatting?.numberOfPoints ? formatting.numberOfPoints : 0;
const getSidePoints = (side:string, el:any) => {
switch(side) {
case "bottom":
return [((el.x) + (el.x+el.width))/2, el.y+el.height+padding];
case "left":
return [el.x-padding, ((el.y) + (el.y+el.height))/2];
case "right":
return [el.x+el.width+padding, ((el.y) + (el.y+el.height))/2];
default: //"top"
return [((el.x) + (el.x+el.width))/2, el.y-padding];
}
}
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: formatting?.startArrowHead,
endArrowHead: formatting?.endArrowHead,
startObjectId: objectA,
endObjectId: 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";
},
};
initFonts();
}
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]]) {
return {
x: points[0][0],
y: points[0][1],
w: Math.abs(points[points.length-1][0]-points[0][0]),
h: Math.abs(points[points.length-1][1]-points[0][1])
}
}
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 initFonts () {
for (let i=0;i<3;i++) {
await (document as any).fonts.load(
window.ExcalidrawAutomate.style.fontSize.toString()+'px ' +
getFontFamily(window.ExcalidrawAutomate.style.fontFamily)
);
}
}
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: {},
}
}

View File

@@ -1,66 +1,194 @@
import { TextFileView, WorkspaceLeaf } from "obsidian";
import {
TextFileView,
WorkspaceLeaf,
normalizePath,
TFile,
WorkspaceItem
} from "obsidian";
import * as React from "react";
import * as ReactDOM from "react-dom";
import Excalidraw from "@excalidraw/excalidraw";
import Excalidraw, {exportToSvg} from "@excalidraw/excalidraw";
import { ExcalidrawElement } from "@excalidraw/excalidraw/types/element/types";
import { AppState } from "@excalidraw/excalidraw/types/types";
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';
interface WorkspaceItemExt extends WorkspaceItem {
containerEl: HTMLElement;
}
export interface ExportSettings {
withBackground: boolean,
withTheme: boolean
}
export default class ExcalidrawView extends TextFileView {
private getScene: any;
private getScene: Function;
private refresh: Function;
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.refresh = null;
this.excalidrawRef = null;
this.plugin = plugin;
this.justLoaded = false;
}
// clear the view content
clear() {
ReactDOM.unmountComponentAtNode(this.contentEl);
this.getScene = null;
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);
}
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,"Force-save now to update transclusion visible in adjacent workspace pane\n(Please note, that autosave is always on)",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());
if (this.app.workspace.layoutReady) {
(this.app.workspace.rootSplit as WorkspaceItem as WorkspaceItemExt).containerEl.addEventListener('scroll',(e)=>{if(this.refresh) this.refresh();});
} else {
this.registerEvent(this.app.workspace.on('layout-ready', async () => (this.app.workspace.rootSplit as WorkspaceItem as WorkspaceItemExt).containerEl.addEventListener('scroll',(e)=>{if(this.refresh) this.refresh();})));
}
}
setViewData (data: string, clear: boolean) {
//save current drawing when user closes workspace leaf
async onunload() {
if(this.excalidrawRef) await this.save();
}
setViewData (data: string, clear: boolean) {
if (this.app.workspace.layoutReady) {
this.loadDrawing(data,clear);
} else {
this.registerEvent(this.app.workspace.on('layout-ready', async () => this.loadDrawing(data,clear)));
}
}
// clear the view content
clear() {
if(this.excalidrawRef) {
this.excalidrawRef = null;
this.getScene = null;
this.refresh = 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)";
else return "Excalidraw (no file)";
}
// confirms this view can accept csv extension
canAcceptExtension(extension: string) {
return extension == 'excalidraw';
return extension == EXCALIDRAW_FILE_EXTENSION;
}
// the view type name
getViewType() {
return "excalidraw";
return VIEW_TYPE_EXCALIDRAW;
}
// icon for the view
getIcon() {
return "document-excalidraw";
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({
@@ -68,40 +196,49 @@ export default class ExcalidrawView extends TextFileView {
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)}
console.log("Excalidraw resize",this.contentEl.clientWidth,this.contentEl.clientHeight);
} 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
}
});
};
this.refresh = () => {
if(!excalidrawRef?.current) return;
excalidrawRef.current.refresh();
};
return React.createElement(
React.Fragment,
null,
@@ -109,7 +246,8 @@ export default class ExcalidrawView extends TextFileView {
"div",
{
className: "excalidraw-wrapper",
ref: excalidrawWrapperRef
ref: excalidrawWrapperRef,
key: "abc",
},
React.createElement(Excalidraw.default, {
ref: excalidrawRef,
@@ -119,13 +257,64 @@ export default class ExcalidrawView extends TextFileView {
canvasActions: {
loadScene: false,
saveScene: false,
saveAsScene: false
saveAsScene: false,
export: false
},
},
initialData: initdata
initialData: initdata,
detectScroll: true,
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, exportSettings:ExportSettings):SVGSVGElement {
try {
const excalidrawData = JSON.parse(data);
return exportToSvg({
elements: excalidrawData.elements,
appState: {
exportBackground: exportSettings.withBackground,
exportWithDarkMode: exportSettings.withTheme ? (excalidrawData.appState?.theme=="light" ? false : true) : false,
... excalidrawData.appState,},
exportPadding:10,
metadata: "Generated by Excalidraw-Obsidian plugin",
});
} catch (error) {
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;
}
}
}

117
src/TransclusionIndex.ts Normal file
View File

@@ -0,0 +1,117 @@
import {Vault,TFile,TAbstractFile} from 'obsidian';
export default class TransclusionIndex {
private vault: Vault;
private doc2ex: Map<string, Set<string>>;
private ex2doc: Map<string, Set<string>>;
constructor(vault: Vault) {
this.vault = vault;
this.doc2ex = new Map<string,Set<string>>(); //markdown document includes these excalidraw drawings
this.ex2doc = new Map<string,Set<string>>(); //excalidraw drawings are referenced in these markdown documents
}
async reloadIndex() {
await this.initialize();
}
async initialize(): Promise<void> {
const doc2ex = new Map<string,Set<string>>();
const ex2doc = new Map<string,Set<string>>();
const markdownFiles = this.vault.getMarkdownFiles();
for (const file of markdownFiles) {
const drawings = await this.parseTransclusionsInFile(file);
if (drawings.size > 0) {
doc2ex.set(file.path, drawings);
drawings.forEach((drawing)=>{
if(ex2doc.has(drawing)) ex2doc.set(drawing,ex2doc.get(drawing).add(file.path));
else ex2doc.set(drawing,(new Set<string>()).add(file.path));
});
}
}
this.doc2ex = doc2ex;
this.ex2doc = ex2doc;
this.registerEventHandlers();
}
private updateMarkdownFile(file:TFile,oldExPath:string,newExPath:string) {
const fileContents = this.vault.read(file);
fileContents.then((c: string) => this.vault.modify(file, c.split("[["+oldExPath).join("[["+newExPath)));
const exlist = this.doc2ex.get(file.path);
exlist.delete(oldExPath);
exlist.add(newExPath);
this.doc2ex.set(file.path,exlist);
}
public updateTransclusion(oldExPath: string, newExPath: string): void {
if(!this.ex2doc.has(oldExPath)) return; //drawing is not transcluded in any markdown document
for(const filePath of this.ex2doc.get(oldExPath)) {
this.updateMarkdownFile(this.vault.getAbstractFileByPath(filePath) as TFile,oldExPath,newExPath);
}
this.ex2doc.set(newExPath, this.ex2doc.get(oldExPath));
this.ex2doc.delete(oldExPath);
}
private indexAbstractFile(file: TAbstractFile) {
if (!(file instanceof TFile)) return;
if (file.extension.toLowerCase() != "md") return; //not a markdown document
this.indexFile(file as TFile);
}
private indexFile(file: TFile) {
this.clearIndex(file.path);
this.parseTransclusionsInFile(file).then((drawings) => {
if(drawings.size == 0) return;
this.doc2ex.set(file.path, drawings);
drawings.forEach((drawing)=>{
if(this.ex2doc.has(drawing)) {
this.ex2doc.set(drawing,this.ex2doc.get(drawing).add(file.path));
}
else this.ex2doc.set(drawing,(new Set<string>()).add(file.path));
});
});
}
private clearIndex(path: string) {
if(!this.doc2ex.get(path)) return;
this.doc2ex.get(path).forEach((ex)=> {
const files = this.ex2doc.get(ex);
files.delete(path);
if(files.size>0) this.ex2doc.set(ex,files);
else this.ex2doc.delete(ex);
});
this.doc2ex.delete(path);
}
private async parseTransclusionsInFile(file: TFile): Promise<Set<string>> {
const fileContents = await this.vault.cachedRead(file);
const pattern = new RegExp('('+String.fromCharCode(96,96,96)+'excalidraw\\s+.*\\[{2})([^|\\]]*).*\\]{2}[\\s]+'+String.fromCharCode(96,96,96),'gm');
const transclusions = new Set<string>();
for(const transclusion of [...fileContents.matchAll(pattern)]) {
if(transclusion[2] && transclusion[2].endsWith('.excalidraw'))
transclusions.add(transclusion[2]);
}
return transclusions;
}
private registerEventHandlers() {
this.vault.on('create', (file: TAbstractFile) => {
this.indexAbstractFile(file);
});
this.vault.on('modify', (file: TAbstractFile) => {
this.indexAbstractFile(file);
});
this.vault.on('delete', (file: TAbstractFile) => {
this.clearIndex(file.path);
});
// We could simply change the references to the old path, but parsing again does the trick as well
this.vault.on('rename', (file: TAbstractFile, oldPath: string) => {
this.clearIndex(oldPath);
this.indexAbstractFile(file);
});
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,109 +1,383 @@
import {
TFile,
TFolder,
Plugin,
WorkspaceLeaf,
addIcon,
App,
PluginManifest,
EventRef,
Menu,
TAbstractFile
TFile,
TFolder,
Plugin,
WorkspaceLeaf,
addIcon,
App,
PluginManifest,
MarkdownView,
normalizePath,
MarkdownPostProcessorContext,
Menu,
MenuItem,
} from 'obsidian';
import { BLANK_DRAWING, VIEW_TYPE_EXCALIDRAW, PALETTE_ICON } from './constants';
import ExcalidrawView from './ExcalidrawView';
import {
BLANK_DRAWING,
VIEW_TYPE_EXCALIDRAW,
EXCALIDRAW_ICON,
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, {ExportSettings} from './ExcalidrawView';
import {
ExcalidrawSettings,
DEFAULT_SETTINGS,
ExcalidrawSettingTab
ExcalidrawSettings,
DEFAULT_SETTINGS,
ExcalidrawSettingTab
} from './settings';
import {OpenFileDialog} from './openDrawing';
import {getDateString} from './utils'
import {
openDialogAction,
OpenFileDialog
} from './openDrawing';
import {
initExcalidrawAutomate,
destroyExcalidrawAutomate
} from './ExcalidrawTemplate';
import TransclusionIndex from './TransclusionIndex';
export interface ExcalidrawAutomate extends Window {
ExcalidrawAutomate: {
theme: string;
createNew: Function;
};
}
export default class ExcalidrawPlugin extends Plugin {
public settings: ExcalidrawSettings;
public view: ExcalidrawView;
private openDialog: OpenFileDialog;
private activeDrawing: TFile;
private activeDrawingFilename: string;
public settings: ExcalidrawSettings;
private openDialog: OpenFileDialog;
private transclusionIndex: TransclusionIndex;
constructor(app: App, manifest: PluginManifest) {
constructor(app: App, manifest: PluginManifest) {
super(app, manifest);
this.activeDrawing = null;
this.activeDrawingFilename = '';
}
async onload() {
addIcon("excalidraw", PALETTE_ICON);
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) => (this.view = new ExcalidrawView(leaf))
(leaf: WorkspaceLeaf) => new ExcalidrawView(leaf, this)
);
this.registerExtensions(["excalidraw"],"excalidraw");
this.registerExtensions([EXCALIDRAW_FILE_EXTENSION],VIEW_TYPE_EXCALIDRAW);
await this.loadSettings();
this.addSettingTab(new ExcalidrawSettingTab(this.app, this));
this.registerMarkdownCodeBlockProcessor(CODEBLOCK_EXCALIDRAW, async (source,el,ctx) => {
el.addEventListener(RERENDER_EVENT,async (e) => {
e.stopPropagation();
el.empty();
this.codeblockProcessor(source,el,ctx,this);
});
this.codeblockProcessor(source,el,ctx,this);
});
this.openDialog = new OpenFileDialog(this.app, this);
this.addRibbonIcon('excalidraw', 'Excalidraw', async () => {
this.openDialog.start();
});
await this.loadSettings();
this.addSettingTab(new ExcalidrawSettingTab(this.app, this));
this.addCommand({
id: "excalidraw-open",
name: "Open existing drawing or create new one",
callback: () => {
this.openDialog.start();
},
});
this.openDialog = new OpenFileDialog(this.app, this);
this.addRibbonIcon(ICON_NAME, 'Create a new drawing in Excalidraw', async (e) => {
this.createDrawing(this.getNextDefaultFilename(), e.ctrlKey);
});
this.addCommand({
id: "excalidraw-autocreate",
name: "Create a new drawing",
callback: () => {
this.createDrawing(this.getNextDefaultFilename());
},
});
}
id: "excalidraw-open",
name: "Open an existing drawing - IN A NEW PANE",
callback: () => {
this.openDialog.start(openDialogAction.openFile, true);
},
});
private async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async saveSettings() {
await this.saveData(this.settings);
}
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 (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 - IN A NEW PANE",
callback: () => {
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(this.getNextDefaultFilename(),false,file.path);
})
});
}
})
);
//watch filename change to rename .svg
this.app.vault.on('rename',async (file,oldPath) => {
this.transclusionIndex.updateTransclusion(oldPath,file.path);
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);
}
}
});
//save open drawings when user quits the application
this.app.workspace.on('quit',(tasks) => {
const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
for (let i=0;i<leaves.length;i++) {
(leaves[i].view as ExcalidrawView).save();
}
});
this.transclusionIndex = new TransclusionIndex(this.app.vault);
this.transclusionIndex.initialize();
}
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) {
const editor = activeView.editor;
editor.replaceSelection(
String.fromCharCode(96,96,96) +
CODEBLOCK_EXCALIDRAW +
"\n[["+data+"]]\n" +
String.fromCharCode(96,96,96));
editor.focus();
}
}
private async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async saveSettings() {
await this.saveData(this.settings);
}
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 openDrawing(drawingFile: TFile, onNewPane: boolean) {
const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
let leaf:WorkspaceLeaf = null;
if (leaves?.length > 0) {
leaf = leaves[0];
}
if(!leaf) {
leaf = this.app.workspace.activeLeaf;
}
if(!leaf) {
leaf = this.app.workspace.getLeaf();
}
if(onNewPane) {
leaf = this.app.workspace.createLeafBySplit(leaf);
}
public async openDrawing(drawingFile: TFile) {
this.activeDrawing = drawingFile;
this.saveSettings();
const leaf = this.view ? this.view.leaf : this.app.workspace.activeLeaf;
leaf.setViewState({
type: VIEW_TYPE_EXCALIDRAW,
state: {file: drawingFile.path}}
);
}
}
private getNextDefaultFilename():string {
return this.settings.folder+'/Drawing ' + getDateString('yyyy-MM-dd HH.mm.ss')+'.excalidraw';
}
private getNextDefaultFilename():string {
return 'Drawing ' + window.moment().format('YYYY-MM-DD HH.mm.ss')+'.'+EXCALIDRAW_FILE_EXTENSION;
}
public async createDrawing(filename: string) {
if(!(this.app.vault.getAbstractFileByPath(this.settings.folder) as TFile)) {
this.app.vault.createFolder(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);
}
const file = (this.app.vault.getAbstractFileByPath(this.settings.templateFilePath) as TFile);
if(file) {
this.app.vault.read(file).then(async (content: string) => {
this.openDrawing(await this.app.vault.create(filename,content==''?BLANK_DRAWING:content))
});
} else {
this.openDrawing(await this.app.vault.create(filename,BLANK_DRAWING));
}
}
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(fname,content==''?BLANK_DRAWING:content), onNewPane);
} else {
this.openDrawing(await this.app.vault.create(fname,BLANK_DRAWING), onNewPane);
}
}
}

View File

@@ -1,26 +1,40 @@
import { App, FuzzySuggestModal, TFile, TFolder, normalizePath, Vault, TAbstractFile, Instruction } from "obsidian";
import {
App,
FuzzySuggestModal,
TFile
} from "obsidian";
import ExcalidrawPlugin from './main';
import ExcalidrawView from './view';
import {
EMPTY_MESSAGE,
EXCALIDRAW_FILE_EXTENSION
} from './constants';
export enum openDialogAction {
openFile,
insertLink,
}
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;
const EMPTY_MESSAGE = "Hit enter to create a new drawing";
this.emptyStateText = EMPTY_MESSAGE;
this.onNewPane = false;
this.setInstructions([{
command: "Select an existing drawing or type title for your new drawing, then hit enter.",
purpose: "The new drawing will be created in the default Excalidraw folder specified in Settings.",
command: "Type name of drawing to select.",
purpose: "",
}]);
this.inputEl.onkeyup = (e) => {
if(e.key=="Enter") {
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');
this.plugin.createDrawing(this.plugin.settings.folder+'/'+this.inputEl.value+'.'+EXCALIDRAW_FILE_EXTENSION, this.onNewPane);
this.close();
}
}
@@ -28,9 +42,8 @@ export class OpenFileDialog extends FuzzySuggestModal<TFile> {
}
getItems(): TFile[] {
let excalidrawFiles: TFile[] = [];
excalidrawFiles = this.app.vault.getFiles();
return excalidrawFiles.filter((f:TFile) => (f.extension=='excalidraw'));
const excalidrawFiles = this.app.vault.getFiles();
return (excalidrawFiles || []).filter((f:TFile) => (f.extension==EXCALIDRAW_FILE_EXTENSION));
}
getItemText(item: TFile): string {
@@ -38,17 +51,30 @@ export class OpenFileDialog extends FuzzySuggestModal<TFile> {
}
onChooseItem(item: TFile, _evt: MouseEvent | KeyboardEvent): void {
this.plugin.openDrawing(item);
switch(this.action) {
case(openDialogAction.openFile):
this.plugin.openDrawing(item, this.onNewPane);
break;
case(openDialogAction.insertLink):
this.plugin.insertCodeblock(item.path);
break;
}
}
start(): void {
try {
let files = this.getItems();
this.open();
}
catch(error) {
console.log(error);
start(action:openDialogAction, onNewPane: boolean): void {
this.action = action;
this.onNewPane = onNewPane;
switch(action) {
case (openDialogAction.openFile):
this.emptyStateText = EMPTY_MESSAGE;
this.setPlaceholder("Select existing drawing or type name of new and hit enter.");
break;
case (openDialogAction.insertLink):
this.emptyStateText = "No file matches your query.";
this.setPlaceholder("Select existing drawing to insert into document.");
break;
}
this.open();
}
}

View File

@@ -1,51 +1,144 @@
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,
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 {
plugin: ExcalidrawPlugin;
plugin: ExcalidrawPlugin;
constructor(app: App, plugin: ExcalidrawPlugin) {
super(app, plugin);
this.plugin = plugin;
}
constructor(app: App, plugin: ExcalidrawPlugin) {
super(app, plugin);
this.plugin = plugin;
}
display(): void {
let {containerEl} = this;
display(): void {
let {containerEl} = this;
this.containerEl.empty();
this.containerEl.empty();
new Setting(containerEl)
.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 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();
}));
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')
.addText(text => text
.setPlaceholder('excalidraw')
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 the extension ".excalidraw". ' +
'Assuming your template is in the default Excalidraw folder, the setting would be: Excalidraw/Template.excalidraw')
.addText(text => text
.setPlaceholder('Excalidraw/Template.excalidraw')
.setValue(this.plugin.settings.templateFilePath)
.onChange(async (value) => {
this.plugin.settings.templateFilePath = value;
await this.plugin.saveSettings();
}));
}
.onChange(async (value) => {
this.plugin.settings.templateFilePath = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.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.')
.addText(text => text
.setPlaceholder('400')
.setValue(this.plugin.settings.width)
.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();
}));
}
}

View File

@@ -1,28 +0,0 @@
export function getDateString(format:string):string {
const pad2 = (n:number) => {
return n>9? n.toString():'0'+n;
}
const now = new Date();
const M=now.getMonth()+1,H=now.getHours(),m=now.getMinutes(),d=now.getDate(),s=now.getSeconds(),yyyy=now.getFullYear();
const n = {
yyyy: yyyy.toString(),
MM : pad2(M),
dd : pad2(d),
HH : pad2(H),
mm : pad2(m),
ss : pad2(s)
};
return format.replace(/([a-zA-Z]+)/g,function (s:string, $1:string):string {
switch($1) {
case 'yyyy' : return n.yyyy;
case 'MM' : return n.MM;
case 'dd' : return n.dd;
case 'HH' : return n.HH;
case 'mm' : return n.mm;
case 'ss' : return n.ss;
}
return '';
});
}

View File

@@ -1,146 +0,0 @@
import { EventRef, Workspace, ItemView, WorkspaceLeaf, TFile } from "obsidian";
import { VIEW_TYPE_EXCALIDRAW } from "./constants";
import * as ReactDOM from "react-dom";
import * as React from "react";
import Excalidraw, { exportToCanvas, exportToSvg, exportToBlob } from "@excalidraw/excalidraw";
import type SceneData from "@excalidraw/excalidraw";
import '../styles.css';
import Scene from "@excalidraw/excalidraw/types/scene/Scene";
import { ExcalidrawElement } from "@excalidraw/excalidraw/types/element/types";
import { AppState } from "@excalidraw/excalidraw/types/types";
export default class ExcalidrawView extends ItemView {
getSVG: any;
getPNG: any;
file: TFile;
workspace: Workspace;
constructor(leaf: WorkspaceLeaf) {
super(leaf);
this.workspace = this.app.workspace;
this.file = null;
}
private instantiateExcalidraw(initdata: any) {
ReactDOM.unmountComponentAtNode(this.contentEl);
ReactDOM.render(React.createElement(() => {
let previousSceneVersion = 0;
const excalidrawRef = React.useRef(null);
const excalidrawWrapperRef = React.useRef(null);
const [dimensions, setDimensions] = React.useState({
width: undefined,
height: undefined
});
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)}
};
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}, [excalidrawWrapperRef]);
/* this.getScene = function() {
return {
elements: excalidrawRef.current.getSceneElements(),
appState: excalidrawRef.current.getAppState()
};
};*/
/* this.updateScene = function(scene: Scene) {
sceneJustUpdated = true;
excalidrawRef.current.updateScene(scene);
};*/
return React.createElement(
React.Fragment,
null,
React.createElement(
"div",
{
className: "excalidraw-wrapper",
ref: excalidrawWrapperRef
},
React.createElement(Excalidraw.default, {
ref: excalidrawRef,
width: dimensions.width,
height: dimensions.height,
UIOptions: {
canvasActions: {
loadScene: false,
saveScene: false,
saveAsScene: false
},
},
initialData: initdata,
onChange: (el: ExcalidrawElement[], st: AppState) => {
if (st.editingElement == null && st.resizingElement == null &&
st.draggingElement == null && st.editingGroupId == null &&
st.editingLinearElement == null ) {
const sceneVersion = Excalidraw.getSceneVersion(el);
if(sceneVersion != previousSceneVersion) {
previousSceneVersion = sceneVersion;
this.saveFile(JSON.stringify({
"type": "excalidraw",
"version": 2,
"source": "https://excalidraw.com",
"elements": el.filter(e => !e.isDeleted),
"appState": {
"theme": st.theme,
"viewBackgroundColor": st.viewBackgroundColor
}
}));
}
}
},
})
)
);
}),(this as any).contentEl);
}
private async saveFile(content: string) {
await this.app.vault.modify(this.file, content);
}
public loadDrawing(file: TFile) {
this.file = file;
this.app.vault.read(file).then((content: string) => {
const data = JSON.parse(content);
this.instantiateExcalidraw({
elements: data.elements,
appState: data.appState,
scrollToContent: true,
});
});
}
public getCurrentDrawingFilename() {
return this.file == null ? '' : this.file.path;
}
getDisplayText() {
return this.file!=null ? this.file.basename : "Excalidraw";
}
getIcon() {
return "palette";
}
getViewType() {
return VIEW_TYPE_EXCALIDRAW;
}
}

View File

@@ -6,8 +6,39 @@
.excalidraw-wrapper {
height: 100%;
margin: 0px;
background-color: white;
}
.context-menu-option__shortcut {
background-color: transparent !important;
}
}
.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;
}
button.ToolIcon_type_button[title="Export"] {
display:none;
}

View File

@@ -13,7 +13,7 @@
"dom",
"es5",
"scripthost",
"es2015",
"es2020",
"DOM.Iterable"
],
"jsx": "react",

View File

@@ -1,3 +1,5 @@
{
".0.1": "0.11.13"
}
"1.0.7": "0.11.13",
"1.0.6": "0.11.13",
"1.0.5": "0.11.13"
}

206
yarn.lock
View File

@@ -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"