mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
44 Commits
1.7.9
...
panel-grou
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b9aa8ccc4 | ||
|
|
b0bb5a00fb | ||
|
|
9bf3e899c2 | ||
|
|
db1f989f49 | ||
|
|
4c45a54ddd | ||
|
|
64d48ed867 | ||
|
|
e2d70687c1 | ||
|
|
1aac4ffdf3 | ||
|
|
b9e154330b | ||
|
|
56ae6baf2b | ||
|
|
64fd43abb8 | ||
|
|
31ae51a421 | ||
|
|
0d879b7a7e | ||
|
|
d8166bbf8c | ||
|
|
82bde38a4b | ||
|
|
36b679434f | ||
|
|
6fa90662b3 | ||
|
|
7f45dad610 | ||
|
|
0e1ee0dde2 | ||
|
|
6c7b63cbdf | ||
|
|
bca7010394 | ||
|
|
3565a5bf94 | ||
|
|
f089911e02 | ||
|
|
992af2b5ca | ||
|
|
f85fc124d9 | ||
|
|
961e75a12d | ||
|
|
3be592eeb8 | ||
|
|
5f094f3d95 | ||
|
|
a16fd53958 | ||
|
|
72d425715f | ||
|
|
4a6aed5dd0 | ||
|
|
f76964a7b7 | ||
|
|
161f7041ec | ||
|
|
334e8130eb | ||
|
|
fdb7e97b11 | ||
|
|
22b3769c20 | ||
|
|
cd96870f07 | ||
|
|
a76854d152 | ||
|
|
32319b2f6c | ||
|
|
38add10053 | ||
|
|
21dc29d1d1 | ||
|
|
e7444f5d8a | ||
|
|
cc9d7828c7 | ||
|
|
96dcaf38c1 |
19
README.md
19
README.md
@@ -17,23 +17,22 @@ Please upgrade to Obsidian v0.12.19 or higher to get the latest release.
|
||||
|[](https://youtu.be/eKFmrSQhFA4)|[](https://youtu.be/qbPIAZguJeo)|[](https://youtu.be/2Y8OhkGiTHg)|
|
||||
|[](https://youtu.be/2v9TZmQNO8c)|[](https://youtu.be/xHPGWR3m0c8)|[](https://youtu.be/gMIKXyhS-dM)|
|
||||
|[](https://youtu.be/Etskjw7a5zo)|[](https://youtu.be/4N6efq1DtH0)|[](https://youtu.be/U2LkBRBk4LY)|
|
||||
| [](https://youtu.be/qiKuqMcNWgU)| | |
|
||||
|
||||
|
||||
| [](https://youtu.be/qiKuqMcNWgU)|[](https://youtu.be/yZQoJg2RCKI)| |
|
||||
|
||||
|
||||
# Key features
|
||||
- The plugin aims to integrate Excalidraw seamlessly into Obsidian including Command Palette actions, File Explorer features, Option Menu commands, and the Ribbon Button.
|
||||
- The plugin integrates Excalidraw seamlessly into Obsidian including Command Palette actions, File Explorer features, Option Menu commands, and the Ribbon Button.
|
||||
- <kbd>CTRL/CMD+Click</kbd> on the ribbon button, or in the file explorer to create / open drawings in a new pane.
|
||||
- Settings will allow you to customize Excalidraw to your needs:
|
||||
- Default folder for new drawings and define custom filename pattern for new drawings.
|
||||
- Template for new drawings. The template will restore stroke properties. This means you can set up defaults in your template for stroke color, stroke width, opacity, font family, font size, fill style, stroke style, etc. This also applies to ExcalidrawAutomate.
|
||||
- If portability is important to you: Auto-export SVG and/or PNG files including keep-in-sync feature so you can embed SVG/PNG into your documents instead of embedding excalidraw files.
|
||||
- Via the template you can customize the color palette used by Excalidraw. Switch to Markdown view. Scroll down to the bottom of the file and find `"AppState": {`. Find `"customColorPalette": {` at the end of the AppState section. You may specify the 3 palettes used in Excalidraw by adding any or all of the following 3 variables: `"canvasBackground":[], "elementBackground":[], "elementStroke": []`. Add a comma separated list of valid HTML colors (e.g. `#FF0000` for red) in the array for each of the variables. See my videos above for further help.
|
||||
- If portability is important to you: Auto-export SVG and/or PNG files including keep-in-sync feature so you can embed SVG/PNG into your documents instead of embedding excalidraw files. You can override export settings for an individual file by adding the `excalidraw-autoexport` frontmatter key. Valid values for this key are `none`, `both`, `png` and `svg`.
|
||||
- Specify the default width of embedded drawings.
|
||||
- Compatibility features to auto-export and keep in sync markdown excalidraw files and legacy .excalidraw files.
|
||||
- Experimental feature to add custom TAG to file explorer to mark drawing files.
|
||||
- Enable / disable autosave.
|
||||
- You can customize the size and position of the embedded images 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 to the rendered SVG element style and to the wrapper DIV element. See [styles.css](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/styles.css) for more insight.
|
||||
- You can customize the size and position of the embedded images 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](https://www.scaler.com/topics/align-image-in-html/). Any text that appears in `<alignment>` will be added to the rendered SVG element style and to the wrapper DIV element. See [styles.css](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/styles.css) for more insight.
|
||||
- Supports hyperlinks e.g. `https://zsolt.blog`, `[Obsidian](https://obsidian.md)`, and internal links e.g. `[[My file in vault|Alias]]` in drawing text.
|
||||
- Links will update when files are moved or renamed, if you have the Obsidian setting Files & Links/Automatically Update Internal Links enabled.
|
||||
- Links in drawings will show up in backlinks of documents
|
||||
@@ -59,6 +58,12 @@ Please upgrade to Obsidian v0.12.19 or higher to get the latest release.
|
||||
- On iOS and Android you can add images from your camera by pressing the add image button in Excalidraw.
|
||||
- You can copy/paste images into your drawing. Images will be saved in your vault.
|
||||
- You can drag and drop images as explained above.
|
||||
- Block referencing parts of images
|
||||
- When referencing an element on the canvas in a link pointing to an Excalidraw file using the elementId or the section header (i.e. a Text Element containing the `# <Section title>`) - e.g. `[[file#^elementID]]`, you can add the `group=` prefix, e.g. `[[file#^group=elementID]]` or the `area=` prefix, e.g. `[[file#area=Section heading]]`.
|
||||
- If the `group=` prefix is found Excalidraw will select the group of elements in the same group as the element referenced by the elementID (block reference) or the section heading.
|
||||
- If the `area=` prefix is found Excalidraw will insert a cutout of the image around the referenced element.
|
||||
- Note that the `area=` selector is not supported when embedding Excalidraw as PNG into your markdown documents.
|
||||
- Referencing the elementID of a text element without the `group=` or `area=` prefix will transclude the element as plain text. Referencing a non-Text Element (e.g. rectangle, ellipse, etc.) without the `group=` or `area=` prefix will result in an Obsidian error since these elementIds are not present in the Excalidraw markdown file as block references.
|
||||
- Since 1.2.0 Drawing files are stored in Markdown files
|
||||
- You can add tags to drawings
|
||||
- You can add metadata to the YAML front matter of drawings
|
||||
@@ -72,7 +77,7 @@ Please upgrade to Obsidian v0.12.19 or higher to get the latest release.
|
||||
- Frontmatter tags to customize image export at a file level [519](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/519). If these keys are present they will override the default excalidraw embed and export settings.
|
||||
- `excalidraw-export-transparent: true`: true == Transparent / false == with background.
|
||||
- `excalidraw-export-dark`: true == Dark mode / false == light mode.
|
||||
- `excalidraw-export-svgpadding`: This only affects export to SVG. Specify the export padding for the image
|
||||
- `excalidraw-export-padding`: Specify the export padding for the image
|
||||
- `excalidraw-export-pngscale`: This only affects export to PNG. Specify the export scale for the image. The typical range is between 0.5 and 5, but you can experiment with other values as well.
|
||||
- Embed complete markdown files into your drawings
|
||||
- Drag from the desired file from the Obsidian file explorer and hold down <kbd>CTRL/CMD</kbd> while dropping the file onto the canvas.
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
Here's the interface implemented by ExcalidrawAutomate:
|
||||
|
||||
```typescript
|
||||
export interface ExcalidrawAutomate {
|
||||
export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
plugin: ExcalidrawPlugin;
|
||||
elementsDict: {}; //contains the ExcalidrawElements currently edited in Automate indexed by el.id
|
||||
imagesDict: {}; //the images files including DataURL, indexed by fileId
|
||||
targetView: ExcalidrawView = null; //the view currently edited
|
||||
elementsDict: {[key:string]:any}; //contains the ExcalidrawElements currently edited in Automate indexed by el.id
|
||||
imagesDict: {[key: FileId]: any}; //the images files including DataURL, indexed by fileId
|
||||
mostRecentMarkdownSVG:SVGSVGElement = null; //Markdown renderer will drop a copy of the most recent SVG here for debugging purposes
|
||||
style: {
|
||||
strokeColor: string; //https://www.w3schools.com/colors/default.asp
|
||||
backgroundColor: string;
|
||||
@@ -29,178 +31,521 @@ export interface ExcalidrawAutomate {
|
||||
viewBackgroundColor: string;
|
||||
gridSize: number;
|
||||
};
|
||||
setFillStyle(val: number): void; //0:"hachure", 1:"cross-hatch" 2:"solid"
|
||||
setStrokeStyle(val: number): void; //0:"solid", 1:"dashed", 2:"dotted"
|
||||
setStrokeSharpness(val: number): void; //0:"round", 1:"sharp"
|
||||
setFontFamily(val: number): void; //1: Virgil, 2:Helvetica, 3:Cascadia
|
||||
setTheme(val: number): void; //0:"light", 1:"dark"
|
||||
addToGroup(objectIds: []): string;
|
||||
toClipboard(templatePath?: string): void;
|
||||
getElements(): ExcalidrawElement[]; //get all elements from ExcalidrawAutomate elementsDict
|
||||
getElement(id: string): ExcalidrawElement; //get single element from ExcalidrawAutomate elementsDict
|
||||
create(params?: {
|
||||
//create a drawing and save it to filename
|
||||
filename?: string; //if null: default filename as defined in Excalidraw settings
|
||||
foldername?: string; //if null: default folder as defined in Excalidraw settings
|
||||
templatePath?: string;
|
||||
onNewPane?: boolean;
|
||||
frontmatterKeys?: {
|
||||
"excalidraw-plugin"?: "raw" | "parsed";
|
||||
"excalidraw-link-prefix"?: string;
|
||||
"excalidraw-link-brackets"?: boolean;
|
||||
"excalidraw-url-prefix"?: string;
|
||||
constructor(plugin: ExcalidrawPlugin, view?: ExcalidrawView);
|
||||
/**
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
getAPI(view?: ExcalidrawView): ExcalidrawAutomate;
|
||||
/**
|
||||
* @param val //0:"hachure", 1:"cross-hatch" 2:"solid"
|
||||
* @returns
|
||||
*/
|
||||
setFillStyle(val: number): "hachure" | "cross-hatch" | "solid";
|
||||
/**
|
||||
* @param val //0:"solid", 1:"dashed", 2:"dotted"
|
||||
* @returns
|
||||
*/
|
||||
setStrokeStyle(val: number): "solid" | "dashed" | "dotted";
|
||||
/**
|
||||
* @param val //0:"round", 1:"sharp"
|
||||
* @returns
|
||||
*/
|
||||
setStrokeSharpness(val: number): "round" | "sharp";
|
||||
/**
|
||||
* @param val //1: Virgil, 2:Helvetica, 3:Cascadia
|
||||
* @returns
|
||||
*/
|
||||
setFontFamily(val: number): "Virgil, Segoe UI Emoji" | "Helvetica, Segoe UI Emoji" | "Cascadia, Segoe UI Emoji" | "LocalFont";
|
||||
/**
|
||||
* @param val //0:"light", 1:"dark"
|
||||
* @returns
|
||||
*/
|
||||
setTheme(val: number): "light" | "dark";
|
||||
/**
|
||||
* @param objectIds
|
||||
* @returns
|
||||
*/
|
||||
addToGroup(objectIds: string[]): string;
|
||||
/**
|
||||
* @param templatePath
|
||||
*/
|
||||
toClipboard(templatePath?: string): Promise<void>;
|
||||
/**
|
||||
* get all elements from ExcalidrawAutomate elementsDict
|
||||
* @returns elements from elemenetsDict
|
||||
*/
|
||||
getElements(): ExcalidrawElement[];
|
||||
/**
|
||||
* get single element from ExcalidrawAutomate elementsDict
|
||||
* @param id
|
||||
* @returns
|
||||
*/
|
||||
getElement(id: string): ExcalidrawElement;
|
||||
/**
|
||||
* create a drawing and save it to filename
|
||||
* @param params
|
||||
* filename: if null, default filename as defined in Excalidraw settings
|
||||
* foldername: if null, default folder as defined in Excalidraw settings
|
||||
* @returns
|
||||
*/
|
||||
create(params?: {
|
||||
filename?: string;
|
||||
foldername?: string;
|
||||
templatePath?: string;
|
||||
onNewPane?: boolean;
|
||||
frontmatterKeys?: {
|
||||
"excalidraw-plugin"?: "raw" | "parsed";
|
||||
"excalidraw-link-prefix"?: string;
|
||||
"excalidraw-link-brackets"?: boolean;
|
||||
"excalidraw-url-prefix"?: string;
|
||||
"excalidraw-export-transparent"?: boolean;
|
||||
"excalidraw-export-dark"?: boolean;
|
||||
"excalidraw-export-svgpadding"?: number;
|
||||
"excalidraw-export-pngscale"?: number;
|
||||
"excalidraw-default-mode"?: "view" | "zen";
|
||||
};
|
||||
}): Promise<string>;
|
||||
/**
|
||||
*
|
||||
* @param templatePath
|
||||
* @param embedFont
|
||||
* @param exportSettings use ExcalidrawAutomate.getExportSettings(boolean,boolean)
|
||||
* @param loader use ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?)
|
||||
* @param theme
|
||||
* @returns
|
||||
*/
|
||||
createSVG(templatePath?: string, embedFont?: boolean, exportSettings?: ExportSettings, loader?: EmbeddedFilesLoader, theme?: string, padding?: number): Promise<SVGSVGElement>;
|
||||
/**
|
||||
*
|
||||
* @param templatePath
|
||||
* @param scale
|
||||
* @param exportSettings use ExcalidrawAutomate.getExportSettings(boolean,boolean)
|
||||
* @param loader use ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?)
|
||||
* @param theme
|
||||
* @returns
|
||||
*/
|
||||
createPNG(templatePath?: string, scale?: number, exportSettings?: ExportSettings, loader?: EmbeddedFilesLoader, theme?: string, padding?: number): Promise<any>;
|
||||
/**
|
||||
*
|
||||
* @param text
|
||||
* @param lineLen
|
||||
* @returns
|
||||
*/
|
||||
wrapText(text: string, lineLen: number): string;
|
||||
private boxedElement;
|
||||
/**
|
||||
*
|
||||
* @param topX
|
||||
* @param topY
|
||||
* @param width
|
||||
* @param height
|
||||
* @returns
|
||||
*/
|
||||
addRect(topX: number, topY: number, width: number, height: number): string;
|
||||
/**
|
||||
*
|
||||
* @param topX
|
||||
* @param topY
|
||||
* @param width
|
||||
* @param height
|
||||
* @returns
|
||||
*/
|
||||
addDiamond(topX: number, topY: number, width: number, height: number): string;
|
||||
/**
|
||||
*
|
||||
* @param topX
|
||||
* @param topY
|
||||
* @param width
|
||||
* @param height
|
||||
* @returns
|
||||
*/
|
||||
addEllipse(topX: number, topY: number, width: number, height: number): string;
|
||||
/**
|
||||
*
|
||||
* @param topX
|
||||
* @param topY
|
||||
* @param width
|
||||
* @param height
|
||||
* @returns
|
||||
*/
|
||||
addBlob(topX: number, topY: number, width: number, height: number): string;
|
||||
/**
|
||||
*
|
||||
* @param topX
|
||||
* @param topY
|
||||
* @param text
|
||||
* @param formatting
|
||||
* box: if !null, text will be boxed
|
||||
* @param id
|
||||
* @returns
|
||||
*/
|
||||
addText(topX: number, topY: number, text: string, formatting?: {
|
||||
wrapAt?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
textAlign?: string;
|
||||
box?: boolean | "box" | "blob" | "ellipse" | "diamond";
|
||||
boxPadding?: number;
|
||||
}, id?: string): string;
|
||||
/**
|
||||
*
|
||||
* @param points
|
||||
* @returns
|
||||
*/
|
||||
addLine(points: [[x: number, y: number]]): string;
|
||||
/**
|
||||
*
|
||||
* @param points
|
||||
* @param formatting
|
||||
* @returns
|
||||
*/
|
||||
addArrow(points: [x: number, y: number][], formatting?: {
|
||||
startArrowHead?: string;
|
||||
endArrowHead?: string;
|
||||
startObjectId?: string;
|
||||
endObjectId?: string;
|
||||
}): string;
|
||||
/**
|
||||
*
|
||||
* @param topX
|
||||
* @param topY
|
||||
* @param imageFile
|
||||
* @returns
|
||||
*/
|
||||
addImage(topX: number, topY: number, imageFile: TFile): Promise<string>;
|
||||
/**
|
||||
*
|
||||
* @param topX
|
||||
* @param topY
|
||||
* @param tex
|
||||
* @returns
|
||||
*/
|
||||
addLaTex(topX: number, topY: number, tex: string): Promise<string>;
|
||||
/**
|
||||
*
|
||||
* @param objectA
|
||||
* @param connectionA type ConnectionPoint = "top" | "bottom" | "left" | "right" | null
|
||||
* @param objectB
|
||||
* @param connectionB when passed null, Excalidraw will automatically decide
|
||||
* @param formatting
|
||||
* numberOfPoints: points on the line. Default is 0 ie. line will only have a start and end point
|
||||
* startArrowHead: "triangle"|"dot"|"arrow"|"bar"|null
|
||||
* endArrowHead: "triangle"|"dot"|"arrow"|"bar"|null
|
||||
* padding:
|
||||
* @returns
|
||||
*/
|
||||
connectObjects(objectA: string, connectionA: ConnectionPoint | null, objectB: string, connectionB: ConnectionPoint | null, formatting?: {
|
||||
numberOfPoints?: number;
|
||||
startArrowHead?: "triangle" | "dot" | "arrow" | "bar" | null;
|
||||
endArrowHead?: "triangle" | "dot" | "arrow" | "bar" | null;
|
||||
padding?: number;
|
||||
}): string;
|
||||
/**
|
||||
* Adds a text label to a line or arrow. Currently only works with a straight (2 point - start & end - line)
|
||||
* @param lineId id of the line or arrow object in elementsDict
|
||||
* @param label the label text
|
||||
* @returns undefined (if unsuccessful) or the id of the new text element
|
||||
*/
|
||||
addLabelToLine(lineId: string, label: string): string;
|
||||
/**
|
||||
* clear elementsDict and imagesDict only
|
||||
*/
|
||||
clear(): void;
|
||||
/**
|
||||
* clear() + reset all style values to default
|
||||
*/
|
||||
reset(): void;
|
||||
/**
|
||||
* returns true if MD file is an Excalidraw file
|
||||
* @param f
|
||||
* @returns
|
||||
*/
|
||||
isExcalidrawFile(f: TFile): boolean;
|
||||
/**
|
||||
*
|
||||
* @param view
|
||||
* @returns
|
||||
*/
|
||||
setView(view: ExcalidrawView | "first" | "active"): ExcalidrawView;
|
||||
/**
|
||||
*
|
||||
* @returns https://github.com/excalidraw/excalidraw/tree/master/src/packages/excalidraw#ref
|
||||
*/
|
||||
getExcalidrawAPI(): any;
|
||||
/**
|
||||
* get elements in View
|
||||
* @returns
|
||||
*/
|
||||
getViewElements(): ExcalidrawElement[];
|
||||
/**
|
||||
*
|
||||
* @param elToDelete
|
||||
* @returns
|
||||
*/
|
||||
deleteViewElements(elToDelete: ExcalidrawElement[]): boolean;
|
||||
/**
|
||||
* get the selected element in the view, if more are selected, get the first
|
||||
* @returns
|
||||
*/
|
||||
getViewSelectedElement(): any;
|
||||
/**
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
getViewSelectedElements(): any[];
|
||||
/**
|
||||
*
|
||||
* @param el
|
||||
* @returns TFile file handle for the image element
|
||||
*/
|
||||
getViewFileForImageElement(el: ExcalidrawElement): TFile | null;
|
||||
/**
|
||||
* copies elements from view to elementsDict for editing
|
||||
* @param elements
|
||||
*/
|
||||
copyViewElementsToEAforEditing(elements: ExcalidrawElement[]): void;
|
||||
/**
|
||||
*
|
||||
* @param forceViewMode
|
||||
* @returns
|
||||
*/
|
||||
viewToggleFullScreen(forceViewMode?: boolean): void;
|
||||
/**
|
||||
* connect an object to the selected element in the view
|
||||
* @param objectA ID of the element
|
||||
* @param connectionA
|
||||
* @param connectionB
|
||||
* @param formatting
|
||||
* @returns
|
||||
*/
|
||||
connectObjectWithViewSelectedElement(objectA: string, connectionA: ConnectionPoint | null, connectionB: ConnectionPoint | null, formatting?: {
|
||||
numberOfPoints?: number;
|
||||
startArrowHead?: "triangle" | "dot" | "arrow" | "bar" | null;
|
||||
endArrowHead?: "triangle" | "dot" | "arrow" | "bar" | null;
|
||||
padding?: number;
|
||||
}): boolean;
|
||||
/**
|
||||
* Adds elements from elementsDict to the current view
|
||||
* @param repositionToCursor default is false
|
||||
* @param save default is true
|
||||
* @param newElementsOnTop controls whether elements created with ExcalidrawAutomate
|
||||
* are added at the bottom of the stack or the top of the stack of elements already in the view
|
||||
* Note that elements copied to the view with copyViewElementsToEAforEditing retain their
|
||||
* position in the stack of elements in the view even if modified using EA
|
||||
* default is false, i.e. the new elements get to the bottom of the stack
|
||||
* @returns
|
||||
*/
|
||||
addElementsToView(repositionToCursor?: boolean, save?: boolean, newElementsOnTop?: boolean): Promise<boolean>;
|
||||
/**
|
||||
* Register instance of EA to use for hooks with TargetView
|
||||
* By default ExcalidrawViews will check window.ExcalidrawAutomate for event hooks.
|
||||
* Using this event you can set a different instance of Excalidraw Automate for hooks
|
||||
* @returns true if successful
|
||||
*/
|
||||
registerThisAsViewEA(): boolean;
|
||||
/**
|
||||
* Sets the targetView EA to window.ExcalidrawAutomate
|
||||
* @returns true if successful
|
||||
*/
|
||||
deregisterThisAsViewEA(): boolean;
|
||||
/**
|
||||
* If set, this callback is triggered when the user closes an Excalidraw view.
|
||||
*/
|
||||
onViewUnloadHook: (view: ExcalidrawView) => void;
|
||||
/**
|
||||
* If set, this callback is triggered, when the user changes the view mode.
|
||||
* You can use this callback in case you want to do something additional when the user switches to view mode and back.
|
||||
*/
|
||||
onViewModeChangeHook: (isViewModeEnabled: boolean, view: ExcalidrawView, ea: ExcalidrawAutomate) => void;
|
||||
/**
|
||||
* If set, this callback is triggered, when the user hovers a link in the scene.
|
||||
* You can use this callback in case you want to do something additional when the onLinkHover event occurs.
|
||||
* This callback must return a boolean value.
|
||||
* In case you want to prevent the excalidraw onLinkHover action you must return false, it will stop the native excalidraw onLinkHover management flow.
|
||||
*/
|
||||
onLinkHoverHook: (element: NonDeletedExcalidrawElement, linkText: string, view: ExcalidrawView, ea: ExcalidrawAutomate) => boolean;
|
||||
/**
|
||||
* If set, this callback is triggered, when the user clicks a link in the scene.
|
||||
* You can use this callback in case you want to do something additional when the onLinkClick event occurs.
|
||||
* This callback must return a boolean value.
|
||||
* In case you want to prevent the excalidraw onLinkClick action you must return false, it will stop the native excalidraw onLinkClick management flow.
|
||||
*/
|
||||
onLinkClickHook: (element: ExcalidrawElement, linkText: string, event: MouseEvent, view: ExcalidrawView, ea: ExcalidrawAutomate) => boolean;
|
||||
/**
|
||||
* If set, this callback is triggered, when Excalidraw receives an onDrop event.
|
||||
* You can use this callback in case you want to do something additional when the onDrop event occurs.
|
||||
* This callback must return a boolean value.
|
||||
* In case you want to prevent the excalidraw onDrop action you must return false, it will stop the native excalidraw onDrop management flow.
|
||||
*/
|
||||
onDropHook: (data: {
|
||||
ea: ExcalidrawAutomate;
|
||||
event: React.DragEvent<HTMLDivElement>;
|
||||
draggable: any;
|
||||
type: "file" | "text" | "unknown";
|
||||
payload: {
|
||||
files: TFile[];
|
||||
text: string;
|
||||
};
|
||||
excalidrawFile: TFile;
|
||||
view: ExcalidrawView;
|
||||
pointerPosition: {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
}) => boolean;
|
||||
/**
|
||||
* utility function to generate EmbeddedFilesLoader object
|
||||
* @param isDark
|
||||
* @returns
|
||||
*/
|
||||
getEmbeddedFilesLoader(isDark?: boolean): EmbeddedFilesLoader;
|
||||
/**
|
||||
* utility function to generate ExportSettings object
|
||||
* @param withBackground
|
||||
* @param withTheme
|
||||
* @returns
|
||||
*/
|
||||
getExportSettings(withBackground: boolean, withTheme: boolean): ExportSettings;
|
||||
/**
|
||||
* get bounding box of elements
|
||||
* bounding box is the box encapsulating all of the elements completely
|
||||
* @param elements
|
||||
* @returns
|
||||
*/
|
||||
getBoundingBox(elements: ExcalidrawElement[]): {
|
||||
topX: number;
|
||||
topY: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
}): Promise<string>;
|
||||
createSVG(
|
||||
templatePath?: string,
|
||||
embedFont?: boolean,
|
||||
exportSettings?: ExportSettings, //use ExcalidrawAutomate.getExportSettings(boolean,boolean)
|
||||
loader?: EmbeddedFilesLoader, //use ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?)
|
||||
theme?: string,
|
||||
): Promise<SVGSVGElement>;
|
||||
createPNG(
|
||||
templatePath?: string,
|
||||
scale?: number,
|
||||
exportSettings?: ExportSettings, //use ExcalidrawAutomate.getExportSettings(boolean,boolean)
|
||||
loader?: EmbeddedFilesLoader, //use ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?)
|
||||
theme?: string,
|
||||
): Promise<any>;
|
||||
wrapText(text: string, lineLen: number): string;
|
||||
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;
|
||||
addBlob(topX: number, topY: number, width: number, height: number): string;
|
||||
addText(
|
||||
topX: number,
|
||||
topY: number,
|
||||
text: string,
|
||||
formatting?: {
|
||||
wrapAt?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
textAlign?: string;
|
||||
box?: boolean | "box" | "blob" | "ellipse" | "diamond"; //if !null, text will be boxed
|
||||
boxPadding?: number;
|
||||
},
|
||||
id?: string,
|
||||
): string;
|
||||
addLine(points: [[x: number, y: number]]): string;
|
||||
addArrow(
|
||||
points: [[x: number, y: number]],
|
||||
formatting?: {
|
||||
startArrowHead?: string;
|
||||
endArrowHead?: string;
|
||||
startObjectId?: string;
|
||||
endObjectId?: string;
|
||||
},
|
||||
): string;
|
||||
addImage(topX: number, topY: number, imageFile: TFile): Promise<string>;
|
||||
addLaTex(topX: number, topY: number, tex: string): Promise<string>;
|
||||
connectObjects(
|
||||
objectA: string,
|
||||
connectionA: ConnectionPoint, //type ConnectionPoint = "top" | "bottom" | "left" | "right" | null
|
||||
objectB: string,
|
||||
connectionB: ConnectionPoint, //when passed null, Excalidraw will automatically decide
|
||||
formatting?: {
|
||||
numberOfPoints?: number; //points on the line. Default is 0 ie. line will only have a start and end point
|
||||
startArrowHead?: string; //"triangle"|"dot"|"arrow"|"bar"|null
|
||||
endArrowHead?: string; //"triangle"|"dot"|"arrow"|"bar"|null
|
||||
padding?: number;
|
||||
},
|
||||
): void;
|
||||
clear(): void; //clear elementsDict and imagesDict only
|
||||
reset(): void; //clear() + reset all style values to default
|
||||
isExcalidrawFile(f: TFile): boolean; //returns true if MD file is an Excalidraw file
|
||||
//view manipulation
|
||||
targetView: ExcalidrawView; //the view currently edited
|
||||
setView(view: ExcalidrawView | "first" | "active"): ExcalidrawView;
|
||||
getExcalidrawAPI(): any; //https://github.com/excalidraw/excalidraw/tree/master/src/packages/excalidraw#ref
|
||||
getViewElements(): ExcalidrawElement[]; //get elements in View
|
||||
deleteViewElements(el: ExcalidrawElement[]): boolean;
|
||||
getViewSelectedElement(): ExcalidrawElement; //get the selected element in the view, if more are selected, get the first
|
||||
getViewSelectedElements(): ExcalidrawElement[];
|
||||
getViewFileForImageElement(el: ExcalidrawElement): TFile | null; //Returns the TFile file handle for the image element
|
||||
copyViewElementsToEAforEditing(elements: ExcalidrawElement[]): void; //copies elements from view to elementsDict for editing
|
||||
viewToggleFullScreen(forceViewMode?: boolean): void;
|
||||
connectObjectWithViewSelectedElement( //connect an object to the selected element in the view
|
||||
objectA: string, //see connectObjects
|
||||
connectionA: ConnectionPoint,
|
||||
connectionB: ConnectionPoint,
|
||||
formatting?: {
|
||||
numberOfPoints?: number;
|
||||
startArrowHead?: string;
|
||||
endArrowHead?: string;
|
||||
padding?: number;
|
||||
},
|
||||
): boolean;
|
||||
addElementsToView( //Adds elements from elementsDict to the current view
|
||||
repositionToCursor?: boolean, //default is false
|
||||
save?: boolean, //default is true
|
||||
//newElementsOnTop controls whether elements created with ExcalidrawAutomate
|
||||
//are added at the bottom of the stack or the top of the stack of elements already in the view
|
||||
//Note that elements copied to the view with copyViewElementsToEAforEditing retain their
|
||||
//position in the stack of elements in the view even if modified using EA
|
||||
newElementsOnTop?: boolean, //default is false, i.e. the new elements get to the bottom of the stack
|
||||
): Promise<boolean>;
|
||||
onDropHook(data: {
|
||||
//if set Excalidraw will call this function onDrop events
|
||||
ea: ExcalidrawAutomate;
|
||||
event: React.DragEvent<HTMLDivElement>;
|
||||
draggable: any; //Obsidian draggable object
|
||||
type: "file" | "text" | "unknown";
|
||||
payload: {
|
||||
files: TFile[]; //TFile[] array of dropped files
|
||||
text: string; //string
|
||||
/**
|
||||
* elements grouped by the highest level groups
|
||||
* @param elements
|
||||
* @returns
|
||||
*/
|
||||
getMaximumGroups(elements: ExcalidrawElement[]): ExcalidrawElement[][];
|
||||
/**
|
||||
* gets the largest element from a group. useful when a text element is grouped with a box, and you want to connect an arrow to the box
|
||||
* @param elements
|
||||
* @returns
|
||||
*/
|
||||
getLargestElement(elements: ExcalidrawElement[]): ExcalidrawElement;
|
||||
/**
|
||||
* Gets the groupId for the group that contains all the elements, or null if such a group does not exist
|
||||
* @param elements
|
||||
* @returns null or the groupId
|
||||
*/
|
||||
getCommonGroupForElements(elements: ExcalidrawElement[]): string;
|
||||
/**
|
||||
* Gets all the elements from elements[] that share one or more groupIds with element.
|
||||
* @param element
|
||||
* @param elements - typically all the non-deleted elements in the scene
|
||||
* @returns
|
||||
*/
|
||||
getElementsInTheSameGroupWithElement(element: ExcalidrawElement, elements: ExcalidrawElement[]): ExcalidrawElement[];
|
||||
/**
|
||||
* @param element
|
||||
* @param a
|
||||
* @param b
|
||||
* @param gap
|
||||
* @returns 2 or 0 intersection points between line going through `a` and `b`
|
||||
* and the `element`, in ascending order of distance from `a`.
|
||||
*/
|
||||
intersectElementWithLine(element: ExcalidrawBindableElement, a: readonly [number, number], b: readonly [number, number], gap?: number): Point[];
|
||||
/**
|
||||
* See OCR plugin for example on how to use scriptSettings
|
||||
* Set by the ScriptEngine
|
||||
*/
|
||||
activeScript: string;
|
||||
/**
|
||||
*
|
||||
* @returns script settings. Saves settings in plugin settings, under the activeScript key
|
||||
*/
|
||||
getScriptSettings(): {};
|
||||
/**
|
||||
* sets script settings.
|
||||
* @param settings
|
||||
* @returns
|
||||
*/
|
||||
setScriptSettings(settings: any): Promise<void>;
|
||||
/**
|
||||
* Open a file in a new workspaceleaf or reuse an existing adjacent leaf depending on Excalidraw Plugin Settings
|
||||
* @param file
|
||||
* @returns
|
||||
*/
|
||||
openFileInNewOrAdjacentLeaf(file: TFile): WorkspaceLeaf;
|
||||
/**
|
||||
* measure text size based on current style settings
|
||||
* @param text
|
||||
* @returns
|
||||
*/
|
||||
measureText(text: string): {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
excalidrawFile: TFile; //the file receiving the drop event
|
||||
view: ExcalidrawView; //the excalidraw view receiving the drop
|
||||
pointerPosition: { x: number; y: number }; //the pointer position on canvas at the time of drop
|
||||
}): boolean; //a return of true will stop the default onDrop processing in Excalidraw
|
||||
mostRecentMarkdownSVG: SVGSVGElement; //Markdown renderer will drop a copy of the most recent SVG here for debugging purposes
|
||||
getEmbeddedFilesLoader(isDark?: boolean): EmbeddedFilesLoader; //utility function to generate EmbeddedFilesLoader object
|
||||
getExportSettings( //utility function to generate ExportSettings object
|
||||
withBackground: boolean,
|
||||
withTheme: boolean,
|
||||
): ExportSettings;
|
||||
getBoundingBox(elements: ExcalidrawElement[]): {
|
||||
//get bounding box of elements
|
||||
topX: number; //bounding box is the box encapsulating all of the elements completely
|
||||
topY: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
//elements grouped by the highest level groups
|
||||
getMaximumGroups(elements: ExcalidrawElement[]): ExcalidrawElement[][];
|
||||
//gets the largest element from a group. useful when a text element is grouped with a box, and you want to connect an arrow to the box
|
||||
getLargestElement(elements: ExcalidrawElement[]): ExcalidrawElement;
|
||||
// Returns 2 or 0 intersection points between line going through `a` and `b`
|
||||
// and the `element`, in ascending order of distance from `a`.
|
||||
intersectElementWithLine(
|
||||
element: ExcalidrawBindableElement,
|
||||
a: readonly [number, number],
|
||||
b: readonly [number, number],
|
||||
gap?: number, //if given, element is inflated by this value
|
||||
): Point[];
|
||||
|
||||
//See OCR plugin for example on how to use scriptSettings
|
||||
activeScript: string; //Set automatically by the ScriptEngine
|
||||
getScriptSettings(): {}; //Returns script settings. Saves settings in plugin settings, under the activeScript key
|
||||
setScriptSettings(settings: any): Promise<void>; //sets script settings.
|
||||
openFileInNewOrAdjacentLeaf(file: TFile): WorkspaceLeaf; //Open a file in a new workspaceleaf or reuse an existing adjacent leaf depending on Excalidraw Plugin Settings
|
||||
measureText(text: string): { width: number; height: number }; //measure text size based on current style settings
|
||||
//verifyMinimumPluginVersion returns true if plugin version is >= than required
|
||||
//recommended use:
|
||||
//if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.20")) {new Notice("message");return;}
|
||||
verifyMinimumPluginVersion(requiredVersion: string): boolean;
|
||||
selectElementsInView(elements: ExcalidrawElement[]): void; //sets selection in view
|
||||
generateElementId(): string; //returns an 8 character long random id
|
||||
cloneElement(element: ExcalidrawElement): ExcalidrawElement; //Returns a clone of the element with a new id
|
||||
moveViewElementToZIndex(elementId:number, newZIndex:number): void; //Moves the element to a specific position in the z-index
|
||||
hexStringToRgb(color: string):number[];
|
||||
rgbToHexString(color: number[]):string;
|
||||
hslToRgb(color: number[]):number[];
|
||||
rgbToHsl(color:number[]):number[];
|
||||
colorNameToHex(color:string):string;
|
||||
}
|
||||
```
|
||||
/**
|
||||
* verifyMinimumPluginVersion returns true if plugin version is >= than required
|
||||
* recommended use:
|
||||
* if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.20")) {new Notice("message");return;}
|
||||
* @param requiredVersion
|
||||
* @returns
|
||||
*/
|
||||
verifyMinimumPluginVersion(requiredVersion: string): boolean;
|
||||
/**
|
||||
* Check if view is instance of ExcalidrawView
|
||||
* @param view
|
||||
* @returns
|
||||
*/
|
||||
isExcalidrawView(view: any): boolean;
|
||||
/**
|
||||
* sets selection in view
|
||||
* @param elements
|
||||
* @returns
|
||||
*/
|
||||
selectElementsInView(elements: ExcalidrawElement[]): void;
|
||||
/**
|
||||
* @returns an 8 character long random id
|
||||
*/
|
||||
generateElementId(): string;
|
||||
/**
|
||||
* @param element
|
||||
* @returns a clone of the element with a new id
|
||||
*/
|
||||
cloneElement(element: ExcalidrawElement): ExcalidrawElement;
|
||||
/**
|
||||
* Moves the element to a specific position in the z-index
|
||||
*/
|
||||
moveViewElementToZIndex(elementId: number, newZIndex: number): void;
|
||||
/**
|
||||
*
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
hexStringToRgb(color: string): number[];
|
||||
/**
|
||||
*
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
rgbToHexString(color: number[]): string;
|
||||
/**
|
||||
*
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
hslToRgb(color: number[]): number[];
|
||||
/**
|
||||
*
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
rgbToHsl(color: number[]): number[];
|
||||
/**
|
||||
*
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
colorNameToHex(color: string): string;
|
||||
}```
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ settings = ea.getScriptSettings();
|
||||
if(!settings["Border color"]) {
|
||||
settings = {
|
||||
"Border color" : {
|
||||
value: "#000000",
|
||||
value: "transparent",
|
||||
description: "Any legal HTML color (#000000, rgb, color-name, etc.). Set to 'transparent' for transparent color."
|
||||
},
|
||||
"Background color" : {
|
||||
|
||||
67
ea-scripts/Grid Selected Images.md
Normal file
67
ea-scripts/Grid Selected Images.md
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||

|
||||
|
||||
This script arranges selected images into compact grid view, removing gaps in-between, resizing when necessary and breaking into multiple rows/columns.
|
||||
|
||||
```javascript
|
||||
*/
|
||||
|
||||
try {
|
||||
let els = ea.getViewSelectedElements().filter(el => el.type == 'image');
|
||||
|
||||
new Notice(els.length);
|
||||
|
||||
if (els.length == 0) throw new Error('No image elements selected');
|
||||
|
||||
const bounds = ea.getBoundingBox(els);
|
||||
const { topX, topY, width, height } = bounds;
|
||||
|
||||
els.sort((a, b) => a.x + a.y < b.x + b.y);
|
||||
|
||||
const areaAvailable = width * height;
|
||||
|
||||
let elWidth = els[0].width;
|
||||
let elHeight = els[0].height;
|
||||
|
||||
if (elWidth * elHeight > areaAvailable) {
|
||||
while (elWidth * elHeight > areaAvailable) {
|
||||
elWidth /= 1.1;
|
||||
elHeight /= 1.1;
|
||||
}
|
||||
} else if (elWidth * elHeight < areaAvailable) {
|
||||
while (elWidth * elHeight > areaAvailable) {
|
||||
elWidth *= 1.1;
|
||||
elHeight *= 1.1;
|
||||
}
|
||||
}
|
||||
|
||||
const rows = (width - elWidth) / elWidth;
|
||||
|
||||
let row = 0, column = 0;
|
||||
for (const element of els) {
|
||||
element.x = topX + (elWidth * row);
|
||||
element.y = topY + (elHeight * column);
|
||||
|
||||
if (element.width > elWidth) {
|
||||
while (element.width >= elWidth) {
|
||||
element.width /= 1.1;
|
||||
element.height /= 1.1;
|
||||
}
|
||||
} else if (element.width < elWidth) {
|
||||
while (element.width <= elWidth) {
|
||||
element.width *= 1.1;
|
||||
element.height *= 1.1;
|
||||
}
|
||||
}
|
||||
|
||||
row++;
|
||||
if (row > rows) {
|
||||
row = 0;
|
||||
column++;
|
||||
}
|
||||
}
|
||||
|
||||
ea.addElementsToView(false, true, true);
|
||||
} catch (err) {
|
||||
_ = new Notice(err.toString())
|
||||
}
|
||||
2
ea-scripts/Grid Selected Images.svg
Normal file
2
ea-scripts/Grid Selected Images.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 376.3492303436433 256.64929991179815" stroke="#000">
|
||||
<g stroke-linecap="round" transform="translate(10 10) rotate(0 58.5858154296875 58.5858154296875)"><path d="M-1.64 1.57 C38.67 2.87, 77.63 0.2, 117.31 1.14 M-0.05 -0.5 C40.75 0.3, 82.48 0.12, 118.09 0.99 M116.67 0.11 C118.02 47.69, 116.43 92.18, 118.63 118.15 M117.41 0.39 C116.57 38.93, 117.51 79.53, 116.57 116.49 M118.65 117.22 C92.96 119.01, 66.56 117.46, -0.42 117.17 M117.21 116.56 C91.43 117.53, 63.4 117.03, -0.23 117.71 M1.5 116.9 C-2.3 91.83, 1.37 68.12, -0.05 -0.85 M-0.84 116.86 C-1.03 87.92, -0.86 61.1, 0.54 0.72" stroke-width="2" fill="none"></path></g><g stroke-linecap="round" transform="translate(128.96603337932902 10.2586194164669) rotate(0 58.5858154296875 58.5858154296875)"><path d="M0.05 1.78 C24.68 -1.8, 48.73 0.22, 118.17 0.33 M-0.27 0.22 C34.72 0.61, 69.52 1.58, 118.12 0.36 M117.6 1.43 C115.71 34.53, 118.33 70.74, 117.89 116.98 M116.68 -0.46 C117.9 41.7, 116.85 81.21, 116.34 117.29 M118.4 119.11 C83.22 117.59, 48.31 117.2, -1.44 117.99 M118.15 116.77 C74.14 115.56, 31.55 116.99, -0.96 117.69 M-1.69 115.77 C1.1 78.33, -0.93 41, -1.14 1.03 M-0.43 116.37 C0.72 90.14, 1.52 62.17, 0.55 -0.66" stroke-width="2" fill="none"></path></g><g stroke-linecap="round" transform="translate(248.3020093440573 10.465294492851172) rotate(0 58.5858154296875 58.5858154296875)"><path d="M0.39 -0.88 C44.51 0.1, 89.74 -0.71, 117.03 -0.11 M0.42 0.03 C30.22 0.74, 59.56 0.11, 117.15 -0.25 M116 -0.35 C118.88 36.78, 118.14 71.41, 118.16 117.65 M117.62 0.75 C115.99 39.07, 115.76 79.63, 117.09 117.91 M117.44 117.56 C81.29 118.3, 44.81 119.12, 1.29 117.25 M116.84 116.27 C88.98 117.24, 60.29 117.22, 0.21 117.92 M1.97 115.99 C0.94 82.75, -0.15 51.05, -1.24 -1.67 M-0.26 118 C1.05 86.36, 1.06 53.45, 0.95 0.65" stroke-width="2" fill="none"></path></g><g stroke-linecap="round" transform="translate(10.875590140210988 129.01237455957016) rotate(0 58.5858154296875 58.5858154296875)"><path d="M0.24 -0.16 C29.59 1.02, 57.45 2.91, 117.44 0.38 M0.41 0.64 C32.74 0.23, 66.39 0.45, 116.84 -0.9 M118.21 0.43 C119.3 32.21, 116.11 63.55, 119.15 115.99 M117.68 -0.62 C115.84 34.54, 116.24 68.9, 116.91 118 M115.86 119.08 C76.96 118.61, 32.48 117.59, -1.75 116.45 M117.39 116.77 C85.7 116.42, 54.91 116.16, 0.1 118.13 M-0.18 118.45 C-0.52 71.18, -0.96 24.41, 1.23 0.16 M0.3 117.66 C0.57 92.18, 0.07 65.33, -0.4 0.17" stroke-width="2" fill="none"></path></g><g stroke-linecap="round" transform="translate(129.84162351954 129.27099397603888) rotate(0 58.5858154296875 58.5858154296875)"><path d="M-0.16 1.48 C27.24 1.09, 56.31 0.13, 117.56 -0.42 M0.64 0.04 C34.52 0.5, 69.88 -0.81, 116.27 -0.23 M117.6 1.5 C115.78 39.75, 114.64 84.85, 115.99 117.12 M116.55 -0.84 C116.19 29.74, 115.7 60.09, 118 117.71 M119.08 118.46 C74.37 114.72, 29.76 114.66, -0.72 116.45 M116.77 116.64 C81.73 117.03, 46.47 117.52, 0.96 118.05 M1.28 117.07 C0.37 77.34, -0.95 39.83, 0.16 -1.64 M0.49 117.63 C0.17 83.07, -1.11 48.64, 0.17 0.23" stroke-width="2" fill="none"></path></g><g stroke-linecap="round" transform="translate(249.17759948426828 129.47766905242315) rotate(0 58.5858154296875 58.5858154296875)"><path d="M1.48 0.05 C26.67 2.03, 51.16 0.48, 116.75 0 M0.04 -0.61 C27.7 -1.15, 53.12 -1.65, 116.94 0.54 M118.67 -0.27 C116.09 22.53, 119.75 46.69, 117.12 116.32 M116.34 -0.31 C117.34 27.65, 117.51 57.73, 117.71 117.89 M118.46 117.96 C73.87 118.06, 32.19 117.57, -0.72 117.14 M116.64 116.71 C84.55 115.98, 52.82 117.66, 0.88 117.52 M-0.11 116.63 C-0.44 89.26, 1.33 65.24, -1.64 -0.19 M0.46 117.75 C-0.38 77.25, -0.75 38.91, 0.23 0.69" stroke-width="2" fill="none"></path></g></svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
@@ -3,13 +3,28 @@ Click to watch the intro video:
|
||||
|
||||
[](https://youtu.be/hePJcObHIso)
|
||||
|
||||
> **Warning**
|
||||
> There is an easier way to install/manage scripts than what is shown in this video
|
||||
|
||||
See the [Excalidraw Script Engine](https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html) documentation for more details.
|
||||
|
||||
## How to install scripts into your Obsidian Vault
|
||||
To install one of the built-in scripts:
|
||||
- Open up an excalidraw drawing in Obsidian
|
||||
- In the pane dropdown menu select "Install or update Excalidraw Scripts"
|
||||
- Click on one of the available scripts
|
||||
- Click on "Install this script" (note if the script is already installed you will instead see an option to update it)
|
||||
- Restart Obsidian so the script will be picked up
|
||||
|
||||
Note: By default this will install the script into your vault in the `Excalidraw/Scripts/Downloaded` folder
|
||||
|
||||
<details><summary>Manual installation of scripts</summary>
|
||||
|
||||
Open the script you are interested in and save it to your Obsidian Vault including the first line `/*`, or open it in "Raw" and copy the entire contents to Obsidian.
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
## List of available scripts
|
||||
|Title|Description|Icon|Contributor|
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -6,7 +6,7 @@ If you are enjoying the Excalidraw plugin then please support my work and enthus
|
||||
|
||||
Jump ahead to the [[#List of available scripts]]
|
||||
|
||||
# Intorducing Excalidraw Automate Script Engine
|
||||
# Introducing Excalidraw Automate Script Engine
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/hePJcObHIso" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
|
||||
Script Engine scripts are installed in the `Downloaded` subfolder of the `Excalidraw Automate script folder` specified in plugin settings.
|
||||
@@ -51,6 +51,7 @@ I would love to include your contribution in the script library. If you have a s
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20spacing.svg"/></div>|[[#Fixed spacing]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20vertical%20distance%20between%20centers.svg"/></div>|[[#Fixed vertical distance between centers]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Fixed%20vertical%20distance.svg"/></div>|[[#Fixed vertical distance]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Grid%20Selected%20Images.svg"/></div>|[[#Grid selected images]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Lighten%20background%20color.svg"/></div>|[[#Lighten background color]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Mindmap%20connector.svg"/></div>|[[#Mindmap connector]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Modify%20background%20color%20opacity.svg"/></div>|[[#Modify background color opacity]]|
|
||||
@@ -216,6 +217,12 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/1-2-3'>@1-2-3</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Fixed%20vertical%20distance.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script arranges the selected elements vertically with a fixed spacing. When we create an architecture diagram or mind map, we often need to arrange a large number of elements in a fixed spacing. `Fixed spacing` and `Fixed vertical Distance` scripts can save us a lot of time.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fixed-vertical-distance.png'></td></tr></table>
|
||||
|
||||
## Grid selected images
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Grid%20Selected%20Images.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/7flash'>@7flash</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Grid%20Selected%20Images.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script arranges selected images into compact grid view, removing gaps in-between, resizing when necessary and breaking into multiple rows/columns.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-grid-selected-images.png'></td></tr></table>
|
||||
|
||||
## Lighten background color
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Lighten%20background%20color.md
|
||||
|
||||
@@ -6,7 +6,7 @@ If you are enjoying the Excalidraw plugin then please support my work and enthus
|
||||
|
||||
Jump ahead to the [[#List of available scripts]]
|
||||
|
||||
# Intorducing Excalidraw Automate Script Engine
|
||||
# Introducing Excalidraw Automate Script Engine
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/hePJcObHIso" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
|
||||
Script Engine scripts are installed in the `Downloaded` subfolder of the `Excalidraw Automate script folder` specified in plugin settings.
|
||||
|
||||
BIN
images/scripts-grid-selected-images.png
Normal file
BIN
images/scripts-grid-selected-images.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 113 KiB |
BIN
images/scripts-sketch-palette-loader-1.jpg
Normal file
BIN
images/scripts-sketch-palette-loader-1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
BIN
images/scripts-sketch-palette-loader-2.jpg
Normal file
BIN
images/scripts-sketch-palette-loader-2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
BIN
images/scripts-sketch-palette-loader-3.jpg
Normal file
BIN
images/scripts-sketch-palette-loader-3.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 156 KiB |
BIN
images/scripts-sketch-palette-loader-4.jpg
Normal file
BIN
images/scripts-sketch-palette-loader-4.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 247 KiB |
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "1.7.9",
|
||||
"minAppVersion": "0.15.7",
|
||||
"version": "1.7.18",
|
||||
"minAppVersion": "0.15.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
"authorUrl": "https://zsolt.blog",
|
||||
|
||||
13
package.json
13
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "obsidian-excalidraw-plugin",
|
||||
"version": "1.7.6",
|
||||
"version": "1.7.11",
|
||||
"description": "This is an Obsidian.md plugin that lets you view and edit Excalidraw drawings",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
@@ -18,14 +18,15 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/lz-string": "^1.3.34",
|
||||
"@zsviczian/excalidraw": "0.12.0-obsidian-0",
|
||||
"@zsviczian/excalidraw": "0.12.0-obsidian-8",
|
||||
"clsx": "^1.1.1",
|
||||
"lz-string": "^1.4.4",
|
||||
"monkey-around": "^2.3.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-scripts": "^5.0.1",
|
||||
"roughjs": "^4.5.2"
|
||||
"roughjs": "^4.5.2",
|
||||
"colormaster": "1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.16.12",
|
||||
@@ -41,7 +42,7 @@
|
||||
"@rollup/plugin-typescript": "^8.3.0",
|
||||
"@types/js-beautify": "^1.13.3",
|
||||
"@types/node": "^15.12.4",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/react-dom": "^17.0.2",
|
||||
"@zerollup/ts-transform-paths": "^1.7.18",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint-config-prettier": "8.3.0",
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
getFontDataURL,
|
||||
getImageSize,
|
||||
getLinkParts,
|
||||
getSVGPadding,
|
||||
getExportPadding,
|
||||
getWithBackground,
|
||||
hasExportBackground,
|
||||
hasExportTheme,
|
||||
@@ -92,7 +92,7 @@ export class EmbeddedFile {
|
||||
if (!this.linkParts.height) {
|
||||
this.linkParts.height = this.plugin.settings.mdSVGmaxHeight;
|
||||
}
|
||||
this.file = this.plugin.app.metadataCache.getFirstLinkpathDest(
|
||||
this.file = app.metadataCache.getFirstLinkpathDest(
|
||||
this.linkParts.path,
|
||||
hostPath,
|
||||
);
|
||||
@@ -108,7 +108,7 @@ export class EmbeddedFile {
|
||||
|
||||
private fileChanged(): boolean {
|
||||
if (!this.file) {
|
||||
this.file = this.plugin.app.metadataCache.getFirstLinkpathDest(
|
||||
this.file = app.metadataCache.getFirstLinkpathDest(
|
||||
this.linkParts.path,
|
||||
this.hostPath,
|
||||
); // maybe the file has synchronized in the mean time
|
||||
@@ -149,7 +149,7 @@ export class EmbeddedFile {
|
||||
|
||||
public isLoaded(isDark: boolean): boolean {
|
||||
if (!this.file) {
|
||||
this.file = this.plugin.app.metadataCache.getFirstLinkpathDest(
|
||||
this.file = app.metadataCache.getFirstLinkpathDest(
|
||||
this.linkParts.path,
|
||||
this.hostPath,
|
||||
); // maybe the file has synchronized in the mean time
|
||||
@@ -218,7 +218,6 @@ export class EmbeddedFilesLoader {
|
||||
};
|
||||
|
||||
let hasSVGwithBitmap = false;
|
||||
const app = this.plugin.app;
|
||||
const isExcalidrawFile = this.plugin.isExcalidrawFile(file);
|
||||
if (
|
||||
!(
|
||||
@@ -253,7 +252,7 @@ export class EmbeddedFilesLoader {
|
||||
[],
|
||||
this.plugin,
|
||||
depth+1,
|
||||
getSVGPadding(this.plugin, file),
|
||||
getExportPadding(this.plugin, file),
|
||||
);
|
||||
//https://stackoverflow.com/questions/51154171/remove-css-filter-on-child-elements
|
||||
const imageList = svg.querySelectorAll(
|
||||
@@ -518,12 +517,13 @@ export class EmbeddedFilesLoader {
|
||||
for(let i=0;i<internalEmbeds.length;i++) {
|
||||
const el = internalEmbeds[i];
|
||||
const src = el.getAttribute("src");
|
||||
if(!src) return;
|
||||
if(!src) continue;
|
||||
const width = el.getAttribute("width");
|
||||
const height = el.getAttribute("height");
|
||||
const f = app.metadataCache.getFirstLinkpathDest(src,file.path);
|
||||
if(!f) return;
|
||||
const embeddedFile = await this.getObsidianImage(f,1);
|
||||
const ef = new EmbeddedFile(plugin,file.path,src);
|
||||
//const f = app.metadataCache.getFirstLinkpathDest(src.split("#")[0],file.path);
|
||||
if(!ef.file) continue;
|
||||
const embeddedFile = await this.getObsidianImage(ef,1);
|
||||
const img = createEl("img");
|
||||
if(width) img.setAttribute("width", width);
|
||||
if(height) img.setAttribute("height", height);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,7 @@ import {
|
||||
REG_BLOCK_REF_CLEAN,
|
||||
FRONTMATTER_KEY_LINKBUTTON_OPACITY,
|
||||
FRONTMATTER_KEY_ONLOAD_SCRIPT,
|
||||
FRONTMATTER_KEY_AUTOEXPORT,
|
||||
} from "./Constants";
|
||||
import { _measureText } from "./ExcalidrawAutomate";
|
||||
import ExcalidrawPlugin from "./main";
|
||||
@@ -51,6 +52,14 @@ declare module "obsidian" {
|
||||
}
|
||||
}
|
||||
|
||||
export enum AutoexportPreference {
|
||||
none,
|
||||
both,
|
||||
png,
|
||||
svg,
|
||||
inherit
|
||||
}
|
||||
|
||||
export const REGEX_LINK = {
|
||||
//![[link|alias]] [alias](link){num}
|
||||
// 1 2 3 4 5 67 8 9
|
||||
@@ -224,6 +233,7 @@ export class ExcalidrawData {
|
||||
private showLinkBrackets: boolean;
|
||||
private linkPrefix: string;
|
||||
private urlPrefix: string;
|
||||
public autoexportPreference: AutoexportPreference = AutoexportPreference.inherit;
|
||||
private textMode: TextMode = TextMode.raw;
|
||||
public loaded: boolean = false;
|
||||
private files: Map<FileId, EmbeddedFile> = null; //fileId, path
|
||||
@@ -401,6 +411,7 @@ export class ExcalidrawData {
|
||||
this.setShowLinkBrackets();
|
||||
this.setLinkPrefix();
|
||||
this.setUrlPrefix();
|
||||
this.setAutoexportPreferences();
|
||||
|
||||
this.scene = null;
|
||||
|
||||
@@ -577,6 +588,7 @@ export class ExcalidrawData {
|
||||
}
|
||||
|
||||
public async setTextMode(textMode: TextMode, forceupdate: boolean = false) {
|
||||
if(!this.scene) return;
|
||||
this.textMode = textMode;
|
||||
await this.updateSceneTextElements(forceupdate);
|
||||
}
|
||||
@@ -728,7 +740,13 @@ export class ExcalidrawData {
|
||||
this.textElements.set(id, { raw, parsed: null, wrapAt });
|
||||
this.parseasync(id, raw, wrapAt);
|
||||
}
|
||||
} else if (!this.textElements.has(te.id)) {
|
||||
const raw = te.rawText && te.rawText !== "" ? te.rawText : te.text; //this is for compatibility with drawings created before the rawText change on ExcalidrawTextElement
|
||||
const wrapAt = estimateMaxLineLen(te.text, te.originalText);
|
||||
this.textElements.set(id, { raw, parsed: null, wrapAt });
|
||||
this.parseasync(id, raw, wrapAt);
|
||||
}
|
||||
|
||||
}
|
||||
if (dirty) {
|
||||
//reload scene json in case it has changed
|
||||
@@ -770,11 +788,14 @@ export class ExcalidrawData {
|
||||
this.textElements.delete(key); //if no longer in the scene, delete the text element
|
||||
} else {
|
||||
const text = await this.getText(key, false);
|
||||
const raw = this.scene.prevTextMode === TextMode.parsed
|
||||
? el[0].rawText
|
||||
: (el[0].originalText ?? el[0].text);
|
||||
if (text !== (el[0].originalText ?? el[0].text)) {
|
||||
const wrapAt = estimateMaxLineLen(el[0].text, el[0].originalText);
|
||||
this.textElements.set(key, {
|
||||
raw: el[0].originalText ?? el[0].text,
|
||||
parsed: (await this.parse(el[0].originalText ?? el[0].text)).parsed,
|
||||
raw,
|
||||
parsed: (await this.parse(raw)).parsed,
|
||||
wrapAt,
|
||||
});
|
||||
}
|
||||
@@ -1320,6 +1341,24 @@ export class ExcalidrawData {
|
||||
return urlPrefix != this.urlPrefix;
|
||||
}
|
||||
|
||||
private setAutoexportPreferences() {
|
||||
const fileCache = this.app.metadataCache.getFileCache(this.file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEY_AUTOEXPORT] != null
|
||||
) {
|
||||
switch ((fileCache.frontmatter[FRONTMATTER_KEY_AUTOEXPORT]).toLowerCase()) {
|
||||
case "none": this.autoexportPreference = AutoexportPreference.none; break;
|
||||
case "both": this.autoexportPreference = AutoexportPreference.both; break;
|
||||
case "png": this.autoexportPreference = AutoexportPreference.png; break;
|
||||
case "svg": this.autoexportPreference = AutoexportPreference.svg; break;
|
||||
default: this.autoexportPreference = AutoexportPreference.inherit;
|
||||
};
|
||||
} else {
|
||||
this.autoexportPreference = AutoexportPreference.inherit;
|
||||
}
|
||||
}
|
||||
|
||||
private setShowLinkBrackets(): boolean {
|
||||
const showLinkBrackets = this.showLinkBrackets;
|
||||
const fileCache = this.app.metadataCache.getFileCache(this.file);
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
MarkdownView,
|
||||
request,
|
||||
Platform,
|
||||
requireApiVersion,
|
||||
} from "obsidian";
|
||||
//import * as React from "react";
|
||||
//import * as ReactDOM from "react-dom";
|
||||
@@ -42,12 +43,13 @@ import {
|
||||
LOCAL_PROTOCOL,
|
||||
} from "./Constants";
|
||||
import ExcalidrawPlugin from "./main";
|
||||
import { repositionElementsToCursor, ExcalidrawAutomate } from "./ExcalidrawAutomate";
|
||||
import { repositionElementsToCursor, ExcalidrawAutomate, getTextElementsMatchingQuery, cloneElement } from "./ExcalidrawAutomate";
|
||||
import { t } from "./lang/helpers";
|
||||
import {
|
||||
ExcalidrawData,
|
||||
REG_LINKINDEX_HYPERLINK,
|
||||
REGEX_LINK,
|
||||
AutoexportPreference,
|
||||
} from "./ExcalidrawData";
|
||||
import {
|
||||
checkAndCreateFolder,
|
||||
@@ -60,15 +62,18 @@ import {
|
||||
debug,
|
||||
embedFontsInSVG,
|
||||
errorlog,
|
||||
getEmbeddedFilenameParts,
|
||||
getExportTheme,
|
||||
getLinkParts,
|
||||
getPNG,
|
||||
getPNGScale,
|
||||
getSVG,
|
||||
getSVGPadding,
|
||||
getExportPadding,
|
||||
getWithBackground,
|
||||
hasExportTheme,
|
||||
isVersionNewerThanOther,
|
||||
scaleLoadedImage,
|
||||
setDocLeftHandedMode,
|
||||
svgToBase64,
|
||||
viewportCoordsToSceneCoords,
|
||||
} from "./utils/Utils";
|
||||
@@ -88,10 +93,9 @@ import { ToolsPanel } from "./menu/ToolsPanel";
|
||||
import { ScriptEngine } from "./Scripts";
|
||||
import { getTextElementAtPointer, getImageElementAtPointer, getElementWithLinkAtPointer } from "./utils/GetElementAtPointer";
|
||||
|
||||
|
||||
export enum TextMode {
|
||||
parsed,
|
||||
raw,
|
||||
parsed = "parsed",
|
||||
raw = "raw",
|
||||
}
|
||||
|
||||
interface WorkspaceItemExt extends WorkspaceItem {
|
||||
@@ -129,7 +133,7 @@ export const addFiles = async (
|
||||
}
|
||||
if (s.dirty) {
|
||||
//debug({where:"ExcalidrawView.addFiles",file:view.file.name,dataTheme:view.excalidrawData.scene.appState.theme,before:"updateScene",state:scene.appState})
|
||||
view.updateScene({
|
||||
await view.updateScene({
|
||||
elements: s.scene.elements,
|
||||
appState: s.scene.appState,
|
||||
commitToHistory: false,
|
||||
@@ -227,6 +231,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
//This semaphore helps avoid collision of saves
|
||||
saving: boolean;
|
||||
hoverSleep: boolean; //flag with timer to prevent hover preview from being triggered dozens of times
|
||||
wheelTimeout:NodeJS.Timeout; //used to avoid hover preview while zooming
|
||||
} = {
|
||||
viewunload: false,
|
||||
scriptsReady: false,
|
||||
@@ -239,6 +244,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
saving: false,
|
||||
forceSaving: false,
|
||||
hoverSleep: false,
|
||||
wheelTimeout: null,
|
||||
};
|
||||
|
||||
public plugin: ExcalidrawPlugin;
|
||||
@@ -246,6 +252,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
public textMode: TextMode = TextMode.raw;
|
||||
private textIsParsed_Element: HTMLElement;
|
||||
private textIsRaw_Element: HTMLElement;
|
||||
private linkAction_Element: HTMLElement;
|
||||
public compatibilityMode: boolean = false;
|
||||
private obsidianMenu: ObsidianMenu;
|
||||
|
||||
@@ -273,7 +280,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
|
||||
preventAutozoom() {
|
||||
this.semaphores.preventAutozoom = true;
|
||||
setTimeout(() => (this.semaphores.preventAutozoom = false), 2000);
|
||||
setTimeout(() => (this.semaphores.preventAutozoom = false), 1500);
|
||||
}
|
||||
|
||||
public saveExcalidraw(scene?: any) {
|
||||
@@ -313,7 +320,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
filename = `${filename}.excalidraw`;
|
||||
const folderpath = splitFolderAndFilename(this.file.path).folderpath;
|
||||
await checkAndCreateFolder(app.vault, folderpath); //create folder if it does not exist
|
||||
await checkAndCreateFolder(folderpath); //create folder if it does not exist
|
||||
const fname = getNewUniqueFilepath(
|
||||
app.vault,
|
||||
filename,
|
||||
@@ -334,7 +341,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
);
|
||||
}
|
||||
|
||||
public async svg(scene: any): Promise<SVGSVGElement> {
|
||||
public async svg(scene: any, theme?:string): Promise<SVGSVGElement> {
|
||||
const exportSettings: ExportSettings = {
|
||||
withBackground: getWithBackground(this.plugin, this.file),
|
||||
withTheme: true,
|
||||
@@ -345,12 +352,12 @@ export default class ExcalidrawView extends TextFileView {
|
||||
...{
|
||||
appState: {
|
||||
...scene.appState,
|
||||
theme: getExportTheme(this.plugin, this.file, scene.appState.theme),
|
||||
theme: theme ?? getExportTheme(this.plugin, this.file, scene.appState.theme),
|
||||
},
|
||||
},
|
||||
},
|
||||
exportSettings,
|
||||
getSVGPadding(this.plugin, this.file),
|
||||
getExportPadding(this.plugin, this.file),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -361,25 +368,35 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
scene = this.getScene();
|
||||
}
|
||||
const filepath = getIMGFilename(this.file.path, "svg"); //.substring(0,this.file.path.lastIndexOf(this.compatibilityMode ? '.excalidraw':'.md')) + '.svg';
|
||||
const file = app.vault.getAbstractFileByPath(normalizePath(filepath));
|
||||
|
||||
const svg = await this.svg(scene);
|
||||
if (!svg) {
|
||||
return;
|
||||
const exportImage = async (filepath:string, theme?:string) => {
|
||||
const file = app.vault.getAbstractFileByPath(normalizePath(filepath));
|
||||
|
||||
const svg = await this.svg(scene,theme);
|
||||
if (!svg) {
|
||||
return;
|
||||
}
|
||||
const serializer = new XMLSerializer();
|
||||
const svgString = serializer.serializeToString(
|
||||
embedFontsInSVG(svg, this.plugin),
|
||||
);
|
||||
if (file && file instanceof TFile) {
|
||||
await app.vault.modify(file, svgString);
|
||||
} else {
|
||||
await app.vault.create(filepath, svgString);
|
||||
}
|
||||
}
|
||||
const serializer = new XMLSerializer();
|
||||
const svgString = serializer.serializeToString(
|
||||
embedFontsInSVG(svg, this.plugin),
|
||||
);
|
||||
if (file && file instanceof TFile) {
|
||||
await app.vault.modify(file, svgString);
|
||||
|
||||
if(this.plugin.settings.autoExportLightAndDark) {
|
||||
await exportImage(getIMGFilename(this.file.path, "dark.svg"),"dark");
|
||||
await exportImage(getIMGFilename(this.file.path, "light.svg"),"light");
|
||||
} else {
|
||||
await app.vault.create(filepath, svgString);
|
||||
await exportImage(getIMGFilename(this.file.path, "svg"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async png(scene: any): Promise<Blob> {
|
||||
public async png(scene: any, theme?:string): Promise<Blob> {
|
||||
const exportSettings: ExportSettings = {
|
||||
withBackground: getWithBackground(this.plugin, this.file),
|
||||
withTheme: true,
|
||||
@@ -390,11 +407,12 @@ export default class ExcalidrawView extends TextFileView {
|
||||
...{
|
||||
appState: {
|
||||
...scene.appState,
|
||||
theme: getExportTheme(this.plugin, this.file, scene.appState.theme),
|
||||
theme: theme ?? getExportTheme(this.plugin, this.file, scene.appState.theme),
|
||||
},
|
||||
},
|
||||
},
|
||||
exportSettings,
|
||||
getExportPadding(this.plugin, this.file),
|
||||
getPNGScale(this.plugin, this.file),
|
||||
);
|
||||
}
|
||||
@@ -407,17 +425,25 @@ export default class ExcalidrawView extends TextFileView {
|
||||
scene = this.getScene();
|
||||
}
|
||||
|
||||
const filepath = getIMGFilename(this.file.path, "png"); //this.file.path.substring(0,this.file.path.lastIndexOf(this.compatibilityMode ? '.excalidraw':'.md')) + '.png';
|
||||
const file = app.vault.getAbstractFileByPath(normalizePath(filepath));
|
||||
const exportImage = async (filepath:string, theme?:string) => {
|
||||
const file = app.vault.getAbstractFileByPath(normalizePath(filepath));
|
||||
|
||||
const png = await this.png(scene);
|
||||
if (!png) {
|
||||
return;
|
||||
const png = await this.png(scene,theme);
|
||||
if (!png) {
|
||||
return;
|
||||
}
|
||||
if (file && file instanceof TFile) {
|
||||
await app.vault.modifyBinary(file, await png.arrayBuffer());
|
||||
} else {
|
||||
await app.vault.createBinary(filepath, await png.arrayBuffer());
|
||||
}
|
||||
}
|
||||
if (file && file instanceof TFile) {
|
||||
await app.vault.modifyBinary(file, await png.arrayBuffer());
|
||||
|
||||
if(this.plugin.settings.autoExportLightAndDark) {
|
||||
await exportImage(getIMGFilename(this.file.path, "dark.png"),"dark");
|
||||
await exportImage(getIMGFilename(this.file.path, "light.png"),"light");
|
||||
} else {
|
||||
await app.vault.createBinary(filepath, await png.arrayBuffer());
|
||||
await exportImage(getIMGFilename(this.file.path, "png"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -487,10 +513,17 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
|
||||
if (!this.semaphores.autosaving && !this.semaphores.viewunload) {
|
||||
if (this.plugin.settings.autoexportSVG) {
|
||||
const autoexportPreference = this.excalidrawData.autoexportPreference;
|
||||
if (
|
||||
(autoexportPreference === AutoexportPreference.inherit && this.plugin.settings.autoexportSVG) ||
|
||||
autoexportPreference === AutoexportPreference.both || autoexportPreference === AutoexportPreference.svg
|
||||
) {
|
||||
await this.saveSVG();
|
||||
}
|
||||
if (this.plugin.settings.autoexportPNG) {
|
||||
if (
|
||||
(autoexportPreference === AutoexportPreference.inherit && this.plugin.settings.autoexportPNG) ||
|
||||
autoexportPreference === AutoexportPreference.both || autoexportPreference === AutoexportPreference.png
|
||||
) {
|
||||
await this.savePNG();
|
||||
}
|
||||
if (
|
||||
@@ -936,6 +969,9 @@ export default class ExcalidrawView extends TextFileView {
|
||||
return this.excalidrawGetSceneVersion(elements.filter(el=>!el.isDeleted));
|
||||
}
|
||||
|
||||
wheelEvent: (ev:WheelEvent)=>void;
|
||||
clearHoverPreview: Function;
|
||||
|
||||
onload() {
|
||||
const apiMissing = Boolean(typeof this.containerEl.onWindowMigrated === "undefined")
|
||||
//@ts-ignore
|
||||
@@ -945,6 +981,19 @@ export default class ExcalidrawView extends TextFileView {
|
||||
this.ownerWindow = this.ownerDocument.defaultView;
|
||||
this.plugin.getPackage(this.ownerWindow);
|
||||
this.semaphores.scriptsReady = true;
|
||||
|
||||
this.wheelEvent = (ev:WheelEvent) => {
|
||||
if(this.semaphores.wheelTimeout) clearTimeout(this.semaphores.wheelTimeout);
|
||||
if(this.semaphores.hoverSleep && this.clearHoverPreview) this.clearHoverPreview();
|
||||
this.semaphores.wheelTimeout = setTimeout(()=>{
|
||||
clearTimeout(this.semaphores.wheelTimeout);
|
||||
this.semaphores.wheelTimeout = null;
|
||||
},1000);
|
||||
}
|
||||
|
||||
this.containerEl.addEventListener("wheel", this.wheelEvent, {
|
||||
passive: false,
|
||||
});
|
||||
|
||||
this.addAction(SCRIPTENGINE_ICON_NAME, t("INSTALL_SCRIPT_BUTTON"), () => {
|
||||
new ScriptInstallPrompt(this.plugin).open();
|
||||
@@ -977,7 +1026,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
() => this.changeTextMode(TextMode.raw),
|
||||
);
|
||||
|
||||
this.addAction("link", t("OPEN_LINK"), (ev) =>
|
||||
this.linkAction_Element = this.addAction("link", t("OPEN_LINK"), (ev) =>
|
||||
this.handleLinkClick(this, ev),
|
||||
);
|
||||
|
||||
@@ -1015,6 +1064,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
});
|
||||
|
||||
this.setupAutosaveTimer();
|
||||
super.onload();
|
||||
}
|
||||
|
||||
//this is to solve sliding panes bug
|
||||
@@ -1114,7 +1164,12 @@ export default class ExcalidrawView extends TextFileView {
|
||||
});
|
||||
}
|
||||
|
||||
private prevTextMode: TextMode;
|
||||
private blockTextModeChange: boolean = false;
|
||||
public async changeTextMode(textMode: TextMode, reload: boolean = true) {
|
||||
if(this.compatibilityMode) return;
|
||||
if(this.blockTextModeChange) return;
|
||||
this.blockTextModeChange = true;
|
||||
this.textMode = textMode;
|
||||
if (textMode === TextMode.parsed) {
|
||||
this.textIsRaw_Element.hide();
|
||||
@@ -1126,15 +1181,17 @@ export default class ExcalidrawView extends TextFileView {
|
||||
if (this.toolsPanelRef && this.toolsPanelRef.current) {
|
||||
this.toolsPanelRef.current.setPreviewMode(textMode === TextMode.parsed);
|
||||
}
|
||||
if (reload) {
|
||||
await this.save(false, true);
|
||||
this.updateContainerSize();
|
||||
const api = this.excalidrawAPI;
|
||||
if (!api) {
|
||||
return;
|
||||
}
|
||||
const api = this.excalidrawAPI;
|
||||
if (api && reload) {
|
||||
await this.save();
|
||||
this.preventAutozoom();
|
||||
await this.excalidrawData.loadData(this.data, this.file, this.textMode);
|
||||
this.excalidrawData.scene.appState.theme = api.getAppState().theme;
|
||||
await this.loadDrawing(false);
|
||||
api.history.clear(); //to avoid undo replacing links with parsed text
|
||||
}
|
||||
this.prevTextMode = this.textMode;
|
||||
this.blockTextModeChange = false;
|
||||
}
|
||||
|
||||
public setupAutosaveTimer() {
|
||||
@@ -1203,6 +1260,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
this.semaphores.viewunload = true;
|
||||
this.ownerWindow?.removeEventListener("keydown", this.onKeyDown, false);
|
||||
this.ownerWindow?.removeEventListener("keyup", this.onKeyUp, false);
|
||||
this.containerEl.removeEventListener("wheel", this.wheelEvent, false);
|
||||
|
||||
if(this.getHookServer().onViewUnloadHook) {
|
||||
try {
|
||||
@@ -1269,21 +1327,26 @@ export default class ExcalidrawView extends TextFileView {
|
||||
this.clearDirty();
|
||||
}
|
||||
|
||||
zoomToElementId(id: string) {
|
||||
async zoomToElementId(id: string, hasGroupref:boolean) {
|
||||
let counter = 0;
|
||||
while (!this.excalidrawAPI && counter++<100) await sleep(50); //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/734
|
||||
const api = this.excalidrawAPI;
|
||||
if (!api) {
|
||||
return;
|
||||
}
|
||||
const elements = api
|
||||
.getSceneElements()
|
||||
.filter((el: ExcalidrawElement) => el.id === id);
|
||||
if (elements.length === 0) {
|
||||
return;
|
||||
const sceneElements = api.getSceneElements();
|
||||
|
||||
let elements = sceneElements.filter((el: ExcalidrawElement) => el.id === id);
|
||||
if(elements.length === 0) return;
|
||||
if(hasGroupref) {
|
||||
const groupElements = this.plugin.ea.getElementsInTheSameGroupWithElement(elements[0],sceneElements)
|
||||
if(groupElements.length>0) {
|
||||
elements = groupElements;
|
||||
}
|
||||
}
|
||||
if (!api.getAppState().viewModeEnabled) {
|
||||
api.selectElements(elements);
|
||||
}
|
||||
api.zoomToFit(elements, this.plugin.settings.zoomToFitMaxLevel, 0.05);
|
||||
|
||||
this.preventAutozoom();
|
||||
this.zoomToElements(!api.getAppState().viewModeEnabled, elements);
|
||||
}
|
||||
|
||||
setEphemeralState(state: any): void {
|
||||
@@ -1308,33 +1371,33 @@ export default class ExcalidrawView extends TextFileView {
|
||||
];
|
||||
}
|
||||
|
||||
if (state.subpath && state.subpath.length > 2) {
|
||||
if (state.subpath[1] === "^") {
|
||||
const id = state.subpath.substring(2);
|
||||
setTimeout(() => self.zoomToElementId(id), 300);
|
||||
} else {
|
||||
query = [`# ${state.subpath.substring(1)}`];
|
||||
}
|
||||
const filenameParts = getEmbeddedFilenameParts(state.subpath);
|
||||
if(filenameParts.hasBlockref) {
|
||||
setTimeout(()=>self.zoomToElementId(filenameParts.blockref, filenameParts.hasGroupref),300);
|
||||
}
|
||||
|
||||
if (state.line && state.line > 0) {
|
||||
if(filenameParts.hasSectionref) {
|
||||
query = [`# ${filenameParts.sectionref}`]
|
||||
} else if (state.line && state.line > 0) {
|
||||
query = [this.data.split("\n")[state.line - 1]];
|
||||
}
|
||||
|
||||
if (query) {
|
||||
setTimeout(() => {
|
||||
setTimeout(async () => {
|
||||
let counter = 0;
|
||||
while (!self.excalidrawAPI && counter++<100) await sleep(50); //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/734
|
||||
|
||||
const api = self.excalidrawAPI;
|
||||
if (!api) {
|
||||
return;
|
||||
}
|
||||
const elements = api
|
||||
.getSceneElements()
|
||||
.filter((el: ExcalidrawElement) => el.type === "text");
|
||||
const elements = api.getSceneElements();
|
||||
|
||||
self.selectElementsMatchingQuery(
|
||||
elements,
|
||||
query,
|
||||
!api.getAppState().viewModeEnabled,
|
||||
true,
|
||||
filenameParts.hasSectionref,
|
||||
filenameParts.hasGroupref
|
||||
);
|
||||
}, 300);
|
||||
}
|
||||
@@ -1360,7 +1423,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
|
||||
private isLoaded: boolean = false;
|
||||
async setViewData(data: string, clear: boolean = false) {
|
||||
checkExcalidrawVersion(app);
|
||||
if(this.plugin.settings.showNewVersionNotification) checkExcalidrawVersion(app);
|
||||
this.isLoaded = false;
|
||||
if (clear) {
|
||||
this.clear();
|
||||
@@ -1373,12 +1436,15 @@ export default class ExcalidrawView extends TextFileView {
|
||||
if (this.compatibilityMode) {
|
||||
this.textIsRaw_Element.hide();
|
||||
this.textIsParsed_Element.hide();
|
||||
this.linkAction_Element.hide();
|
||||
this.textMode = TextMode.raw;
|
||||
await this.excalidrawData.loadLegacyData(data, this.file);
|
||||
if (!this.plugin.settings.compatibilityMode) {
|
||||
new Notice(t("COMPATIBILITY_MODE"), 4000);
|
||||
}
|
||||
this.excalidrawData.disableCompression = true;
|
||||
} else {
|
||||
this.linkAction_Element.show();
|
||||
this.excalidrawData.disableCompression = false;
|
||||
const textMode = getTextMode(data);
|
||||
this.changeTextMode(textMode, false);
|
||||
@@ -1595,7 +1661,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
if(this.getSceneVersion(inData.scene.elements) !== this.previousSceneVersion) {
|
||||
this.setDirty(3);
|
||||
}
|
||||
this.excalidrawAPI.updateScene({elements: sceneElements});
|
||||
await this.updateScene({elements: sceneElements});
|
||||
if(reloadFiles) this.loadSceneFiles();
|
||||
} catch(e) {
|
||||
errorlog({
|
||||
@@ -1635,7 +1701,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
//debug({where:"ExcalidrawView.loadDrawing",file:this.file.name,dataTheme:excalidrawData.appState.theme,before:"updateScene"})
|
||||
api.setLocalFont(this.plugin.settings.experimentalEnableFourthFont);
|
||||
|
||||
this.updateScene(
|
||||
await this.updateScene(
|
||||
{
|
||||
elements: excalidrawData.elements.concat(deletedElements??[]), //need to preserve deleted elements during autosave if images, links, etc. are updated
|
||||
appState: {
|
||||
@@ -1707,6 +1773,12 @@ export default class ExcalidrawView extends TextFileView {
|
||||
//console.log(debug);
|
||||
this.semaphores.dirty = this.file?.path;
|
||||
this.diskIcon.querySelector("svg").addClass("excalidraw-dirty");
|
||||
if(!app.isMobile) {
|
||||
if(requireApiVersion("0.16.0")) {
|
||||
//@ts-ignore
|
||||
this.leaf.tabHeaderInnerTitleEl.style.color="var(--color-accent)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public clearDirty() {
|
||||
@@ -1720,6 +1792,12 @@ export default class ExcalidrawView extends TextFileView {
|
||||
this.previousSceneVersion = this.getSceneVersion(el);
|
||||
}
|
||||
this.diskIcon.querySelector("svg").removeClass("excalidraw-dirty");
|
||||
if(!app.isMobile) {
|
||||
if(requireApiVersion("0.16.0")) {
|
||||
//@ts-ignore
|
||||
this.leaf.tabHeaderInnerTitleEl.style.color=""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public initializeToolsIconPanelAfterLoading() {
|
||||
@@ -1781,10 +1859,22 @@ export default class ExcalidrawView extends TextFileView {
|
||||
this.plugin.openDrawing(
|
||||
await this.plugin.convertSingleExcalidrawToMD(this.file),
|
||||
"active-pane",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
onPaneMenu(menu: Menu, source: string): void {
|
||||
if(this.excalidrawAPI && this.getViewSelectedElements().some(el=>el.type==="text")) {
|
||||
menu.addItem(item => {
|
||||
item
|
||||
.setTitle(t("OPEN_LINK"))
|
||||
.setIcon("external-link")
|
||||
.setSection("pane")
|
||||
.onClick(evt => {
|
||||
this.handleLinkClick(this, evt as MouseEvent);
|
||||
});
|
||||
})
|
||||
}
|
||||
// Add a menu item to force the board to markdown view
|
||||
if (!this.compatibilityMode) {
|
||||
menu
|
||||
@@ -1867,7 +1957,15 @@ export default class ExcalidrawView extends TextFileView {
|
||||
this.saveSVG();
|
||||
});
|
||||
})
|
||||
.addSeparator();
|
||||
.addItem(item => {
|
||||
item
|
||||
.setTitle(t("INSTALL_SCRIPT_BUTTON"))
|
||||
.setIcon(SCRIPTENGINE_ICON_NAME)
|
||||
.setSection("pane")
|
||||
.onClick(()=>{
|
||||
new ScriptInstallPrompt(this.plugin).open();
|
||||
})
|
||||
})
|
||||
super.onPaneMenu(menu, source);
|
||||
}
|
||||
|
||||
@@ -1942,24 +2040,6 @@ export default class ExcalidrawView extends TextFileView {
|
||||
excalidrawRef.current.readyPromise.then(
|
||||
(api: ExcalidrawImperativeAPI) => {
|
||||
this.excalidrawAPI = api;
|
||||
// UpdateScene was added as a workaround here. In theory this is not required... however there was an odd error
|
||||
// that I wasn't able to track down to its source
|
||||
// https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/715
|
||||
// For some reason, if the drawing includes files (image attachments), then on Obsidian Mobile switching to markdown
|
||||
// view and back resulted in Excalidraw loading an empty file even though initialData contained the required info
|
||||
// this did not happen on a desktop
|
||||
// In theory this updateScene here is harmless, since it simply reloads the drawing that is already loaded with
|
||||
// initial data.
|
||||
this.updateScene(
|
||||
{
|
||||
elements: initdata.elements,
|
||||
appState: initdata.appState,
|
||||
files: initdata.files,
|
||||
commitToHistory: false
|
||||
},
|
||||
true,
|
||||
)
|
||||
//
|
||||
api.setLocalFont(this.plugin.settings.experimentalEnableFourthFont);
|
||||
this.loadSceneFiles();
|
||||
this.updateContainerSize(null, true);
|
||||
@@ -2249,12 +2329,17 @@ export default class ExcalidrawView extends TextFileView {
|
||||
const elements = newElementsOnTop
|
||||
? el.concat(newElements.filter((e) => !removeList.includes(e.id)))
|
||||
: newElements.filter((e) => !removeList.includes(e.id)).concat(el);
|
||||
this.updateScene({
|
||||
elements,
|
||||
commitToHistory: true,
|
||||
});
|
||||
|
||||
await this.updateScene(
|
||||
{
|
||||
elements,
|
||||
commitToHistory: true,
|
||||
},
|
||||
false,
|
||||
true
|
||||
);
|
||||
|
||||
if (images) {
|
||||
if (images && images !== {}) {
|
||||
const files: BinaryFileData[] = [];
|
||||
Object.keys(images).forEach((k) => {
|
||||
files.push({
|
||||
@@ -2341,11 +2426,13 @@ export default class ExcalidrawView extends TextFileView {
|
||||
gridSize: st.gridSize,
|
||||
colorPalette: st.colorPalette,
|
||||
},
|
||||
prevTextMode: this.prevTextMode,
|
||||
files,
|
||||
};
|
||||
};
|
||||
|
||||
this.refresh = () => {
|
||||
if(this.contentEl.clientWidth === 0 || this.contentEl.clientHeight === 0) return;
|
||||
const api = this.excalidrawAPI;
|
||||
if (!excalidrawRef?.current || !api) {
|
||||
return;
|
||||
@@ -2355,7 +2442,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
|
||||
let hoverPoint = { x: 0, y: 0 };
|
||||
let hoverPreviewTarget: EventTarget = null;
|
||||
const clearHoverPreview = () => {
|
||||
this.clearHoverPreview = () => {
|
||||
if (hoverPreviewTarget) {
|
||||
const event = new MouseEvent("click", {
|
||||
view: this.ownerWindow,
|
||||
@@ -2439,6 +2526,8 @@ export default class ExcalidrawView extends TextFileView {
|
||||
|
||||
const showHoverPreview = (linktext?: string, element?: ExcalidrawElement) => {
|
||||
if(!mouseEvent) return;
|
||||
if(this.excalidrawAPI?.getAppState()?.editingElement) return; //should not activate hover preview when element is being edited
|
||||
if(this.semaphores.wheelTimeout) return;
|
||||
if (!linktext) {
|
||||
if(!currentPosition) return;
|
||||
linktext = "";
|
||||
@@ -2602,7 +2691,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
mouseEvent = e.nativeEvent;
|
||||
},
|
||||
onMouseOver: () => {
|
||||
clearHoverPreview();
|
||||
this.clearHoverPreview();
|
||||
},
|
||||
onDragOver: (e: any) => {
|
||||
const action = dropAction(e.dataTransfer);
|
||||
@@ -2628,6 +2717,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
saveToActiveFile: false,
|
||||
},
|
||||
},
|
||||
initState: initdata?.appState,
|
||||
initialData: initdata,
|
||||
detectScroll: true,
|
||||
onPointerUpdate: (p: any) => {
|
||||
@@ -2637,7 +2727,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
(Math.abs(hoverPoint.x - p.pointer.x) > 50 ||
|
||||
Math.abs(hoverPoint.y - p.pointer.y) > 50)
|
||||
) {
|
||||
clearHoverPreview();
|
||||
this.clearHoverPreview();
|
||||
}
|
||||
if (!viewModeEnabled) {
|
||||
return;
|
||||
@@ -2938,6 +3028,9 @@ export default class ExcalidrawView extends TextFileView {
|
||||
clearTimeout(this.isEditingTextResetTimer);
|
||||
this.isEditingTextResetTimer = null;
|
||||
this.semaphores.isEditingText = true; //to prevent autoresize on mobile when keyboard pops up
|
||||
if(this.compatibilityMode) {
|
||||
return textElement.originalText ?? textElement.text;
|
||||
}
|
||||
const raw = this.excalidrawData.getRawText(textElement.id);
|
||||
if (!raw) {
|
||||
return textElement.rawText;
|
||||
@@ -2954,6 +3047,23 @@ export default class ExcalidrawView extends TextFileView {
|
||||
if (!api) {
|
||||
return [null, null, null];
|
||||
}
|
||||
const FORBIDDEN_TEXT = `{"type":"excalidraw/clipboard","elements":[{"`;
|
||||
const WARNING = "PASTING EXCALIDRAW ELEMENTS AS A TEXT ELEMENT IS NOT ALLOWED";
|
||||
if(text.startsWith(FORBIDDEN_TEXT)) {
|
||||
setTimeout(async ()=>{
|
||||
const elements = this.excalidrawAPI.getSceneElements();
|
||||
const el = elements.filter((el:ExcalidrawElement)=>el.id === textElement.id);
|
||||
if(el.length === 1) {
|
||||
const clone = cloneElement(el[0]);
|
||||
clone.rawText = WARNING;
|
||||
elements[elements.indexOf(el[0])] = clone;
|
||||
this.excalidrawData.setTextElement(clone.id,WARNING,WARNING,()=>{});
|
||||
await this.updateScene({elements});
|
||||
api.history.clear();
|
||||
}
|
||||
});
|
||||
return [WARNING,WARNING,null];
|
||||
}
|
||||
this.semaphores.isEditingText = true;
|
||||
this.isEditingTextResetTimer = setTimeout(() => {
|
||||
this.semaphores.isEditingText = false;
|
||||
@@ -2983,14 +3093,26 @@ export default class ExcalidrawView extends TextFileView {
|
||||
textElement.id,
|
||||
text,
|
||||
originalText,
|
||||
async () => {
|
||||
await this.save(false);
|
||||
//save preventReload==false, it will reload and update container sizes
|
||||
async (wrappedParsedText:string, parsedText:string) => {
|
||||
//this callback function will only be invoked if quick parse fails, i.e. there is a transclusion in the raw text
|
||||
//thus I only check if TextMode.parsed, text is always != with parseResult
|
||||
if (this.textMode === TextMode.parsed) {
|
||||
api.history.clear();
|
||||
if(this.textMode === TextMode.raw) return;
|
||||
|
||||
const elements = this.excalidrawAPI.getSceneElements();
|
||||
const el = elements.filter((el:ExcalidrawElement)=>el.id === textElement.id);
|
||||
if(el.length === 1) {
|
||||
const clone = cloneElement(el[0]);
|
||||
this.excalidrawData.updateTextElement(
|
||||
clone,
|
||||
wrappedParsedText,
|
||||
parsedText,
|
||||
true
|
||||
);
|
||||
elements[elements.indexOf(el[0])] = clone;
|
||||
await this.updateScene({elements});
|
||||
if(clone.containerId) this.updateContainerSize(clone.containerId);
|
||||
}
|
||||
|
||||
api.history.clear();
|
||||
},
|
||||
);
|
||||
if (parseResultWrapped) {
|
||||
@@ -3117,7 +3239,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
: this.leaf;
|
||||
await leaf.openFile(
|
||||
file,
|
||||
subpath ? { active: false, eState: { subpath } } : {active:false},
|
||||
subpath ? { active: false, eState: { subpath } } : {active:false}, //active false: to avoid taking the focus from ExcaliBrain
|
||||
); //if file exists open file and jump to reference
|
||||
//app.workspace.setActiveLeaf(leaf, true, true); //0.15.4 ExcaliBrain focus issue
|
||||
} catch (e) {
|
||||
@@ -3178,6 +3300,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
|
||||
const observer = React.useRef(
|
||||
new ResizeObserver((entries) => {
|
||||
if(!toolsPanelRef || !toolsPanelRef.current) return;
|
||||
const { width, height } = entries[0].contentRect;
|
||||
const dx = toolsPanelRef.current.onRightEdge
|
||||
? toolsPanelRef.current.previousWidth - width
|
||||
@@ -3189,7 +3312,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}),
|
||||
);
|
||||
React.useEffect(() => {
|
||||
if (toolsPanelRef.current) {
|
||||
if (toolsPanelRef?.current) {
|
||||
observer.current.observe(toolsPanelRef.current.containerRef.current);
|
||||
}
|
||||
return () => {
|
||||
@@ -3201,10 +3324,15 @@ export default class ExcalidrawView extends TextFileView {
|
||||
|
||||
return React.createElement(React.Fragment, null, excalidrawDiv);
|
||||
});
|
||||
/**REACT 18
|
||||
const root = ReactDOM.createRoot(this.contentEl);
|
||||
root.render(reactElement);
|
||||
*/
|
||||
ReactDOM.render(reactElement, this.contentEl, () => {});
|
||||
}
|
||||
|
||||
private updateContainerSize(containerId?: string, delay: boolean = false) {
|
||||
//console.log("updateContainerSize", containerId);
|
||||
const api = this.excalidrawAPI;
|
||||
if (!api) {
|
||||
return;
|
||||
@@ -3242,7 +3370,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
return;
|
||||
}
|
||||
const maxZoom = this.plugin.settings.zoomToFitMaxLevel;
|
||||
const elements = api.getSceneElements();
|
||||
const elements = api.getSceneElements().filter((el:ExcalidrawElement)=>el.width<10000 && el.height<10000);
|
||||
if (delay) {
|
||||
//time for the DOM to render, I am sure there is a more elegant solution
|
||||
setTimeout(
|
||||
@@ -3275,44 +3403,59 @@ export default class ExcalidrawView extends TextFileView {
|
||||
query: string[],
|
||||
selectResult: boolean = true,
|
||||
exactMatch: boolean = false, //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/530
|
||||
selectGroup: boolean = false,
|
||||
) {
|
||||
if (!elements || elements.length === 0 || !query || query.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const match = elements.filter((el: any) =>
|
||||
query.some((q) => {
|
||||
if (exactMatch) {
|
||||
const text = el.rawText.toLowerCase().split("\n")[0].trim();
|
||||
const m = text.match(/^#*(# .*)/);
|
||||
if (!m || m.length !== 2) {
|
||||
return false;
|
||||
}
|
||||
return m[1] === q.toLowerCase();
|
||||
}
|
||||
const text = el.rawText.toLowerCase().replaceAll("\n", " ").trim();
|
||||
return text.match(q.toLowerCase()); //to distinguish between "# frame" and "# frame 1"
|
||||
}),
|
||||
let match = getTextElementsMatchingQuery(
|
||||
elements.filter((el: ExcalidrawElement) => el.type === "text"),
|
||||
query,
|
||||
exactMatch
|
||||
);
|
||||
|
||||
if (match.length === 0) {
|
||||
new Notice("I could not find a matching text element");
|
||||
return;
|
||||
}
|
||||
|
||||
if(selectGroup) {
|
||||
const groupElements = this.plugin.ea.getElementsInTheSameGroupWithElement(match[0],elements)
|
||||
if(groupElements.length>0) {
|
||||
match = groupElements;
|
||||
}
|
||||
}
|
||||
|
||||
this.zoomToElements(selectResult,match);
|
||||
}
|
||||
|
||||
public zoomToElements(
|
||||
selectResult: boolean,
|
||||
elements: ExcalidrawElement[]
|
||||
) {
|
||||
const api = this.excalidrawAPI;
|
||||
if (!api) {
|
||||
return;
|
||||
}
|
||||
if (!api) return;
|
||||
|
||||
const zoomLevel = this.plugin.settings.zoomToFitMaxLevel;
|
||||
const ownerWindow = this.ownerWindow;
|
||||
if (selectResult) {
|
||||
api.selectElements(match);
|
||||
api.selectElements(elements);
|
||||
}
|
||||
api.zoomToFit(match, this.plugin.settings.zoomToFitMaxLevel, 0.05);
|
||||
api.zoomToFit(elements, zoomLevel, 0.05);
|
||||
/**REACT 18
|
||||
ownerWindow.requestAnimationFrame(async ()=>{
|
||||
if (selectResult) {
|
||||
api.selectElements(elements);
|
||||
await sleep(100);
|
||||
}
|
||||
ownerWindow.requestAnimationFrame(()=> {
|
||||
api.zoomToFit(elements, zoomLevel, 0.05);
|
||||
});
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
public getViewSelectedElements(): ExcalidrawElement[] {
|
||||
const api = this.excalidrawAPI;
|
||||
if (!api) {
|
||||
return;
|
||||
return [];
|
||||
}
|
||||
const selectedElements = api.getAppState()?.selectedElementIds;
|
||||
if (!selectedElements) {
|
||||
@@ -3348,12 +3491,31 @@ export default class ExcalidrawView extends TextFileView {
|
||||
.filter((el: ExcalidrawElement) => elementIDs.contains(el.id));
|
||||
}
|
||||
|
||||
public async copyLinkToSelectedElementToClipboard() {
|
||||
public async copyLinkToSelectedElementToClipboard(prefix:string) {
|
||||
const elements = this.getViewSelectedElements();
|
||||
if (elements.length !== 1) {
|
||||
if (elements.length < 1) {
|
||||
new Notice(t("INSERT_LINK_TO_ELEMENT_ERROR"));
|
||||
return;
|
||||
}
|
||||
|
||||
let elementId:string = undefined;
|
||||
|
||||
if(elements.length === 2) {
|
||||
const textEl = elements.filter(el=>el.type==="text");
|
||||
if(textEl.length===1 && (textEl[0] as ExcalidrawTextElement).containerId) {
|
||||
const container = elements.filter(el=>el.boundElements.some(be=>be.type==="text"))
|
||||
if(container.length===1) {
|
||||
elementId = textEl[0].id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!elementId) {
|
||||
elementId = elements.length === 1
|
||||
? elements[0].id
|
||||
: this.plugin.ea.getLargestElement(elements).id;
|
||||
}
|
||||
|
||||
const alias = await ScriptEngine.inputPrompt(
|
||||
app,
|
||||
"Set link alias",
|
||||
@@ -3361,12 +3523,12 @@ export default class ExcalidrawView extends TextFileView {
|
||||
"",
|
||||
);
|
||||
navigator.clipboard.writeText(
|
||||
`[[${this.file.path}#^${elements[0].id}${alias ? `|${alias}` : ``}]]`,
|
||||
`[[${this.file.path}#^${prefix}${elementId}${alias ? `|${alias}` : ``}]]`,
|
||||
);
|
||||
new Notice(t("INSERT_LINK_TO_ELEMENT_READY"));
|
||||
}
|
||||
|
||||
public updateScene(
|
||||
public async updateScene(
|
||||
scene: {
|
||||
elements?: ExcalidrawElement[];
|
||||
appState?: any;
|
||||
@@ -3374,6 +3536,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
commitToHistory?: boolean;
|
||||
},
|
||||
restore: boolean = false,
|
||||
awaitFrame: boolean = false,
|
||||
) {
|
||||
const api = this.excalidrawAPI;
|
||||
if (!api) {
|
||||
@@ -3411,6 +3574,9 @@ export default class ExcalidrawView extends TextFileView {
|
||||
warningUnknowSeriousError();
|
||||
}
|
||||
}
|
||||
/**REACT 18
|
||||
if(awaitFrame) await awaitNextAnimationFrame(); //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/747
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,15 +12,15 @@ import ExcalidrawPlugin from "./main";
|
||||
import {getIMGFilename,} from "./utils/FileUtils";
|
||||
import {
|
||||
embedFontsInSVG,
|
||||
getEmbeddedFilenameParts,
|
||||
getExportTheme,
|
||||
getQuickImagePreview,
|
||||
getSVGPadding,
|
||||
getExportPadding,
|
||||
getWithBackground,
|
||||
hasExportTheme,
|
||||
svgToBase64,
|
||||
} from "./utils/Utils";
|
||||
import { isObsidianThemeDark } from "./utils/ObsidianUtils";
|
||||
import { splitFolderAndFilename } from "./utils/FileUtils";
|
||||
|
||||
interface imgElementAttributes {
|
||||
file?: TFile;
|
||||
@@ -58,13 +58,15 @@ const getIMG = async (
|
||||
): Promise<HTMLElement> => {
|
||||
let file = imgAttributes.file;
|
||||
if (!imgAttributes.file) {
|
||||
const f = vault.getAbstractFileByPath(imgAttributes.fname);
|
||||
const f = vault.getAbstractFileByPath(imgAttributes.fname?.split("#")[0]);
|
||||
if (!(f && f instanceof TFile)) {
|
||||
return null;
|
||||
}
|
||||
file = f;
|
||||
}
|
||||
|
||||
const filenameParts = getEmbeddedFilenameParts(imgAttributes.fname);
|
||||
|
||||
// https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/387
|
||||
imgAttributes.style = imgAttributes.style.replaceAll(" ", "-");
|
||||
|
||||
@@ -103,24 +105,28 @@ const getIMG = async (
|
||||
|
||||
if (!plugin.settings.displaySVGInPreview) {
|
||||
const width = parseInt(imgAttributes.fwidth);
|
||||
let scale = 1;
|
||||
if (width >= 600) {
|
||||
scale = 2;
|
||||
}
|
||||
if (width >= 1200) {
|
||||
scale = 3;
|
||||
}
|
||||
if (width >= 1800) {
|
||||
scale = 4;
|
||||
}
|
||||
if (width >= 2400) {
|
||||
scale = 5;
|
||||
}
|
||||
const scale = width >= 2400
|
||||
? 5
|
||||
: width >= 1800
|
||||
? 4
|
||||
: width >= 1200
|
||||
? 3
|
||||
: width >= 600
|
||||
? 2
|
||||
: 1;
|
||||
|
||||
//In case of PNG I cannot change the viewBox to select the area of the element
|
||||
//being referenced. For PNG only the group reference works
|
||||
const quickPNG = !filenameParts.hasGroupref
|
||||
? await getQuickImagePreview(plugin, file.path, "png")
|
||||
: undefined;
|
||||
|
||||
const png =
|
||||
(await getQuickImagePreview(plugin, file.path, "png")) ??
|
||||
quickPNG ??
|
||||
(await createPNG(
|
||||
file.path,
|
||||
filenameParts.hasGroupref
|
||||
? filenameParts.filepath + filenameParts.linkpartReference
|
||||
: file.path,
|
||||
scale,
|
||||
exportSettings,
|
||||
loader,
|
||||
@@ -137,14 +143,19 @@ const getIMG = async (
|
||||
img.src = URL.createObjectURL(png);
|
||||
return img;
|
||||
}
|
||||
const quickSVG = await getQuickImagePreview(plugin, file.path, "svg");
|
||||
if (quickSVG) {
|
||||
img.setAttribute("src", svgToBase64(quickSVG));
|
||||
return img;
|
||||
|
||||
if(!(filenameParts.hasBlockref || filenameParts.hasSectionref)) {
|
||||
const quickSVG = await getQuickImagePreview(plugin, file.path, "svg");
|
||||
if (quickSVG) {
|
||||
img.setAttribute("src", svgToBase64(quickSVG));
|
||||
return img;
|
||||
}
|
||||
}
|
||||
const svgSnapshot = (
|
||||
await createSVG(
|
||||
file.path,
|
||||
filenameParts.hasGroupref || filenameParts.hasBlockref || filenameParts.hasSectionref
|
||||
? filenameParts.filepath + filenameParts.linkpartReference
|
||||
: file.path,
|
||||
true,
|
||||
exportSettings,
|
||||
loader,
|
||||
@@ -154,7 +165,7 @@ const getIMG = async (
|
||||
[],
|
||||
plugin,
|
||||
0,
|
||||
getSVGPadding(plugin, file),
|
||||
getExportPadding(plugin, file),
|
||||
)
|
||||
).outerHTML;
|
||||
let svg: SVGSVGElement = null;
|
||||
@@ -180,7 +191,7 @@ const createImageDiv = async (
|
||||
const img = await getIMG(attr);
|
||||
return createDiv(attr.style, (el) => {
|
||||
el.append(img);
|
||||
el.setAttribute("src", attr.file.path);
|
||||
el.setAttribute("src", attr.fname);
|
||||
if (attr.fwidth) {
|
||||
el.setAttribute("w", attr.fwidth);
|
||||
}
|
||||
@@ -196,13 +207,17 @@ const createImageDiv = async (
|
||||
}
|
||||
const src = el.getAttribute("src");
|
||||
if (src) {
|
||||
const srcParts = src.match(/([^#]*)(.*)/);
|
||||
if(!srcParts) return;
|
||||
plugin.openDrawing(
|
||||
vault.getAbstractFileByPath(src) as TFile,
|
||||
vault.getAbstractFileByPath(srcParts[1]) as TFile,
|
||||
ev[CTRL_OR_CMD]
|
||||
? "new-pane"
|
||||
: (ev.metaKey && !app.isMobile)
|
||||
? "popout-window"
|
||||
: "active-pane",
|
||||
true,
|
||||
srcParts[2],
|
||||
);
|
||||
} //.ctrlKey||ev.metaKey);
|
||||
});
|
||||
@@ -220,106 +235,108 @@ const createImageDiv = async (
|
||||
});
|
||||
};
|
||||
|
||||
const processInternalEmbeds = async (
|
||||
const processReadingMode = async (
|
||||
embeddedItems: NodeListOf<Element> | [HTMLElement],
|
||||
ctx: MarkdownPostProcessorContext,
|
||||
) => {
|
||||
//if not, then we are processing a non-excalidraw file in reading mode
|
||||
//in that cases embedded files will be displayed in an .internal-embed container
|
||||
//We are processing a non-excalidraw file in reading mode
|
||||
//Embedded files will be displayed in an .internal-embed container
|
||||
|
||||
//Iterating all the containers in the file to check which one is an excalidraw drawing
|
||||
//This is a for loop instead of embeddedItems.forEach() because processInternalEmbed at the end
|
||||
//is awaited, otherwise excalidraw images would not display in the Kanban plugin
|
||||
for (const maybeDrawing of embeddedItems) {
|
||||
//check to see if the file in the src attribute exists
|
||||
const fname = maybeDrawing.getAttribute("src")?.split("#")[0];
|
||||
if(!fname) continue;
|
||||
|
||||
const file = metadataCache.getFirstLinkpathDest(fname, ctx.sourcePath);
|
||||
|
||||
//if the embeddedFile exits and it is an Excalidraw file
|
||||
//then lets replace the .internal-embed with the generated PNG or SVG image
|
||||
if (file && file instanceof TFile && plugin.isExcalidrawFile(file)) {
|
||||
if(isTextOnlyEmbed(maybeDrawing)) {
|
||||
//legacy reference to a block or section as text
|
||||
//should be embedded as legacy text
|
||||
continue;
|
||||
}
|
||||
|
||||
maybeDrawing.parentElement.replaceChild(
|
||||
await processInternalEmbed(maybeDrawing,file),
|
||||
maybeDrawing
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const processInternalEmbed = async (internalEmbedEl: Element, file: TFile ):Promise<HTMLDivElement> => {
|
||||
const attr: imgElementAttributes = {
|
||||
fname: "",
|
||||
fheight: "",
|
||||
fwidth: "",
|
||||
style: "",
|
||||
};
|
||||
let alt: string;
|
||||
let parts;
|
||||
let file: TFile;
|
||||
|
||||
//Iterating through all the containers to check which one is an excalidraw drawing
|
||||
//This is a for loop instead of embeddedItems.forEach() because createImageDiv at the end
|
||||
//is awaited, otherwise excalidraw images would not display in the Kanban plugin
|
||||
for (const maybeDrawing of embeddedItems) {
|
||||
//check to see if the file in the src attribute exists
|
||||
attr.fname = maybeDrawing.getAttribute("src");
|
||||
file = metadataCache.getFirstLinkpathDest(
|
||||
attr.fname?.split("#")[0],
|
||||
ctx.sourcePath,
|
||||
);
|
||||
const src = internalEmbedEl.getAttribute("src");
|
||||
if(!src) return;
|
||||
attr.fwidth = internalEmbedEl.getAttribute("width")
|
||||
? internalEmbedEl.getAttribute("width")
|
||||
: getDefaultWidth(plugin);
|
||||
attr.fheight = internalEmbedEl.getAttribute("height");
|
||||
let alt = internalEmbedEl.getAttribute("alt");
|
||||
attr.style = "excalidraw-svg";
|
||||
processAltText(src.split("#")[0],alt,attr);
|
||||
const fnameParts = getEmbeddedFilenameParts(src);
|
||||
attr.fname = file?.path + (fnameParts.hasBlockref||fnameParts.hasSectionref?fnameParts.linkpartReference:"");
|
||||
attr.file = file;
|
||||
return await createImageDiv(attr);
|
||||
}
|
||||
|
||||
//if the embeddedFile exits and it is an Excalidraw file
|
||||
//then lets replace the .internal-embed with the generated PNG or SVG image
|
||||
if (file && file instanceof TFile && plugin.isExcalidrawFile(file)) {
|
||||
attr.fwidth = maybeDrawing.getAttribute("width")
|
||||
? maybeDrawing.getAttribute("width")
|
||||
: getDefaultWidth(plugin);
|
||||
attr.fheight = maybeDrawing.getAttribute("height");
|
||||
alt = maybeDrawing.getAttribute("alt");
|
||||
if (alt == attr.fname) {
|
||||
alt = "";
|
||||
} //when the filename starts with numbers followed by a space Obsidian recognizes the filename as alt-text
|
||||
attr.style = "excalidraw-svg";
|
||||
if (alt) {
|
||||
//for some reason Obsidian renders ![]() in a DIV and ![[]] in a SPAN
|
||||
//also the alt-text of the DIV does not include the alt-text of the image
|
||||
//thus need to add an additional "|" character when its a SPAN
|
||||
if (maybeDrawing.tagName.toLowerCase() == "span") {
|
||||
alt = `|${alt}`;
|
||||
}
|
||||
//1:width, 2:height, 3:style 1 2 3
|
||||
parts = alt.match(/[^\|]*\|?(\d*%?)x?(\d*%?)\|?(.*)/);
|
||||
attr.fwidth = parts[1] ? parts[1] : getDefaultWidth(plugin);
|
||||
attr.fheight = parts[2];
|
||||
if (parts[3] != attr.fname) {
|
||||
attr.style = `excalidraw-svg${parts[3] ? `-${parts[3]}` : ""}`;
|
||||
}
|
||||
}
|
||||
attr.fname = file?.path;
|
||||
attr.file = file;
|
||||
const div = await createImageDiv(attr);
|
||||
maybeDrawing.parentElement.replaceChild(div, maybeDrawing);
|
||||
const processAltText = (
|
||||
fname: string,
|
||||
alt:string,
|
||||
attr: imgElementAttributes
|
||||
) => {
|
||||
if (alt && !alt.startsWith(fname)) {
|
||||
//2:width, 3:height, 4:style 12 3 4
|
||||
const parts = alt.match(/[^\|\d]*\|?((\d*%?)x?(\d*%?))?\|?(.*)/);
|
||||
attr.fwidth = parts[2] ?? attr.fwidth;
|
||||
attr.fheight = parts[3] ?? attr.fheight;
|
||||
if (parts[4] && !parts[4].startsWith(fname)) {
|
||||
attr.style = `excalidraw-svg${`-${parts[4]}`}`;
|
||||
}
|
||||
if (
|
||||
(!parts[4] || parts[4]==="") &&
|
||||
(!parts[2] || parts[2]==="") &&
|
||||
parts[0] && parts[0] !== ""
|
||||
) {
|
||||
attr.style = `excalidraw-svg${`-${parts[0]}`}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const isTextOnlyEmbed = (internalEmbedEl: Element):boolean => {
|
||||
const src = internalEmbedEl.getAttribute("src");
|
||||
if(!src) return true; //technically this does not mean this is a text only embed, but still should abort further processing
|
||||
const fnameParts = getEmbeddedFilenameParts(src);
|
||||
return !(fnameParts.hasArearef || fnameParts.hasGroupref) &&
|
||||
(fnameParts.hasBlockref || fnameParts.hasSectionref)
|
||||
}
|
||||
|
||||
const tmpObsidianWYSIWYG = async (
|
||||
el: HTMLElement,
|
||||
ctx: MarkdownPostProcessorContext,
|
||||
) => {
|
||||
if (!ctx.frontmatter) {
|
||||
return;
|
||||
}
|
||||
if (!ctx.frontmatter.hasOwnProperty("excalidraw-plugin")) {
|
||||
return;
|
||||
}
|
||||
const file = app.vault.getAbstractFileByPath(ctx.sourcePath);
|
||||
if(!(file instanceof TFile)) return;
|
||||
if(!plugin.isExcalidrawFile(file)) return;
|
||||
|
||||
//@ts-ignore
|
||||
if (ctx.remainingNestLevel < 4) {
|
||||
return;
|
||||
}
|
||||
if (!el.querySelector(".frontmatter")) {
|
||||
el.style.display = "none";
|
||||
return;
|
||||
}
|
||||
const attr: imgElementAttributes = {
|
||||
fname: ctx.sourcePath,
|
||||
fheight: "",
|
||||
fwidth: getDefaultWidth(plugin),
|
||||
style: "excalidraw-svg",
|
||||
};
|
||||
|
||||
attr.file = metadataCache.getFirstLinkpathDest(ctx.sourcePath, "");
|
||||
|
||||
el.empty();
|
||||
|
||||
if (!plugin.settings.experimentalLivePreview) {
|
||||
el.appendChild(await createImageDiv(attr));
|
||||
return;
|
||||
}
|
||||
|
||||
const div = createDiv();
|
||||
el.appendChild(div);
|
||||
|
||||
//The timeout gives time for obsidian to attach el to the displayed document
|
||||
//The timeout gives time for Obsidian to attach el to the displayed document
|
||||
//Once the element is attached, I can traverse up the dom tree to find .internal-embed
|
||||
//If internal embed is not found, it means the that the excalidraw.md file
|
||||
//is being rendered in "reading" mode. In that case, the image with the default width
|
||||
@@ -327,7 +344,12 @@ const tmpObsidianWYSIWYG = async (
|
||||
//if .internal-embed is found, then contents is replaced with the image using the
|
||||
//alt, width, and height attributes of .internal-embed to size and style the image
|
||||
setTimeout(async () => {
|
||||
let internalEmbedDiv: HTMLElement = div;
|
||||
//wait for el to be attached to the displayed document
|
||||
let counter = 0;
|
||||
while(!el.parentElement && counter++<=50) await sleep(50);
|
||||
if(!el.parentElement) return;
|
||||
|
||||
let internalEmbedDiv: HTMLElement = el;
|
||||
while (
|
||||
!internalEmbedDiv.hasClass("internal-embed") &&
|
||||
internalEmbedDiv.parentElement
|
||||
@@ -335,52 +357,49 @@ const tmpObsidianWYSIWYG = async (
|
||||
internalEmbedDiv = internalEmbedDiv.parentElement;
|
||||
}
|
||||
|
||||
const attr: imgElementAttributes = {
|
||||
fname: ctx.sourcePath,
|
||||
fheight: "",
|
||||
fwidth: getDefaultWidth(plugin),
|
||||
style: "excalidraw-svg",
|
||||
};
|
||||
|
||||
attr.file = file;
|
||||
|
||||
if (!internalEmbedDiv.hasClass("internal-embed")) {
|
||||
//We are processing the markdown preview of an actual Excalidraw file
|
||||
//This could be in a hover preview of the file
|
||||
//Or the file could be in markdown mode and the user switched markdown
|
||||
//view of the drawing to reading mode
|
||||
el.empty();
|
||||
el.appendChild(await createImageDiv(attr));
|
||||
const mdPreviewSection = el.parentElement;
|
||||
if(!mdPreviewSection.hasClass("markdown-preview-section")) return;
|
||||
if(mdPreviewSection.hasAttribute("ready")) {
|
||||
mdPreviewSection.removeChild(el);
|
||||
return;
|
||||
}
|
||||
mdPreviewSection.setAttribute("ready","");
|
||||
const imgDiv = await createImageDiv(attr);
|
||||
el.appendChild(imgDiv);
|
||||
return;
|
||||
}
|
||||
|
||||
if(isTextOnlyEmbed(internalEmbedDiv)) {
|
||||
//legacy reference to a block or section as text
|
||||
//should be embedded as legacy text
|
||||
return;
|
||||
}
|
||||
|
||||
el.empty();
|
||||
|
||||
if(internalEmbedDiv.hasAttribute("ready")) {
|
||||
return;
|
||||
}
|
||||
internalEmbedDiv.setAttribute("ready","");
|
||||
|
||||
internalEmbedDiv.empty();
|
||||
|
||||
const basename = splitFolderAndFilename(attr.fname).basename;
|
||||
const setAttr = () => {
|
||||
const hasWidth = internalEmbedDiv.getAttribute("width") && (internalEmbedDiv.getAttribute("width") !== "");
|
||||
const hasHeight = internalEmbedDiv.getAttribute("height") && (internalEmbedDiv.getAttribute("height") !== "");
|
||||
if (hasWidth) {
|
||||
attr.fwidth = internalEmbedDiv.getAttribute("width");
|
||||
}
|
||||
if (hasHeight) {
|
||||
attr.fheight = internalEmbedDiv.getAttribute("height");
|
||||
}
|
||||
const alt = internalEmbedDiv.getAttribute("alt");
|
||||
const hasAttr =
|
||||
alt &&
|
||||
alt !== "" &&
|
||||
alt !== basename &&
|
||||
alt !== internalEmbedDiv.getAttribute("src");
|
||||
if (hasAttr) {
|
||||
//1:width, 2:height, 3:style 1 2 3
|
||||
const parts = alt.match(/(\d*%?)x?(\d*%?)\|?(.*)/);
|
||||
attr.fwidth = parts[1] ? parts[1] : getDefaultWidth(plugin);
|
||||
attr.fheight = parts[2];
|
||||
if (parts[3] != attr.fname) {
|
||||
attr.style = `excalidraw-svg${parts[3] ? `-${parts[3]}` : ""}`;
|
||||
}
|
||||
}
|
||||
if (!hasWidth && !hasHeight && !hasAttr) {
|
||||
attr.fheight = "";
|
||||
attr.fwidth = getDefaultWidth(plugin);
|
||||
attr.style = "excalidraw-svg";
|
||||
}
|
||||
};
|
||||
|
||||
const createImgElement = async () => {
|
||||
setAttr();
|
||||
const imgDiv = await createImageDiv(attr);
|
||||
internalEmbedDiv.appendChild(imgDiv);
|
||||
};
|
||||
await createImgElement();
|
||||
const imgDiv = await processInternalEmbed(internalEmbedDiv,file);
|
||||
internalEmbedDiv.appendChild(imgDiv);
|
||||
|
||||
//timer to avoid the image flickering when the user is typing
|
||||
let timer: NodeJS.Timeout = null;
|
||||
@@ -391,17 +410,17 @@ const tmpObsidianWYSIWYG = async (
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
timer = setTimeout(() => {
|
||||
timer = setTimeout(async () => {
|
||||
timer = null;
|
||||
setAttr();
|
||||
internalEmbedDiv.empty();
|
||||
createImgElement();
|
||||
const imgDiv = await processInternalEmbed(internalEmbedDiv,file);
|
||||
internalEmbedDiv.appendChild(imgDiv);
|
||||
}, 500);
|
||||
});
|
||||
observer.observe(internalEmbedDiv, {
|
||||
attributes: true, //configure it to listen to attribute changes
|
||||
});
|
||||
}, 300);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -413,7 +432,8 @@ export const markdownPostProcessor = async (
|
||||
el: HTMLElement,
|
||||
ctx: MarkdownPostProcessorContext,
|
||||
) => {
|
||||
//check to see if we are rendering in editing mode of live preview
|
||||
|
||||
//check to see if we are rendering in editing mode or live preview
|
||||
//if yes, then there should be no .internal-embed containers
|
||||
const embeddedItems = el.querySelectorAll(".internal-embed");
|
||||
if (embeddedItems.length === 0) {
|
||||
@@ -425,12 +445,13 @@ export const markdownPostProcessor = async (
|
||||
//then I want to hide all embedded items as these will be
|
||||
//transcluded text element or some other transcluded content inside the Excalidraw file
|
||||
//in reading mode these elements should be hidden
|
||||
if (ctx.frontmatter?.hasOwnProperty("excalidraw-plugin")) {
|
||||
const excalidrawFile = Boolean(ctx.frontmatter?.hasOwnProperty("excalidraw-plugin"));
|
||||
if (excalidrawFile) {
|
||||
el.style.display = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
await processInternalEmbeds(embeddedItems, ctx);
|
||||
await processReadingMode(embeddedItems, ctx);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,7 +34,7 @@ export class ScriptEngine {
|
||||
if (!path.endsWith(".svg")) {
|
||||
return;
|
||||
}
|
||||
const scriptFile = this.plugin.app.vault.getAbstractFileByPath(
|
||||
const scriptFile = app.vault.getAbstractFileByPath(
|
||||
getIMGFilename(path, "md"),
|
||||
);
|
||||
if (scriptFile && scriptFile instanceof TFile) {
|
||||
@@ -53,7 +53,7 @@ export class ScriptEngine {
|
||||
handleSvgFileChange(file.path);
|
||||
};
|
||||
this.plugin.registerEvent(
|
||||
this.plugin.app.vault.on("delete", deleteEventHandler),
|
||||
app.vault.on("delete", deleteEventHandler),
|
||||
);
|
||||
|
||||
const createEventHandler = async (file: TFile) => {
|
||||
@@ -67,7 +67,7 @@ export class ScriptEngine {
|
||||
handleSvgFileChange(file.path);
|
||||
};
|
||||
this.plugin.registerEvent(
|
||||
this.plugin.app.vault.on("create", createEventHandler),
|
||||
app.vault.on("create", createEventHandler),
|
||||
);
|
||||
|
||||
const renameEventHandler = async (file: TAbstractFile, oldPath: string) => {
|
||||
@@ -86,7 +86,7 @@ export class ScriptEngine {
|
||||
}
|
||||
};
|
||||
this.plugin.registerEvent(
|
||||
this.plugin.app.vault.on("rename", renameEventHandler),
|
||||
app.vault.on("rename", renameEventHandler),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -101,7 +101,6 @@ export class ScriptEngine {
|
||||
}
|
||||
|
||||
public getListofScripts(): TFile[] {
|
||||
const app = this.plugin.app;
|
||||
this.scriptPath = this.plugin.settings.scriptFolderPath;
|
||||
if (!app.vault.getAbstractFileByPath(this.scriptPath)) {
|
||||
this.scriptPath = null;
|
||||
@@ -140,10 +139,10 @@ export class ScriptEngine {
|
||||
|
||||
async addScriptIconToMap(scriptPath: string, name: string) {
|
||||
const svgFilePath = getIMGFilename(scriptPath, "svg");
|
||||
const file = this.plugin.app.vault.getAbstractFileByPath(svgFilePath);
|
||||
const file = app.vault.getAbstractFileByPath(svgFilePath);
|
||||
const svgString: string =
|
||||
file && file instanceof TFile
|
||||
? await this.plugin.app.vault.read(file)
|
||||
? await app.vault.read(file)
|
||||
: null;
|
||||
this.scriptIconMap = {
|
||||
...this.scriptIconMap,
|
||||
@@ -168,7 +167,7 @@ export class ScriptEngine {
|
||||
const view = app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (view) {
|
||||
(async()=>{
|
||||
const script = await this.plugin.app.vault.read(f);
|
||||
const script = await app.vault.read(f);
|
||||
if(script) {
|
||||
this.executeScript(view, script, scriptName);
|
||||
}
|
||||
@@ -181,7 +180,6 @@ export class ScriptEngine {
|
||||
}
|
||||
|
||||
unloadScripts() {
|
||||
const app = this.plugin.app;
|
||||
const scripts = app.vault
|
||||
.getFiles()
|
||||
.filter((f: TFile) => f.path.startsWith(this.scriptPath));
|
||||
@@ -198,7 +196,6 @@ export class ScriptEngine {
|
||||
this.scriptIconMap = { ...this.scriptIconMap };
|
||||
this.updateToolPannels();
|
||||
|
||||
const app = this.plugin.app;
|
||||
const commandId = `${PLUGIN_ID}:${basename}`;
|
||||
// @ts-ignore
|
||||
if (!app.commands.commands[commandId]) {
|
||||
@@ -229,7 +226,7 @@ export class ScriptEngine {
|
||||
buttons?: [{ caption: string; action: Function }],
|
||||
) =>
|
||||
ScriptEngine.inputPrompt(
|
||||
this.plugin.app,
|
||||
app,
|
||||
header,
|
||||
placeholder,
|
||||
value,
|
||||
@@ -242,7 +239,7 @@ export class ScriptEngine {
|
||||
instructions?: Instruction[],
|
||||
) =>
|
||||
ScriptEngine.suggester(
|
||||
this.plugin.app,
|
||||
app,
|
||||
displayItems,
|
||||
items,
|
||||
hint,
|
||||
@@ -259,7 +256,7 @@ export class ScriptEngine {
|
||||
|
||||
private updateToolPannels() {
|
||||
const leaves =
|
||||
this.plugin.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
leaves.forEach((leaf: WorkspaceLeaf) => {
|
||||
const excalidrawView = leaf.view as ExcalidrawView;
|
||||
excalidrawView.toolsPanelRef?.current?.updateScriptIconMap(
|
||||
|
||||
@@ -21,12 +21,14 @@ export const REG_BLOCK_REF_CLEAN =
|
||||
/[!"#$%&()*+,.:;<=>?@^`{|}~\/\[\]\\]/g; //https://discord.com/channels/686053708261228577/989603365606531104/1000128926619816048
|
||||
// /\+|\/|~|=|%|\(|\)|{|}|,|&|\.|\$|!|\?|;|\[|]|\^|#|\*|<|>|&|@|\||\\|"|:|\s/g;
|
||||
export const IMAGE_TYPES = ["jpeg", "jpg", "png", "gif", "svg"];
|
||||
export const EXPORT_TYPES = ["svg", "dark.svg", "light.svg", "png", "dark.png", "light.png"];
|
||||
export const MAX_IMAGE_SIZE = 500;
|
||||
export const FRONTMATTER_KEY = "excalidraw-plugin";
|
||||
export const FRONTMATTER_KEY_EXPORT_TRANSPARENT =
|
||||
"excalidraw-export-transparent";
|
||||
export const FRONTMATTER_KEY_EXPORT_DARK = "excalidraw-export-dark";
|
||||
export const FRONTMATTER_KEY_EXPORT_SVGPADDING = "excalidraw-export-svgpadding";
|
||||
export const FRONTMATTER_KEY_EXPORT_SVGPADDING = "excalidraw-export-svgpadding"; //depricated
|
||||
export const FRONTMATTER_KEY_EXPORT_PADDING = "excalidraw-export-padding";
|
||||
export const FRONTMATTER_KEY_EXPORT_PNGSCALE = "excalidraw-export-pngscale";
|
||||
export const FRONTMATTER_KEY_CUSTOM_PREFIX = "excalidraw-link-prefix";
|
||||
export const FRONTMATTER_KEY_CUSTOM_URL_PREFIX = "excalidraw-url-prefix";
|
||||
@@ -38,6 +40,7 @@ export const FRONTMATTER_KEY_FONT = "excalidraw-font";
|
||||
export const FRONTMATTER_KEY_FONTCOLOR = "excalidraw-font-color";
|
||||
export const FRONTMATTER_KEY_BORDERCOLOR = "excalidraw-border-color";
|
||||
export const FRONTMATTER_KEY_MD_STYLE = "excalidraw-css";
|
||||
export const FRONTMATTER_KEY_AUTOEXPORT = "excalidraw-autoexport"
|
||||
export const LOCAL_PROTOCOL = "md://";
|
||||
export const VIEW_TYPE_EXCALIDRAW = "excalidraw";
|
||||
export const ICON_NAME = "excalidraw-icon";
|
||||
|
||||
@@ -17,18 +17,130 @@ I develop this plugin as a hobby, spending most of my free time doing this. If y
|
||||
|
||||
<div class="ex-coffee-div"><a href="https://ko-fi.com/zsolt"><img src="https://cdn.ko-fi.com/cdn/kofi3.png?v=3" height=45></a></div>
|
||||
`,
|
||||
"1.7.18":`
|
||||
## Critical fix
|
||||
- duplicating text elements, adding text elements from the library, and pasting excalidraw text elements results in a corrupted file!!`,
|
||||
"1.7.17":`
|
||||
## Fixed
|
||||
- Block transclusions sometimes got lost when switching between RAW mode and PREVIEW mode. [#769](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/769)
|
||||
|
||||
## New
|
||||
- Added feature to disable "new Excalidraw version" notification [#770](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/770)
|
||||
- Added option to export both light- and dark-themed images at the same time. If this is enabled Excalidraw will create two files "filename.dark.png" and "filename.light.png" (or .svg depending on your other settings). See practical use case here: [Aadam's Notes](https://notes.aadam.dev/SBYNtPHqsTW9Ck1Kuoxsu/)
|
||||
- Added custom export padding for PNG images. Use the frontmatter key ${String.fromCharCode(96)}excalidraw-export-padding${String.fromCharCode(96)} to set the padding at a file level, or set padding for all your files in plugin settings. The new feature replaces the old "SVG Padding" option and applies to both SVG and PNG exports.
|
||||
|
||||
## ExcalidrawAutomate
|
||||
- Added ${String.fromCharCode(96)}padding${String.fromCharCode(96)} to the createPNG function call.
|
||||
${String.fromCharCode(96, 96, 96)}typescript
|
||||
async createPNG(
|
||||
templatePath?: string,
|
||||
scale: number = 1,
|
||||
exportSettings?: ExportSettings,
|
||||
loader?: EmbeddedFilesLoader,
|
||||
theme?: string,
|
||||
padding?: number,
|
||||
)
|
||||
${String.fromCharCode(96, 96, 96)}
|
||||
`,
|
||||
"1.7.16":`
|
||||
## Fixed
|
||||
- Excalidraw canvas is empty after saving the drawing and re-opening it at a later time. If you accidentally paste Excalidraw elements from the clipboard as the contents of a text element, in certain situations this can corrupt the Excalidraw file and as a result, Excalidraw will load an empty-looking drawing the next time. Changing to markdown view, these files can be repaired, however, to avoid accidental data loss, I have prevented pasting of excalidraw clipboard contents as text elements. [#768](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/768)
|
||||
|
||||
## New
|
||||
- Add zoom % display in tray-mode [737](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/737)
|
||||
`,
|
||||
"1.7.15":`
|
||||
## Fixed
|
||||
- Canvas turns white when adding point for curved line [#760](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/760), [#738](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/738), [#5602](https://github.com/excalidraw/excalidraw/issues/5602)
|
||||
`,
|
||||
"1.7.14": `
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/yZQoJg2RCKI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## New
|
||||
- The ${String.fromCharCode(96)}Copy markdown link for selected element to clipboard${String.fromCharCode(96)} action in the Obsidian menu is now more intelligent. If multiple elements are selected it will copy the Element Reference for the largest element.
|
||||
- When referencing an element in a link pointing to an Excalidraw file using the elementId or the section header as the block reference e.g. ${String.fromCharCode(96)}[[file#^elementID]]${String.fromCharCode(96)}, you can now add the ${String.fromCharCode(96)}group=${String.fromCharCode(96)} prefix, e.g. ${String.fromCharCode(96)}[[file#^group=elementID]]${String.fromCharCode(96)} and the ${String.fromCharCode(96)}area=${String.fromCharCode(96)} prefix, e.g. ${String.fromCharCode(96)}[[file#area=Section heading]]${String.fromCharCode(96)}.
|
||||
- If the ${String.fromCharCode(96)}group=${String.fromCharCode(96)} prefix is found, Excalidraw will select the group of elements in the same group as the element referenced by the elementID or heading section.
|
||||
- If the ${String.fromCharCode(96)}area=${String.fromCharCode(96)} prefix is found, excalidraw will insert a cutout of the image around the referenced element.
|
||||
- The ${String.fromCharCode(96)}area=${String.fromCharCode(96)} selector is not supported when embedding Excalidraw as PNG into your markdown documents.
|
||||
- I added "Toggle left-handed mode" to the Command Palette. The action is only visible if tray-mode is enabled. It will move the tray from left to right and back. [749](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/749)
|
||||
|
||||
## Fixed
|
||||
- Zooming with CTRL+Wheel will no longer trigger hover preview.
|
||||
- When editing text in a text element CTRL+C will not launch the hover preview in case the mouse pointer is over the text element being edited. Hover preview will only show if the element is not in editing mode.
|
||||
- ExcalidrawAutomate did not reliably save changes. This caused issues for example in the "Add link to an existing file and open" script. [#747](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/747)
|
||||
- Create a new folder not working when clicking on a link in Erxcalidraw that points to a file that is in a folder that does not yet exist. [741](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/741)
|
||||
- Downgraded to React 17 due to various stability issues, including [#738](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/738) and [#747](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/747)
|
||||
|
||||
## New in Excalidraw Automate
|
||||
- I added two new Excalidraw Automate functions
|
||||
${String.fromCharCode(96, 96, 96)}typescript
|
||||
/**
|
||||
* Gets the groupId for the group that contains all the elements, or null if such a group does not exist
|
||||
* @param elements
|
||||
* @returns null or the groupId
|
||||
*/
|
||||
getCommonGroupForElements(elements: ExcalidrawElement[]): string;
|
||||
|
||||
/**
|
||||
* Gets all the elements from elements[] that share one or more groupIds with element.
|
||||
* @param element
|
||||
* @param elements - typically all the non-deleted elements in the scene
|
||||
* @returns
|
||||
*/
|
||||
getElementsInTheSameGroupWithElement(element: ExcalidrawElement, elements: ExcalidrawElement[]): ExcalidrawElement[];
|
||||
${String.fromCharCode(96, 96, 96)}`,
|
||||
"1.7.13": `
|
||||
## Fix from Excalidraw.com
|
||||
- Resize multiple elements from center ([#5560](https://github.com/excalidraw/excalidraw/pull/5560))
|
||||
|
||||
## Obsidian 0.16.0 compatibility (getting ready, because 0.16.0 will be available to insiders soon)
|
||||
- ${String.fromCharCode(96)}Install or update Excalidraw Scripts${String.fromCharCode(96)} was only available via the page header button. Because the page header is hidden by default, the install script action is now available through the pane menu and through the command palette as well.
|
||||
- ${String.fromCharCode(96)}Open selected text as link${String.fromCharCode(96)} page header button is now also available via the pane menu
|
||||
- ${String.fromCharCode(96)}Open in Adjacent Pane${String.fromCharCode(96)} and ${String.fromCharCode(96)}Open in Main Workspace${String.fromCharCode(96)} Excalidraw plugin settings is fixed
|
||||
`,
|
||||
"1.7.12": `
|
||||
## New from Excalidraw.com:
|
||||
- Showing a mid-point for lines and arrows. By touching the mid-point you can easily add an additional point to a two-point line. This is especially helpful when working on a tablet with touch input. ([#5534](https://github.com/excalidraw/excalidraw/pull/5534))
|
||||
- Lock angle when editing a line or an arrow with SHIFT pressed. Pressing SHIFT will restrict the edited point to snap to certain discrete angles. ([#5527](https://github.com/excalidraw/excalidraw/pull/5527))
|
||||
|
||||
## Fixed:
|
||||
- Clicking Obsidian search-results pointing to an element on the canvas works again ([#734](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/734))
|
||||
- The feature to allow resizing and rotation of lines and arrows consisting of 3 or more points by showing the bounding box when selected is back ([#5554](https://github.com/excalidraw/excalidraw/pull/5554))
|
||||
|
||||
## New
|
||||
- You can now use the following frontmatter key to allow/prevent automatic export of PNG/SVG images at a file level. This frontmatter will override export settings for the given file. ([#732](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/732)
|
||||
${String.fromCharCode(96)}excalidraw-autoexport: none|both|svg|png${String.fromCharCode(96)}
|
||||
`,
|
||||
"1.7.11": `
|
||||
## Fixed
|
||||
- Markdown files embed into the Excalidraw canvas crashed when the embedded markdown file included a nested Markdown embed with a block reference (i.e. the markdown document you are dropping into Excalidraw included a quote you referenced from another file using a ${String.fromCharCode(96)}[[other-file#^blockref]]${String.fromCharCode(96)} block or section reference.
|
||||
- Horizontal flipping of arrows and lines broke in 1.7.10. ([#726](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/726))
|
||||
`,
|
||||
"1.7.10": `
|
||||
## New from Excalidraw.com
|
||||
- Improved handling of arrows and lines. ([#5501](https://github.com/excalidraw/excalidraw/pull/5501))
|
||||
|
||||
## Fixed
|
||||
- When opening a document in view-mode or zen-mode the panel buttons no longer flash up for a moment before switching to the desired mode. ([#479](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/479))
|
||||
- The "blinding white screen" no longer flashes up while loading the scene if the scene is dark ([#241](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/241))
|
||||
|
||||
## Under the hood
|
||||
- Finalized migration to React 18 (no longer showing an error about React 17 compatibility mode in console log)
|
||||
`,
|
||||
"1.7.9": `
|
||||
# New features and fixes from Excalidraw.com:
|
||||
## New features and fixes from Excalidraw.com:
|
||||
- The right-click context menu is now scrollable on smaller screens ([#4030](https://github.com/excalidraw/excalidraw/pull/4030), [#5520](https://github.com/excalidraw/excalidraw/pull/5520))
|
||||
- Holding down the shift key while rotating an object will rotate it at discrete angles. Rotation is continuous without the SHIFT key. ([#5500](https://github.com/excalidraw/excalidraw/pull/5500))
|
||||
- Improved cursor alignment when resizing an element proportionally (maintain aspect ratio) by holding SHIFT during resizing. ([#5513](https://github.com/excalidraw/excalidraw/pull/5515))
|
||||
- Improved freedraw performance during editing (now has proper canvas caching), and no more blurry freedraw shapes when exporting on a higher scale. ([#5481](https://github.com/excalidraw/excalidraw/pull/5481))
|
||||
- Sidebar stencil library now correctly scrolls vertically ([#5459](https://github.com/excalidraw/excalidraw/pull/5459))
|
||||
|
||||
# New in Obsidian:
|
||||
## New in Obsidian:
|
||||
- Fullscreen mode on iPad. When there are multiple work panes open, clicking the fullscreen action in the Excalidraw Obsidian menu will hide the other work panes and make Excalidraw fullscreen.
|
||||
|
||||
# Fixes in Obsidian:
|
||||
## Fixes in Obsidian:
|
||||
- Drag&Drop an image from a web browser into Excalidraw ([#697](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/697))
|
||||
- On Obsidian Mobile 1.3.0, when the drawing included an embedded image, switching from markdown-view to Excalidraw-view caused the drawing to disappear (it had to be recovered from backup or synchronization history). ([#715](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/715))
|
||||
- When working on a mobile device (tablet and phone) and using two work panes (one for drawing and the other for editing a markdown document) if you switched focus from the drawing to the markdown document auto-zoom changed the zoom level of the drawing. ([#723](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/723)), ([#705](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/705))
|
||||
|
||||
@@ -55,7 +55,7 @@ export class OpenFileDialog extends FuzzySuggestModal<TFile> {
|
||||
onChooseItem(item: TFile): void {
|
||||
switch (this.action) {
|
||||
case openDialogAction.openFile:
|
||||
this.plugin.openDrawing(item, this.onNewPane?"new-pane":"active-pane");
|
||||
this.plugin.openDrawing(item, this.onNewPane?"new-pane":"active-pane",true);
|
||||
break;
|
||||
case openDialogAction.insertLinkToDrawing:
|
||||
this.plugin.embedDrawing(item);
|
||||
|
||||
@@ -13,6 +13,7 @@ import ExcalidrawView from "../ExcalidrawView";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { sleep } from "../utils/Utils";
|
||||
import { getNewOrAdjacentLeaf } from "../utils/ObsidianUtils";
|
||||
import { checkAndCreateFolder, splitFolderAndFilename } from "src/utils/FileUtils";
|
||||
|
||||
export class Prompt extends Modal {
|
||||
private promptEl: HTMLInputElement;
|
||||
@@ -434,6 +435,8 @@ export class NewFileActions extends Modal {
|
||||
if (!this.path.match(/\.md$/)) {
|
||||
this.path = `${this.path}.md`;
|
||||
}
|
||||
const folderpath = splitFolderAndFilename(this.path).folderpath;
|
||||
checkAndCreateFolder(folderpath);
|
||||
const f = await this.app.vault.create(this.path, data);
|
||||
return f;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { App, MarkdownRenderer, Modal } from "obsidian";
|
||||
import { isVersionNewerThanOther } from "src/utils/Utils";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { FIRST_RUN, RELEASE_NOTES } from "./Messages";
|
||||
|
||||
@@ -11,13 +12,12 @@ export class ReleaseNotes extends Modal {
|
||||
constructor(app: App, plugin: ExcalidrawPlugin, version: string) {
|
||||
super(app);
|
||||
this.plugin = plugin;
|
||||
//@ts-ignore
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
onOpen(): void {
|
||||
this.contentEl.classList.add("excalidraw-release");
|
||||
this.containerEl.classList.add(".excalidraw-release");
|
||||
//this.contentEl.classList.add("excalidraw-release");
|
||||
this.containerEl.classList.add("excalidraw-release");
|
||||
this.titleEl.setText(`Welcome to Excalidraw ${this.version ?? ""}`);
|
||||
this.createForm();
|
||||
}
|
||||
@@ -31,10 +31,10 @@ export class ReleaseNotes extends Modal {
|
||||
|
||||
async createForm() {
|
||||
let prevRelease = this.plugin.settings.previousRelease;
|
||||
prevRelease = this.version === prevRelease ? "0" : prevRelease;
|
||||
prevRelease = this.version === prevRelease ? "0.0.0" : prevRelease;
|
||||
const message = this.version
|
||||
? Object.keys(RELEASE_NOTES)
|
||||
.filter((key) => key > prevRelease)
|
||||
.filter((key) => key === "Intro" || isVersionNewerThanOther(key,prevRelease))
|
||||
.map((key: string) => `# ${key}\n${RELEASE_NOTES[key]}`)
|
||||
.slice(0, 10)
|
||||
.join("\n\n---\n")
|
||||
|
||||
@@ -385,11 +385,17 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "getLargestElement",
|
||||
code: "getLargestElement(elements: ExcalidrawElement[]): ExcalidrawElement;",
|
||||
desc: "Gets the largest element from a group. useful when a text element is grouped with a box, and you want to connect an arrow to the box",
|
||||
field: "getCommonGroupForElements",
|
||||
code: "getCommonGroupForElements(elements: ExcalidrawElement[]): string;",
|
||||
desc: "Gets the groupId for the group that contains all the elements, or null if such a group does not exist",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "getElementsInTheSameGroupWithElement",
|
||||
code: "getElementsInTheSameGroupWithElement(element: ExcalidrawElement, elements: ExcalidrawElement[]): ExcalidrawElement[];",
|
||||
desc: "Gets all the elements from elements[] that share one or more groupIds with element.",
|
||||
after: ""
|
||||
},
|
||||
{
|
||||
field: "activeScript",
|
||||
code: "activeScript: string;",
|
||||
@@ -581,9 +587,9 @@ export const FRONTMATTER_KEYS_INFO: SuggesterInfo[] = [
|
||||
after: ": true",
|
||||
},
|
||||
{
|
||||
field: "export-svgpadding",
|
||||
field: "export-padding",
|
||||
code: null,
|
||||
desc: "If this key is present it will override the default excalidraw embed and export setting. This only affects export to SVG. Specify the export padding for the image.",
|
||||
desc: "If this key is present it will override the default excalidraw embed and export setting. This only affects both SVG and PNG export. Specify the export padding for the image.",
|
||||
after: ": 5",
|
||||
},
|
||||
{
|
||||
@@ -592,4 +598,11 @@ export const FRONTMATTER_KEYS_INFO: SuggesterInfo[] = [
|
||||
desc: "If this key is present it will override the default excalidraw embed and export setting. This only affects export to PNG. Specify the export scale for the image. The typical range is between 0.5 and 5, but you can experiment with other values as well.",
|
||||
after: ": 1",
|
||||
},
|
||||
{
|
||||
field: "autoexport",
|
||||
code: null,
|
||||
desc: "Override autoexport settings for this file. Valid values are\nnone\nboth\npng\nsvg",
|
||||
after: ": png",
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
@@ -28,6 +28,7 @@ export default {
|
||||
"Open an existing drawing - IN THE CURRENT ACTIVE PANE",
|
||||
TRANSCLUDE: "Transclude (embed) a drawing",
|
||||
TRANSCLUDE_MOST_RECENT: "Transclude (embed) the most recently edited drawing",
|
||||
TOGGLE_LEFTHANDED_MODE: "Toggle left-handed mode",
|
||||
NEW_IN_NEW_PANE: "Create a new drawing - IN A NEW PANE",
|
||||
NEW_IN_ACTIVE_PANE: "Create a new drawing - IN THE CURRENT ACTIVE PANE",
|
||||
NEW_IN_POPOUT_WINDOW: "Create a new drawing - IN A POPOUT WINDOW",
|
||||
@@ -35,13 +36,19 @@ export default {
|
||||
"Create a new drawing - IN A NEW PANE - and embed into active document",
|
||||
NEW_IN_ACTIVE_PANE_EMBED:
|
||||
"Create a new drawing - IN THE CURRENT ACTIVE PANE - and embed into active document",
|
||||
NEW_IN_POPOUT_WINDOW_EMBED: "Create a new drawing - IN A POPOUT WINDOW - and embedd into active document",
|
||||
NEW_IN_POPOUT_WINDOW_EMBED: "Create a new drawing - IN A POPOUT WINDOW - and embed into active document",
|
||||
EXPORT_SVG: "Save as SVG next to the current file",
|
||||
EXPORT_PNG: "Save as PNG next to the current file",
|
||||
TOGGLE_LOCK: "Toggle Text Element edit RAW/PREVIEW",
|
||||
DELETE_FILE: "Delete selected Image or Markdown file from Obsidian Vault",
|
||||
INSERT_LINK_TO_ELEMENT:
|
||||
"Copy markdown link for selected element to clipboard",
|
||||
"Copy markdown link for selected element to clipboard. CTRL/CMD+Click to copy group link. SHIFT+click to copy an area link.",
|
||||
INSERT_LINK_TO_ELEMENT_GROUP:
|
||||
"Copy 'group=' markdown link for selected element to clipboard.",
|
||||
INSERT_LINK_TO_ELEMENT_AREA:
|
||||
"Copy 'area=' markdown link for selected element to clipboard.",
|
||||
INSERT_LINK_TO_ELEMENT_NORMAL:
|
||||
"Copy markdown link for selected element to clipboard.",
|
||||
INSERT_LINK_TO_ELEMENT_ERROR: "Select a single element in the scene",
|
||||
INSERT_LINK_TO_ELEMENT_READY: "Link is READY and available on the clipboard",
|
||||
INSERT_LINK: "Insert link to file",
|
||||
@@ -84,6 +91,11 @@ export default {
|
||||
RELEASE_NOTES_DESC:
|
||||
"<b>Toggle ON:</b> Display release notes each time you update Excalidraw to a newer version.<br>" +
|
||||
"<b>Toggle OFF:</b> Silent mode. You can still read release notes on <a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/releases'>GitHub</a>.",
|
||||
NEWVERSION_NOTIFICATION_NAME: "Plugin update notification",
|
||||
NEWVERSION_NOTIFICATION_DESC:
|
||||
"<b>Toggle ON:</b> Show a notification when a new version of the plugin is avaiable.<br>" +
|
||||
"<b>Toggle OFF:</b> Silent mode. You need to check for plugin updates in Community Plugins.",
|
||||
|
||||
FOLDER_NAME: "Excalidraw folder",
|
||||
FOLDER_DESC:
|
||||
"Default location for new drawings. If empty, drawings will be created in the Vault root.",
|
||||
@@ -91,8 +103,8 @@ export default {
|
||||
"Use Excalidraw folder when embedding a drawing into the active document",
|
||||
FOLDER_EMBED_DESC:
|
||||
"Define which folder to place the newly inserted drawing into " +
|
||||
"when using the command palette action: 'Create a new drawing and embed into active document'. " +
|
||||
"ON: Use Excalidraw folder; OFF: use attachments folder defined in Obsidian settings",
|
||||
"when using the command palette action: 'Create a new drawing and embed into active document'.<br>" +
|
||||
"<b>Toggle ON:</b> Use Excalidraw folder<br><b>Toggle OFF:</b> use the attachments folder defined in Obsidian settings.",
|
||||
TEMPLATE_NAME: "Excalidraw template file",
|
||||
TEMPLATE_DESC:
|
||||
"Full filepath to the Excalidraw template. " +
|
||||
@@ -116,7 +128,7 @@ export default {
|
||||
"be saved without compression, so that you can read and edit the JSON string. The drawing will be compressed again " +
|
||||
"once you switch back to Excalidraw view. " +
|
||||
"The setting only has effect 'point forward', meaning, existing drawings will not be effected by the setting " +
|
||||
"until you open them and save them. ",
|
||||
"until you open them and save them.<br><b>Toggle ON:</b> Compress drawing JSON<br><b>Toggle OFF:</b> Leave drawing JSON uncompressed",
|
||||
AUTOSAVE_NAME: "Enable Autosave",
|
||||
AUTOSAVE_DESC:
|
||||
"Automatically save the active drawing, in case there are changes, every 15, 30 seconds, or 1, 2, 3, 4, or 5 minute. Save normally happens when you close Excalidraw or Obsidian, or move " +
|
||||
@@ -137,8 +149,8 @@ export default {
|
||||
"Filename prefix when embedding a new drawing into a markdown note",
|
||||
FILENAME_PREFIX_EMBED_DESC:
|
||||
"Should the filename of the newly inserted drawing start with the name of the active markdown note " +
|
||||
"when using the command palette action: <code>Create a new drawing and embed into active document</code>? " +
|
||||
"ON: Yes, OFF: Not",
|
||||
"when using the command palette action: <code>Create a new drawing and embed into active document</code>?<br>" +
|
||||
"<b>Toggle ON:</b> Yes, the filename of a new drawing should start with filename of the active document<br><b>Toggle OFF:</b> No, filename of a new drawing should not include the filename of the active document",
|
||||
FILENAME_POSTFIX_NAME:
|
||||
"Custom text after markdown Note's name when embedding",
|
||||
FILENAME_POSTFIX_DESC:
|
||||
@@ -149,26 +161,25 @@ export default {
|
||||
FILENAME_EXCALIDRAW_EXTENSION_NAME: ".excalidraw.md or .md",
|
||||
FILENAME_EXCALIDRAW_EXTENSION_DESC:
|
||||
"This setting does not apply if you use Excalidraw in compatibility mode, " +
|
||||
"i.e. you are not using Excalidraw markdown files. Toggle ON = filename ends with .excalidraw.md, Toggle OFF = filename ends with .md",
|
||||
/*SVG_IN_MD_NAME: "SVG Snapshot to markdown file",
|
||||
SVG_IN_MD_DESC: "If the switch is 'on' Excalidraw will include an SVG snapshot in the markdown file. "+
|
||||
"When SVG snapshots are saved to the Excalidraw.md file, drawings that include large png, jpg, gif images may take extreme long time to open in markdown view. " +
|
||||
"On the other hand, SVG snapshots provide some level of platform independence and longevity to your drawings. Even if Excalidraw will no longer exist, the snapshot " +
|
||||
"can be opened with an app that reads SVGs. In addition hover previews will be less resource intensive if SVG snapshots are enabled.",*/
|
||||
"i.e. you are not using Excalidraw markdown files.<br><b>Toggle ON:</b> filename ends with .excalidraw.md<br><b>Toggle OFF:</b> filename ends with .md",
|
||||
DISPLAY_HEAD: "Display",
|
||||
LEFTHANDED_MODE_NAME: "Left-handed mode",
|
||||
LEFTHANDED_MODE_DESC:
|
||||
"Currently only has effect in tray-mode. If turned on, the tray will be on the right side.",
|
||||
"Currently only has effect in tray-mode. If turned on, the tray will be on the right side." +
|
||||
"<br><b>Toggle ON:</b> Left-handed mode.<br><b>Toggle OFF:</b> Right-handed moded",
|
||||
MATCH_THEME_NAME: "New drawing to match Obsidian theme",
|
||||
MATCH_THEME_DESC:
|
||||
"If theme is dark, new drawing will be created in dark mode. This does not apply when you use a template for new drawings. " +
|
||||
"Also this will not effect when you open an existing drawing. Those will follow the theme of the template/drawing respectively.",
|
||||
"Also this will not effect when you open an existing drawing. Those will follow the theme of the template/drawing respectively." +
|
||||
"<br><b>Toggle ON:</b> Follow Obsidian Theme<br><b>Toggle OFF:</b>Follow theme defined in your template",
|
||||
MATCH_THEME_ALWAYS_NAME: "Existing drawings to match Obsidian theme",
|
||||
MATCH_THEME_ALWAYS_DESC:
|
||||
"If theme is dark, drawings will be opened in dark mode. If your theme is light, they will be opened in light mode. ",
|
||||
"If theme is dark, drawings will be opened in dark mode. If your theme is light, they will be opened in light mode. " +
|
||||
"<br><b>Toggle ON:</b> Match Obsidian theme<br><b>Toggle OFF:</b> Open with the same theme as last saved",
|
||||
MATCH_THEME_TRIGGER_NAME: "Excalidraw to follow when Obsidian Theme changes",
|
||||
MATCH_THEME_TRIGGER_DESC:
|
||||
"If this option is enabled open Excalidraw pane will switch to light/dark mode when Obsidian theme changes. ",
|
||||
"If this option is enabled open Excalidraw pane will switch to light/dark mode when Obsidian theme changes. " +
|
||||
"<br><b>Toggle ON:</b> Follow theme changes<br><b>Toggle OFF:</b> Drawings are not effected by Obsidian theme changes",
|
||||
DEFAULT_OPEN_MODE_NAME: "Default mode when opening Excalidraw",
|
||||
DEFAULT_OPEN_MODE_DESC:
|
||||
"Specifies the mode how Excalidraw opens: Normal, Zen, or View mode. You may also set this behavior on a file level by " +
|
||||
@@ -177,7 +188,8 @@ export default {
|
||||
DEFAULT_PEN_MODE_DESC:
|
||||
"Should pen mode be automatically enabled when opening Excalidraw?",
|
||||
ZOOM_TO_FIT_NAME: "Zoom to fit on view resize",
|
||||
ZOOM_TO_FIT_DESC: "Zoom to fit drawing when the pane is resized",
|
||||
ZOOM_TO_FIT_DESC: "Zoom to fit drawing when the pane is resized" +
|
||||
"<br><b>Toggle ON:</b> Zoom to fit<br><b>Toggle OFF:</b> Auto zoom disabled",
|
||||
ZOOM_TO_FIT_MAX_LEVEL_NAME: "Zoom to fit max ZOOM level",
|
||||
ZOOM_TO_FIT_MAX_LEVEL_DESC:
|
||||
"Set the maximum level to which zoom to fit will enlarge the drawing. Minimum is 0.5 (50%) and maximum is 10 (1000%).",
|
||||
@@ -294,7 +306,8 @@ export default {
|
||||
"For a number of reasons, the same approach cannot be used to expedite the loading of drawings with many embedded objects. See demonstration <a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/1.6.23' target='_blank'>here</a>.",
|
||||
EMBED_PREVIEW_SVG_NAME: "Display SVG in markdown preview",
|
||||
EMBED_PREVIEW_SVG_DESC:
|
||||
"The default is to display drawings as SVG images in the markdown preview. Turning this feature off, the markdown preview will display the drawing as an embedded PNG image.",
|
||||
"<b>Toggle ON</b>: Embed drawing as an <a href='https://en.wikipedia.org/wiki/Scalable_Vector_Graphics' target='_blank'>SVG</a> image into the markdown preview.<br>" +
|
||||
"<b>Toggle OFF</b>: Embedd drawing as a <a href='' target='_blank'>PNG</a> image. Note, that some of the <a href='https://www.youtube.com/watch?v=yZQoJg2RCKI&t=633s' target='_blank'>image block referencing features</a> do not work with PNG embeds.",
|
||||
PREVIEW_MATCH_OBSIDIAN_NAME: "Excalidraw preview to match Obsidian theme",
|
||||
PREVIEW_MATCH_OBSIDIAN_DESC:
|
||||
"Image preview in documents should match the Obsidian theme. If enabled, when Obsidian is in dark mode, Excalidraw images will render in dark mode. " +
|
||||
@@ -318,9 +331,11 @@ export default {
|
||||
EXPORT_BACKGROUND_NAME: "Export image with background",
|
||||
EXPORT_BACKGROUND_DESC:
|
||||
"If turned off, the exported image will be transparent.",
|
||||
EXPORT_SVG_PADDING_NAME: "SVG Padding",
|
||||
EXPORT_SVG_PADDING_DESC:
|
||||
"The padding (in pixels) around the exported SVG image. If you have curved lines close to the edge of the image they might get cropped during SVG export. You can increase this value to avoid cropping.",
|
||||
EXPORT_PADDING_NAME: "Image Padding",
|
||||
EXPORT_PADDING_DESC:
|
||||
"The padding (in pixels) around the exported SVG or PNG image. " +
|
||||
"If you have curved lines close to the edge of the image they might get cropped during image export. You can increase this value to avoid cropping. " +
|
||||
"You can also override this setting at a file level by adding the <code>excalidraw-export-padding: 5<code> frontmatter key.",
|
||||
EXPORT_THEME_NAME: "Export image with theme",
|
||||
EXPORT_THEME_DESC:
|
||||
"Export the image matching the dark/light theme of your drawing. If turned off, " +
|
||||
@@ -336,9 +351,14 @@ export default {
|
||||
"Automatically create an SVG export of your drawing matching the title of your file. " +
|
||||
"The plugin will save the *.SVG file in the same folder as the drawing. " +
|
||||
"Embed the .svg file into your documents instead of Excalidraw making you embeds platform independent. " +
|
||||
"While the auto-export switch is on, this file will get updated every time you edit the Excalidraw drawing with the matching name.",
|
||||
"While the auto-export switch is on, this file will get updated every time you edit the Excalidraw drawing with the matching name. " +
|
||||
"You can override this setting on a file level by adding the <code>excalidraw-autoexport</code> frontmatter key. Valid values for this key are " +
|
||||
"<code>none</code>,<code>both</code>,<code>svg</code>, and <code>png</code>",
|
||||
EXPORT_PNG_NAME: "Auto-export PNG",
|
||||
EXPORT_PNG_DESC: "Same as the auto-export SVG, but for *.PNG",
|
||||
EXPORT_BOTH_DARK_AND_LIGHT_NAME: "Export both dark- and light-themed image",
|
||||
EXPORT_BOTH_DARK_AND_LIGHT_DESC: "When enabled, Excalidraw will export two files instead of one: filename.dark.png, filename.light.png and/or filename.dark.svg and filename.light.svg<br>"+
|
||||
"Double files will be exported both if auto-export SVG or PNG (or both) are enabled, as well as when clicking export on a single image.",
|
||||
COMPATIBILITY_HEAD: "Compatibility features",
|
||||
EXPORT_EXCALIDRAW_NAME: "Auto-export Excalidraw",
|
||||
EXPORT_EXCALIDRAW_DESC: "Same as the auto-export SVG, but for *.Excalidraw",
|
||||
|
||||
266
src/main.ts
266
src/main.ts
@@ -41,6 +41,7 @@ import {
|
||||
SCRIPT_INSTALL_FOLDER,
|
||||
VIRGIL_FONT,
|
||||
VIRGIL_DATAURL,
|
||||
EXPORT_TYPES,
|
||||
} from "./Constants";
|
||||
import ExcalidrawView, { TextMode, getTextMode } from "./ExcalidrawView";
|
||||
import {
|
||||
@@ -73,7 +74,6 @@ import {
|
||||
getDrawingFilename,
|
||||
getEmbedFilename,
|
||||
getIMGFilename,
|
||||
getIMGPathFromExcalidrawFile,
|
||||
getNewUniqueFilepath,
|
||||
} from "./utils/FileUtils";
|
||||
import {
|
||||
@@ -83,6 +83,8 @@ import {
|
||||
setLeftHandedMode,
|
||||
sleep,
|
||||
debug,
|
||||
isVersionNewerThanOther,
|
||||
getExportTheme,
|
||||
} from "./utils/Utils";
|
||||
import { getAttachmentsFolderAndFilePath, getNewOrAdjacentLeaf, getParentOfClass, isObsidianThemeDark } from "./utils/ObsidianUtils";
|
||||
//import { OneOffs } from "./OneOffs";
|
||||
@@ -99,6 +101,7 @@ import { ReleaseNotes } from "./dialogs/ReleaseNotes";
|
||||
import { decompressFromBase64 } from "lz-string";
|
||||
import { Packages } from "./types";
|
||||
import * as React from "react";
|
||||
import { ScriptInstallPrompt } from "./dialogs/ScriptInstallPrompt";
|
||||
|
||||
|
||||
declare module "obsidian" {
|
||||
@@ -226,8 +229,8 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
if (this.settings.showReleaseNotes) {
|
||||
//I am repurposing imageElementNotice, if the value is true, this means the plugin was just newly installed to Obsidian.
|
||||
const obsidianJustInstalled = this.settings.imageElementNotice;
|
||||
|
||||
if (PLUGIN_VERSION > this.settings.previousRelease) {
|
||||
|
||||
if (isVersionNewerThanOther(PLUGIN_VERSION, this.settings.previousRelease)) {
|
||||
new ReleaseNotes(
|
||||
this.app,
|
||||
this,
|
||||
@@ -310,10 +313,10 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
//@ts-ignore
|
||||
win.MathJax.startup.pagePromise.then(async () => {
|
||||
//https://github.com/xldenis/obsidian-latex/blob/master/main.ts
|
||||
const file = self.app.vault.getAbstractFileByPath("preamble.sty");
|
||||
const file = app.vault.getAbstractFileByPath("preamble.sty");
|
||||
const preamble: string =
|
||||
file && file instanceof TFile
|
||||
? await self.app.vault.read(file)
|
||||
? await app.vault.read(file)
|
||||
: null;
|
||||
try {
|
||||
if (preamble) {
|
||||
@@ -346,7 +349,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
const self = this;
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
let leaf: WorkspaceLeaf;
|
||||
for (leaf of self.app.workspace.getLeavesOfType("markdown")) {
|
||||
for (leaf of app.workspace.getLeavesOfType("markdown")) {
|
||||
if (
|
||||
leaf.view instanceof MarkdownView &&
|
||||
self.isExcalidrawFile(leaf.view.file)
|
||||
@@ -453,7 +456,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
if (file) {
|
||||
await this.app.vault.modify(file as TFile, data);
|
||||
} else {
|
||||
await checkAndCreateFolder(this.app.vault, folder);
|
||||
await checkAndCreateFolder(folder);
|
||||
file = await this.app.vault.create(localPath, data);
|
||||
}
|
||||
return file;
|
||||
@@ -833,7 +836,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
).folder;
|
||||
const file = await this.createDrawing(filename, folder);
|
||||
await this.embedDrawing(file);
|
||||
this.openDrawing(file, location);
|
||||
this.openDrawing(file, location, true);
|
||||
};
|
||||
|
||||
this.addCommand({
|
||||
@@ -963,7 +966,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
return false;
|
||||
}
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (view) {
|
||||
if (view && !view.compatibilityMode) {
|
||||
view.changeTextMode(
|
||||
view.textMode === TextMode.parsed ? TextMode.raw : TextMode.parsed,
|
||||
);
|
||||
@@ -973,6 +976,20 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "scriptengine-store",
|
||||
name: t("INSTALL_SCRIPT_BUTTON"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
return (
|
||||
Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView))
|
||||
);
|
||||
}
|
||||
new ScriptInstallPrompt(this).open();
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "delete-file",
|
||||
name: t("DELETE_FILE"),
|
||||
@@ -1028,20 +1045,82 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
this.addCommand({
|
||||
id: "insert-link-to-element",
|
||||
hotkeys: [{ modifiers: ["Ctrl" || "Meta", "Shift"], key: "k" }],
|
||||
name: t("INSERT_LINK_TO_ELEMENT"),
|
||||
name: t("INSERT_LINK_TO_ELEMENT_NORMAL"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
return Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView))
|
||||
}
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (view) {
|
||||
view.copyLinkToSelectedElementToClipboard();
|
||||
view.copyLinkToSelectedElementToClipboard("");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "insert-link-to-element-group",
|
||||
name: t("INSERT_LINK_TO_ELEMENT_GROUP"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
return Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView))
|
||||
}
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (view) {
|
||||
view.copyLinkToSelectedElementToClipboard("group=");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "insert-link-to-element-area",
|
||||
name: t("INSERT_LINK_TO_ELEMENT_AREA"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
return Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView))
|
||||
}
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (view) {
|
||||
view.copyLinkToSelectedElementToClipboard("area=");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "toggle-lefthanded-mode",
|
||||
name: t("TOGGLE_LEFTHANDED_MODE"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
if(this.app.workspace.getActiveViewOfType(ExcalidrawView)) {
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
const api = view?.excalidrawAPI;
|
||||
if(!api) return false;
|
||||
const st = api.getAppState();
|
||||
if(!st.trayModeEnabled) return false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
(async()=>{
|
||||
const isLeftHanded = this.settings.isLeftHanded;
|
||||
await this.loadSettings(false);
|
||||
this.settings.isLeftHanded = !isLeftHanded;
|
||||
this.saveSettings();
|
||||
//not clear why I need to do this. If I don't double apply the stylesheet changes
|
||||
//then the style won't be applied in the popout windows
|
||||
setLeftHandedMode(!isLeftHanded);
|
||||
setTimeout(()=>setLeftHandedMode(!isLeftHanded));
|
||||
})()
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "insert-image",
|
||||
name: t("INSERT_IMAGE"),
|
||||
@@ -1231,7 +1310,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
FRONTMATTER + (await this.exportSceneToMD(data)),
|
||||
);
|
||||
if (this.settings.keepInSync) {
|
||||
[".svg", ".png"].forEach((ext: string) => {
|
||||
EXPORT_TYPES.forEach((ext: string) => {
|
||||
const oldIMGpath =
|
||||
file.path.substring(0, file.path.lastIndexOf(".excalidraw")) + ext;
|
||||
const imgFile = this.app.vault.getAbstractFileByPath(
|
||||
@@ -1342,7 +1421,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
"markdown"
|
||||
) {
|
||||
// Then check for the excalidraw frontMatterKey
|
||||
const cache = self.app.metadataCache.getCache(state.state.file);
|
||||
const cache = app.metadataCache.getCache(state.state.file);
|
||||
|
||||
if (cache?.frontmatter && cache.frontmatter[FRONTMATTER_KEY]) {
|
||||
// If we have it, force the view type to excalidraw
|
||||
@@ -1363,43 +1442,6 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
// Add a menu item to go back to Excalidraw view
|
||||
/*this.register(
|
||||
around(MarkdownView.prototype, {
|
||||
onPaneMenu(next) {
|
||||
return function (menu: Menu) {
|
||||
const file = this.file;
|
||||
const cache = file
|
||||
? self.app.metadataCache.getFileCache(file)
|
||||
: null;
|
||||
|
||||
if (
|
||||
!file ||
|
||||
!cache?.frontmatter ||
|
||||
!cache.frontmatter[FRONTMATTER_KEY]
|
||||
) {
|
||||
return next.call(this, menu);
|
||||
}
|
||||
|
||||
menu
|
||||
.addItem((item) => {
|
||||
item
|
||||
.setTitle(t("OPEN_AS_EXCALIDRAW"))
|
||||
.setIcon(ICON_NAME)
|
||||
.setSection("pane")
|
||||
.onClick(() => {
|
||||
self.excalidrawFileModes[this.leaf.id || file.path] =
|
||||
VIEW_TYPE_EXCALIDRAW;
|
||||
self.setExcalidrawView(this.leaf);
|
||||
});
|
||||
})
|
||||
.addSeparator();
|
||||
next.call(this, menu);
|
||||
};
|
||||
},
|
||||
}),
|
||||
);*/
|
||||
}
|
||||
|
||||
private popScope: Function = null;
|
||||
@@ -1421,21 +1463,21 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
if (!self.settings.keepInSync) {
|
||||
return;
|
||||
}
|
||||
[".svg", ".png", ".excalidraw"].forEach(async (ext: string) => {
|
||||
const oldIMGpath = getIMGPathFromExcalidrawFile(oldPath, ext);
|
||||
const imgFile = self.app.vault.getAbstractFileByPath(
|
||||
[EXPORT_TYPES, "excalidraw"].flat().forEach(async (ext: string) => {
|
||||
const oldIMGpath = getIMGFilename(oldPath, ext);
|
||||
const imgFile = app.vault.getAbstractFileByPath(
|
||||
normalizePath(oldIMGpath),
|
||||
);
|
||||
if (imgFile && imgFile instanceof TFile) {
|
||||
const newIMGpath = getIMGPathFromExcalidrawFile(file.path, ext);
|
||||
await self.app.fileManager.renameFile(imgFile, newIMGpath);
|
||||
const newIMGpath = getIMGFilename(file.path, ext);
|
||||
await app.fileManager.renameFile(imgFile, newIMGpath);
|
||||
}
|
||||
});
|
||||
};
|
||||
self.registerEvent(self.app.vault.on("rename", renameEventHandler));
|
||||
self.registerEvent(app.vault.on("rename", renameEventHandler));
|
||||
|
||||
const modifyEventHandler = async (file: TFile) => {
|
||||
const leaves = self.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
const leaves = app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
leaves.forEach(async (leaf: WorkspaceLeaf) => {
|
||||
const excalidrawView = leaf.view as ExcalidrawView;
|
||||
if (
|
||||
@@ -1468,7 +1510,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
});
|
||||
};
|
||||
self.registerEvent(self.app.vault.on("modify", modifyEventHandler));
|
||||
self.registerEvent(app.vault.on("modify", modifyEventHandler));
|
||||
|
||||
//watch file delete and delete corresponding .svg and .png
|
||||
const deleteEventHandler = async (file: TFile) => {
|
||||
@@ -1483,7 +1525,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
|
||||
//close excalidraw view where this file is open
|
||||
const leaves = self.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
const leaves = app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
for (let i = 0; i < leaves.length; i++) {
|
||||
if ((leaves[i].view as ExcalidrawView).file.path == file.path) {
|
||||
await leaves[i].setViewState({
|
||||
@@ -1496,29 +1538,29 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
//delete PNG and SVG files as well
|
||||
if (self.settings.keepInSync) {
|
||||
setTimeout(() => {
|
||||
[".svg", ".png", ".excalidraw"].forEach(async (ext: string) => {
|
||||
const imgPath = getIMGPathFromExcalidrawFile(file.path, ext);
|
||||
const imgFile = self.app.vault.getAbstractFileByPath(
|
||||
[EXPORT_TYPES, "excalidraw"].flat().forEach(async (ext: string) => {
|
||||
const imgPath = getIMGFilename(file.path, ext);
|
||||
const imgFile = app.vault.getAbstractFileByPath(
|
||||
normalizePath(imgPath),
|
||||
);
|
||||
if (imgFile && imgFile instanceof TFile) {
|
||||
await self.app.vault.delete(imgFile);
|
||||
await app.vault.delete(imgFile);
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
};
|
||||
self.registerEvent(self.app.vault.on("delete", deleteEventHandler));
|
||||
self.registerEvent(app.vault.on("delete", deleteEventHandler));
|
||||
|
||||
//save open drawings when user quits the application
|
||||
//Removing because it is not guaranteed to run, and frequently gets terminated mid flight, causing file consistency issues
|
||||
/*const quitEventHandler = async () => {
|
||||
const leaves = self.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
const leaves = app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
for (let i = 0; i < leaves.length; i++) {
|
||||
await (leaves[i].view as ExcalidrawView).save(true);
|
||||
}
|
||||
};
|
||||
self.registerEvent(self.app.workspace.on("quit", quitEventHandler));*/
|
||||
self.registerEvent(app.workspace.on("quit", quitEventHandler));*/
|
||||
|
||||
//save Excalidraw leaf and update embeds when switching to another leaf
|
||||
const activeLeafChangeEventHandler = async (leaf: WorkspaceLeaf) => {
|
||||
@@ -1587,7 +1629,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
};
|
||||
self.registerEvent(
|
||||
self.app.workspace.on(
|
||||
app.workspace.on(
|
||||
"active-leaf-change",
|
||||
activeLeafChangeEventHandler,
|
||||
),
|
||||
@@ -1595,7 +1637,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
self.addFileSaveTriggerEventHandlers();
|
||||
|
||||
const metaCache: MetadataCache = self.app.metadataCache;
|
||||
const metaCache: MetadataCache = app.metadataCache;
|
||||
//@ts-ignore
|
||||
metaCache.getCachedFiles().forEach((filename: string) => {
|
||||
const fm = metaCache.getCache(filename)?.frontmatter;
|
||||
@@ -1604,7 +1646,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
filename.match(/\.excalidraw$/)
|
||||
) {
|
||||
self.updateFileCache(
|
||||
self.app.vault.getAbstractFileByPath(filename) as TFile,
|
||||
app.vault.getAbstractFileByPath(filename) as TFile,
|
||||
fm,
|
||||
);
|
||||
}
|
||||
@@ -1760,50 +1802,75 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
public async embedDrawing(file: TFile) {
|
||||
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (activeView && activeView.file) {
|
||||
const data = this.app.metadataCache.fileToLinktext(
|
||||
const excalidrawRelativePath = this.app.metadataCache.fileToLinktext(
|
||||
file,
|
||||
activeView.file.path,
|
||||
this.settings.embedType === "excalidraw",
|
||||
);
|
||||
const editor = activeView.editor;
|
||||
|
||||
//embed Excalidraw
|
||||
if (this.settings.embedType === "excalidraw") {
|
||||
editor.replaceSelection(
|
||||
this.settings.embedWikiLink
|
||||
? `![[${data}]]`
|
||||
: `})`,
|
||||
? `![[${excalidrawRelativePath}]]`
|
||||
: `})`,
|
||||
);
|
||||
editor.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
const filename = getIMGPathFromExcalidrawFile(
|
||||
data,
|
||||
`.${this.settings.embedType.toLowerCase()}`,
|
||||
);
|
||||
const filepath = getIMGPathFromExcalidrawFile(
|
||||
file.path,
|
||||
`.${this.settings.embedType.toLowerCase()}`,
|
||||
);
|
||||
//embed image
|
||||
let theme = this.settings.autoExportLightAndDark
|
||||
? getExportTheme (
|
||||
this,
|
||||
file,
|
||||
this.settings.exportWithTheme
|
||||
? isObsidianThemeDark() ? "dark":"light"
|
||||
: "light"
|
||||
)
|
||||
: "";
|
||||
|
||||
const imgFile = this.app.vault.getAbstractFileByPath(filepath);
|
||||
theme = theme===""?"":theme+".";
|
||||
|
||||
const imageRelativePath = getIMGFilename(
|
||||
excalidrawRelativePath,
|
||||
theme+this.settings.embedType.toLowerCase(),
|
||||
);
|
||||
const imageFullpath = getIMGFilename(
|
||||
file.path,
|
||||
theme+this.settings.embedType.toLowerCase(),
|
||||
);
|
||||
|
||||
//will hold incorrect value if theme==="", however in that case it won't be used
|
||||
const otherTheme = theme === "dark." ? "light.":"dark.";
|
||||
const otherImageRelativePath = getIMGFilename(
|
||||
excalidrawRelativePath,
|
||||
otherTheme+this.settings.embedType.toLowerCase(),
|
||||
);
|
||||
|
||||
|
||||
const imgFile = this.app.vault.getAbstractFileByPath(imageFullpath);
|
||||
if (!imgFile) {
|
||||
await this.app.vault.create(filepath, "");
|
||||
await this.app.vault.create(imageFullpath, "");
|
||||
await sleep(200);
|
||||
}
|
||||
|
||||
editor.replaceSelection(
|
||||
this.settings.embedWikiLink
|
||||
? `![[${filename}]]\n%%[[${data}|🖋 Edit in Excalidraw]]%%`
|
||||
: `})\n%%[🖋 Edit in Excalidraw](${encodeURI(
|
||||
data,
|
||||
)})%%`,
|
||||
? `![[${imageRelativePath}]]\n%%[[${excalidrawRelativePath}|🖋 Edit in Excalidraw]]${
|
||||
otherImageRelativePath ? ", and the [["+otherImageRelativePath+"|"+otherTheme.split(".")[0]+" exported image]]":""}%%`
|
||||
: `})\n%%[🖋 Edit in Excalidraw](${encodeURI(
|
||||
excalidrawRelativePath,
|
||||
)})${otherImageRelativePath?", and the ["+otherTheme.split(".")[0]+" exported image]("+encodeURI(otherImageRelativePath)+")":""}%%`,
|
||||
);
|
||||
editor.focus();
|
||||
}
|
||||
}
|
||||
|
||||
public async loadSettings() {
|
||||
public async loadSettings(applyLefthandedMode:boolean = true) {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
setLeftHandedMode(this.settings.isLeftHanded);
|
||||
if(applyLefthandedMode) setLeftHandedMode(this.settings.isLeftHanded);
|
||||
this.settings.autosave = true;
|
||||
this.settings.autosaveInterval = 10000;
|
||||
}
|
||||
@@ -1848,7 +1915,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
public openDrawing(
|
||||
drawingFile: TFile,
|
||||
location: "active-pane"|"new-pane"|"popout-window"
|
||||
location: "active-pane"|"new-pane"|"popout-window",
|
||||
active: boolean = false,
|
||||
subpath?: string
|
||||
) {
|
||||
let leaf: WorkspaceLeaf;
|
||||
if(location === "popout-window") {
|
||||
@@ -1862,10 +1931,17 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
leaf.setViewState({
|
||||
leaf.openFile(
|
||||
drawingFile,
|
||||
!subpath || subpath === ""
|
||||
? {active}
|
||||
: { active, eState: { subpath } }
|
||||
);
|
||||
|
||||
/* leaf.setViewState({
|
||||
type: VIEW_TYPE_EXCALIDRAW,
|
||||
state: { file: drawingFile.path },
|
||||
});
|
||||
state: { file: drawingFile.path, eState: {subpath}},
|
||||
});*/
|
||||
}
|
||||
|
||||
public async getBlankDrawing(): Promise<string> {
|
||||
@@ -1944,7 +2020,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
const folderpath = normalizePath(
|
||||
foldername ? foldername : this.settings.folder,
|
||||
);
|
||||
await checkAndCreateFolder(this.app.vault, folderpath); //create folder if it does not exist
|
||||
await checkAndCreateFolder(folderpath); //create folder if it does not exist
|
||||
const fname = getNewUniqueFilepath(this.app.vault, filename, folderpath);
|
||||
const file = await this.app.vault.create(
|
||||
fname,
|
||||
@@ -1971,7 +2047,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
initData?: string,
|
||||
): Promise<string> {
|
||||
const file = await this.createDrawing(filename, foldername, initData);
|
||||
this.openDrawing(file, location);
|
||||
this.openDrawing(file, location, true);
|
||||
return file.path;
|
||||
}
|
||||
|
||||
@@ -2023,7 +2099,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
filename = `${filename}.excalidrawlib`;
|
||||
const folderpath = normalizePath(this.settings.folder);
|
||||
await checkAndCreateFolder(this.app.vault, folderpath); //create folder if it does not exist
|
||||
await checkAndCreateFolder(folderpath); //create folder if it does not exist
|
||||
const fname = getNewUniqueFilepath(
|
||||
this.app.vault,
|
||||
filename,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Notice, TFile } from "obsidian";
|
||||
import * as React from "react";
|
||||
import { ActionButton } from "./ActionButton";
|
||||
import { ICONS } from "./ActionIcons";
|
||||
import { SCRIPT_INSTALL_FOLDER } from "../Constants";
|
||||
import { SCRIPT_INSTALL_FOLDER, CTRL_OR_CMD } from "../Constants";
|
||||
import { insertLaTeXToView, search } from "../ExcalidrawAutomate";
|
||||
import ExcalidrawView, { TextMode } from "../ExcalidrawView";
|
||||
import { t } from "../lang/helpers";
|
||||
@@ -464,8 +464,10 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
<ActionButton
|
||||
key={"link-to-element"}
|
||||
title={t("INSERT_LINK_TO_ELEMENT")}
|
||||
action={() => {
|
||||
this.props.view.copyLinkToSelectedElementToClipboard();
|
||||
action={(e:React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
this.props.view.copyLinkToSelectedElementToClipboard(
|
||||
e[CTRL_OR_CMD] ? "group=" : (e.shiftKey ? "area=" : "")
|
||||
);
|
||||
}}
|
||||
icon={ICONS.copyElementLink}
|
||||
view={this.props.view}
|
||||
|
||||
@@ -62,6 +62,7 @@ export interface ExcalidrawSettings {
|
||||
keepInSync: boolean;
|
||||
autoexportSVG: boolean;
|
||||
autoexportPNG: boolean;
|
||||
autoExportLightAndDark: boolean;
|
||||
autoexportExcalidraw: boolean;
|
||||
embedType: "excalidraw" | "PNG" | "SVG";
|
||||
embedWikiLink: boolean;
|
||||
@@ -101,6 +102,7 @@ export interface ExcalidrawSettings {
|
||||
defaultTrayMode: boolean;
|
||||
previousRelease: string;
|
||||
showReleaseNotes: boolean;
|
||||
showNewVersionNotification: boolean;
|
||||
mathjaxSourceURL: string;
|
||||
}
|
||||
|
||||
@@ -144,10 +146,11 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
pngExportScale: 1,
|
||||
exportWithTheme: true,
|
||||
exportWithBackground: true,
|
||||
exportPaddingSVG: 10,
|
||||
exportPaddingSVG: 10, //since 1.6.17, not only SVG but also PNG
|
||||
keepInSync: false,
|
||||
autoexportSVG: false,
|
||||
autoexportPNG: false,
|
||||
autoExportLightAndDark: false,
|
||||
autoexportExcalidraw: false,
|
||||
embedType: "excalidraw",
|
||||
embedWikiLink: true,
|
||||
@@ -182,6 +185,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
defaultTrayMode: false,
|
||||
previousRelease: "1.6.13",
|
||||
showReleaseNotes: true,
|
||||
showNewVersionNotification: true,
|
||||
mathjaxSourceURL: "https://cdn.jsdelivr.net/npm/mathjax@3.2.1/es5/tex-svg.js"
|
||||
};
|
||||
|
||||
@@ -219,7 +223,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
this.plugin.saveSettings();
|
||||
if (this.requestReloadDrawings) {
|
||||
const exs =
|
||||
this.plugin.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
for (const v of exs) {
|
||||
if (v.view instanceof ExcalidrawView) {
|
||||
await v.view.save(false);
|
||||
@@ -269,6 +273,18 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(t("NEWVERSION_NOTIFICATION_NAME"))
|
||||
.setDesc(fragWithHTML(t("NEWVERSION_NOTIFICATION_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.showNewVersionNotification)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.showNewVersionNotification = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(t("FOLDER_NAME"))
|
||||
.setDesc(fragWithHTML(t("FOLDER_DESC")))
|
||||
@@ -443,7 +459,10 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
.setValue(this.plugin.settings.isLeftHanded)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.isLeftHanded = value;
|
||||
setLeftHandedMode(value);
|
||||
//not clear why I need to do this. If I don't double apply the stylesheet changes
|
||||
//then the style won't be applied in the popout windows
|
||||
setLeftHandedMode(value);
|
||||
setTimeout(()=>setLeftHandedMode(value));
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
@@ -581,7 +600,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(t("LINK_BRACKETS_NAME"))
|
||||
.setName(fragWithHTML(t("LINK_BRACKETS_NAME")))
|
||||
.setDesc(fragWithHTML(t("LINK_BRACKETS_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
@@ -993,8 +1012,8 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
let exportPadding: HTMLDivElement;
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(t("EXPORT_SVG_PADDING_NAME"))
|
||||
.setDesc(fragWithHTML(t("EXPORT_SVG_PADDING_DESC")))
|
||||
.setName(t("EXPORT_PADDING_NAME"))
|
||||
.setDesc(fragWithHTML(t("EXPORT_PADDING_DESC")))
|
||||
.addSlider((slider) =>
|
||||
slider
|
||||
.setLimits(0, 50, 5)
|
||||
@@ -1090,6 +1109,18 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(t("EXPORT_BOTH_DARK_AND_LIGHT_NAME"))
|
||||
.setDesc(fragWithHTML(t("EXPORT_BOTH_DARK_AND_LIGHT_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.autoExportLightAndDark)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.autoExportLightAndDark = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
this.containerEl.createEl("h1", { text: t("COMPATIBILITY_HEAD") });
|
||||
|
||||
new Setting(containerEl)
|
||||
|
||||
@@ -42,7 +42,7 @@ export function splitFolderAndFilename(filepath: string): {
|
||||
* @param newExtension - extension of IMG file in ".extension" format
|
||||
* @returns
|
||||
*/
|
||||
export function getIMGPathFromExcalidrawFile(
|
||||
/*export function getIMGPathFromExcalidrawFile(
|
||||
excalidrawPath: string,
|
||||
newExtension: string,
|
||||
): string {
|
||||
@@ -52,6 +52,16 @@ export function getIMGPathFromExcalidrawFile(
|
||||
excalidrawPath.substring(0, excalidrawPath.lastIndexOf(replaceExtension)) +
|
||||
newExtension
|
||||
);
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Generates the image filename based on the excalidraw filename
|
||||
* @param path - path to the excalidraw file
|
||||
* @param extension - extension without the preceeding "."
|
||||
* @returns
|
||||
*/
|
||||
export function getIMGFilename(path: string, extension: string): string {
|
||||
return `${path.substring(0, path.lastIndexOf("."))}.${extension}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,7 +130,8 @@ export function getEmbedFilename(
|
||||
* Open or create a folderpath if it does not exist
|
||||
* @param folderpath
|
||||
*/
|
||||
export async function checkAndCreateFolder(vault: Vault, folderpath: string) {
|
||||
export async function checkAndCreateFolder(folderpath: string) {
|
||||
const vault = app.vault;
|
||||
folderpath = normalizePath(folderpath);
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/658
|
||||
//@ts-ignore
|
||||
@@ -134,6 +145,3 @@ export async function checkAndCreateFolder(vault: Vault, folderpath: string) {
|
||||
await vault.createFolder(folderpath);
|
||||
}
|
||||
|
||||
export function getIMGFilename(path: string, extension: string): string {
|
||||
return `${path.substring(0, path.lastIndexOf("."))}.${extension}`;
|
||||
}
|
||||
@@ -36,14 +36,22 @@ export const getNewOrAdjacentLeaf = (
|
||||
//@ts-ignore
|
||||
const leafId = leaf.id;
|
||||
const layout = app.workspace.getLayout();
|
||||
const getLeaves = (l:any)=> l.children
|
||||
.filter((c:any)=>c.type!=="leaf")
|
||||
.map((c:any)=>getLeaves(c))
|
||||
.flat()
|
||||
.concat(l.children.filter((c:any)=>c.type==="leaf").map((c:any)=>c.id))
|
||||
|
||||
const mainLeavesIds = getLeaves(layout.main);
|
||||
|
||||
const leafLoc =
|
||||
layout.main && layout.main.children.filter((x:any)=>x.type==="leaf" && x.id ===leafId).length > 0
|
||||
layout.main && mainLeavesIds.contains(leafId)
|
||||
? "main"
|
||||
: layout.floating && layout.floating.children.filter((x:any)=>x.type==="leaf" && x.id ===leafId).length > 0
|
||||
: layout.floating && getLeaves(layout.floating).contains(leafId)
|
||||
? "popout"
|
||||
: layout.left && layout.left.children.filter((x:any)=>x.type==="leaf" && x.id ===leafId).length > 0
|
||||
: layout.left && getLeaves(layout.left).contains(leafId)
|
||||
? "left"
|
||||
: layout.right && layout.right.children.filter((x:any)=>x.type==="leaf" && x.id ===leafId).length > 0
|
||||
: layout.right && getLeaves(layout.right).contains(leafId)
|
||||
? "right"
|
||||
: "hover";
|
||||
|
||||
@@ -53,10 +61,9 @@ export const getNewOrAdjacentLeaf = (
|
||||
return mainLeaf;
|
||||
}
|
||||
mainLeaf = null;
|
||||
app.workspace.getLayout().main.children
|
||||
.filter((child:any)=>child.type==="leaf")
|
||||
.forEach((listItem:any)=> {
|
||||
const l = app.workspace.getLeafById(listItem.id);
|
||||
mainLeavesIds
|
||||
.forEach((id:any)=> {
|
||||
const l = app.workspace.getLeafById(id);
|
||||
if(mainLeaf ||
|
||||
!l.view?.navigation ||
|
||||
leaf === l
|
||||
@@ -137,7 +144,7 @@ export const getAttachmentsFolderAndFilePath = async (
|
||||
if (!folder || folder === "/") {
|
||||
folder = "";
|
||||
}
|
||||
await checkAndCreateFolder(app.vault, folder);
|
||||
await checkAndCreateFolder(folder);
|
||||
return {
|
||||
folder,
|
||||
filepath: normalizePath(
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
FRONTMATTER_KEY_EXPORT_TRANSPARENT,
|
||||
FRONTMATTER_KEY_EXPORT_SVGPADDING,
|
||||
FRONTMATTER_KEY_EXPORT_PNGSCALE,
|
||||
FRONTMATTER_KEY_EXPORT_PADDING,
|
||||
} from "../Constants";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { ExcalidrawElement } from "@zsviczian/excalidraw/types/element/types";
|
||||
@@ -69,7 +70,7 @@ export const checkExcalidrawVersion = async (app: App) => {
|
||||
.filter((el: any) => el.version.match(/^\d+\.\d+\.\d+$/))
|
||||
.sort((el1: any, el2: any) => el2.published - el1.published)[0].version;
|
||||
|
||||
if (latestVersion > PLUGIN_VERSION) {
|
||||
if (isVersionNewerThanOther(latestVersion,PLUGIN_VERSION)) {
|
||||
new Notice(
|
||||
`A newer version of Excalidraw is available in Community Plugins.\n\nYou are using ${PLUGIN_VERSION}.\nThe latest is ${latestVersion}`,
|
||||
);
|
||||
@@ -287,6 +288,7 @@ export const getSVG = async (
|
||||
export const getPNG = async (
|
||||
scene: any,
|
||||
exportSettings: ExportSettings,
|
||||
padding: number,
|
||||
scale: number = 1,
|
||||
) => {
|
||||
try {
|
||||
@@ -300,6 +302,7 @@ export const getPNG = async (
|
||||
...scene.appState,
|
||||
},
|
||||
files: scene.files,
|
||||
exportPadding: padding,
|
||||
mimeType: "image/png",
|
||||
getDimensions: (width: number, height: number) => ({
|
||||
width: width * scale,
|
||||
@@ -395,6 +398,19 @@ export const scaleLoadedImage = (
|
||||
}
|
||||
};
|
||||
|
||||
export const setDocLeftHandedMode = (isLeftHanded: boolean, ownerDocument:Document) => {
|
||||
const newStylesheet = ownerDocument.createElement("style");
|
||||
newStylesheet.id = "excalidraw-left-handed";
|
||||
newStylesheet.textContent = `.excalidraw .App-bottom-bar{justify-content:flex-end;}`;
|
||||
const oldStylesheet = ownerDocument.getElementById(newStylesheet.id);
|
||||
if (oldStylesheet) {
|
||||
ownerDocument.head.removeChild(oldStylesheet);
|
||||
}
|
||||
if (isLeftHanded) {
|
||||
ownerDocument.head.appendChild(newStylesheet);
|
||||
}
|
||||
}
|
||||
|
||||
export const setLeftHandedMode = (isLeftHanded: boolean) => {
|
||||
const visitedDocs = new Set<Document>();
|
||||
app.workspace.iterateAllLeaves((leaf) => {
|
||||
@@ -402,17 +418,8 @@ export const setLeftHandedMode = (isLeftHanded: boolean) => {
|
||||
if(!ownerDocument) return;
|
||||
if(visitedDocs.has(ownerDocument)) return;
|
||||
visitedDocs.add(ownerDocument);
|
||||
const newStylesheet = ownerDocument.createElement("style");
|
||||
newStylesheet.id = "excalidraw-letf-handed";
|
||||
newStylesheet.textContent = `.excalidraw .App-bottom-bar{justify-content:flex-end;}`;
|
||||
const oldStylesheet = ownerDocument.getElementById(newStylesheet.id);
|
||||
if (oldStylesheet) {
|
||||
ownerDocument.head.removeChild(oldStylesheet);
|
||||
}
|
||||
if (isLeftHanded) {
|
||||
ownerDocument.head.appendChild(newStylesheet);
|
||||
}
|
||||
})
|
||||
setDocLeftHandedMode(isLeftHanded,ownerDocument);
|
||||
})
|
||||
};
|
||||
|
||||
export type LinkParts = {
|
||||
@@ -512,16 +519,25 @@ export const getWithBackground = (
|
||||
return plugin.settings.exportWithBackground;
|
||||
};
|
||||
|
||||
export const getSVGPadding = (
|
||||
export const getExportPadding = (
|
||||
plugin: ExcalidrawPlugin,
|
||||
file: TFile,
|
||||
): number => {
|
||||
if (file) {
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEY_EXPORT_SVGPADDING] != null
|
||||
) {
|
||||
if(!fileCache?.frontmatter) return plugin.settings.exportPaddingSVG;
|
||||
|
||||
if (fileCache.frontmatter[FRONTMATTER_KEY_EXPORT_PADDING] != null) {
|
||||
const val = parseInt(
|
||||
fileCache.frontmatter[FRONTMATTER_KEY_EXPORT_PADDING],
|
||||
);
|
||||
if (!isNaN(val)) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
//depricated. Retained for backward compatibility
|
||||
if (fileCache.frontmatter[FRONTMATTER_KEY_EXPORT_SVGPADDING] != null) {
|
||||
const val = parseInt(
|
||||
fileCache.frontmatter[FRONTMATTER_KEY_EXPORT_SVGPADDING],
|
||||
);
|
||||
@@ -529,6 +545,7 @@ export const getSVGPadding = (
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return plugin.settings.exportPaddingSVG;
|
||||
};
|
||||
@@ -551,12 +568,72 @@ export const getPNGScale = (plugin: ExcalidrawPlugin, file: TFile): number => {
|
||||
return plugin.settings.pngExportScale;
|
||||
};
|
||||
|
||||
export const isVersionNewerThanOther = (version: string, otherVersion: string): boolean => {
|
||||
const v = version.match(/(\d*)\.(\d*)\.(\d*)/);
|
||||
const o = otherVersion.match(/(\d*)\.(\d*)\.(\d*)/);
|
||||
|
||||
return Boolean(v && v.length === 4 && o && o.length === 4 &&
|
||||
!(isNaN(parseInt(v[1])) || isNaN(parseInt(v[2])) || isNaN(parseInt(v[3]))) &&
|
||||
!(isNaN(parseInt(o[1])) || isNaN(parseInt(o[2])) || isNaN(parseInt(o[3]))) &&
|
||||
(
|
||||
parseInt(v[1])>parseInt(o[1]) ||
|
||||
(parseInt(v[1]) >= parseInt(o[1]) && parseInt(v[2]) > parseInt(o[2])) ||
|
||||
(parseInt(v[1]) >= parseInt(o[1]) && parseInt(v[2]) >= parseInt(o[2]) && parseInt(v[3]) > parseInt(o[3]))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export const getEmbeddedFilenameParts = (fname:string):{
|
||||
filepath: string,
|
||||
hasBlockref: boolean,
|
||||
hasGroupref: boolean,
|
||||
hasArearef: boolean,
|
||||
blockref: string,
|
||||
hasSectionref: boolean,
|
||||
sectionref: string,
|
||||
linkpartReference: string,
|
||||
linkpartAlias: string
|
||||
} => {
|
||||
// 0 1 23 4 5 6 7 8 9
|
||||
const parts = fname?.match(/([^#\^]*)((#\^)(group=|area=)?([^\|]*)|(#)(group=|area=)?([^\^\|]*))(.*)/);
|
||||
if(!parts) {
|
||||
return {
|
||||
filepath: fname,
|
||||
hasBlockref: false,
|
||||
hasGroupref: false,
|
||||
hasArearef: false,
|
||||
blockref: "",
|
||||
hasSectionref: false,
|
||||
sectionref: "",
|
||||
linkpartReference: "",
|
||||
linkpartAlias: ""
|
||||
}
|
||||
}
|
||||
return {
|
||||
filepath: parts[1],
|
||||
hasBlockref: Boolean(parts[3]),
|
||||
hasGroupref: (parts[4]==="group=") || (parts[7]==="group="),
|
||||
hasArearef: (parts[4]==="area=") || (parts[7]==="area="),
|
||||
blockref: parts[5],
|
||||
hasSectionref: Boolean(parts[6]),
|
||||
sectionref: parts[8],
|
||||
linkpartReference: parts[2],
|
||||
linkpartAlias: parts[9]
|
||||
}
|
||||
}
|
||||
|
||||
export const errorlog = (data: {}) => {
|
||||
console.error({ plugin: "Excalidraw", ...data });
|
||||
};
|
||||
|
||||
export const sleep = async (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
/**REACT 18
|
||||
//see also: https://github.com/zsviczian/obsidian-excalidraw-plugin/commit/b67d70c5196f30e2968f9da919d106ee66f2a5eb
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/commit/cc9d7828c7ee7755c1ef942519c43df32eae249f
|
||||
export const awaitNextAnimationFrame = async () => new Promise(requestAnimationFrame);
|
||||
*/
|
||||
|
||||
export const log = console.log.bind(window.console);
|
||||
export const debug = console.log.bind(window.console);
|
||||
//export const debug = function(){};
|
||||
|
||||
29
styles.css
29
styles.css
@@ -127,17 +127,22 @@ li[data-testid] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal-content.excalidraw-scriptengine-install {
|
||||
.excalidraw-scriptengine-install .modal-content {
|
||||
max-width: 130ch;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.excalidraw-scriptengine-install .modal {
|
||||
max-height:90%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.excalidraw-prompt-center {
|
||||
text-align: center;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.excalidraw-prompt-center button {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.excalidraw-prompt-center.filepath {
|
||||
@@ -172,15 +177,16 @@ li[data-testid] {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.excalidraw-release {
|
||||
.excalidraw-release .modal-content{
|
||||
padding-right: 5px;
|
||||
margin-right: -5px;
|
||||
max-width: 130ch;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.excalidraw-release .modal {
|
||||
max-height:90%;
|
||||
max-height: 90%;
|
||||
width: auto;
|
||||
max-width: 130ch;
|
||||
}
|
||||
|
||||
.excalidraw .Island .scrollbar {
|
||||
@@ -204,4 +210,17 @@ li[data-testid] {
|
||||
|
||||
.excalidraw-release p>a>img {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.excalidraw .context-menu-option {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
textarea.excalidraw-wysiwyg {
|
||||
border: none;
|
||||
outline: none;
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"1.7.9": "0.15.7",
|
||||
"1.7.13": "0.15.6",
|
||||
"1.7.8": "0.15.5",
|
||||
"1.7.7": "0.15.4",
|
||||
"1.7.6": "0.15.3",
|
||||
|
||||
54
yarn.lock
54
yarn.lock
@@ -1861,14 +1861,14 @@
|
||||
"resolved" "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz"
|
||||
"version" "1.2.4"
|
||||
|
||||
"@types/react-dom@^18.0.6":
|
||||
"integrity" "sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA=="
|
||||
"resolved" "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz"
|
||||
"version" "18.0.6"
|
||||
"@types/react-dom@^17.0.2":
|
||||
"integrity" "sha512-VjnqEmqGnasQKV0CWLevqMTXBYG9GbwuE6x3VetERLh0cq2LTptFE73MrQi2S7GkKXCf2GgwItB/melLnxfnsg=="
|
||||
"resolved" "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.17.tgz"
|
||||
"version" "17.0.17"
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
"@types/react" "^17"
|
||||
|
||||
"@types/react@*":
|
||||
"@types/react@^17":
|
||||
"integrity" "sha512-Xt40xQsrkdvjn1EyWe1Bc0dJLcil/9x2vAuW7ya+PuQip4UYUaXyhzWmAbwRsdMgwOFHpfp7/FFZebDU6Y8VHA=="
|
||||
"resolved" "https://registry.npmjs.org/@types/react/-/react-17.0.2.tgz"
|
||||
"version" "17.0.2"
|
||||
@@ -2216,10 +2216,10 @@
|
||||
dependencies:
|
||||
"@zerollup/ts-helpers" "^1.7.18"
|
||||
|
||||
"@zsviczian/excalidraw@0.12.0-obsidian-0":
|
||||
"integrity" "sha512-YPHQqLf2FCwQJvaatx4qE5ACFKHQEUlvIYZrOC5pmmz0govEm9oPtPgfz9PY6oPWRRDwLAngFLgO6/kRw9fODw=="
|
||||
"resolved" "https://registry.npmjs.org/@zsviczian/excalidraw/-/excalidraw-0.12.0-obsidian-0.tgz"
|
||||
"version" "0.12.0-obsidian-0"
|
||||
"@zsviczian/excalidraw@0.12.0-obsidian-8":
|
||||
"integrity" "sha512-pftFJjZD0oFzwG/qc63f63fXP7ZPhQJZlr1A1+woWLhf426D3Z50H2K3EhrteIl1i8uy5kIDZ68K/rNZg+zbOg=="
|
||||
"resolved" "https://registry.npmjs.org/@zsviczian/excalidraw/-/excalidraw-0.12.0-obsidian-8.tgz"
|
||||
"version" "0.12.0-obsidian-8"
|
||||
|
||||
"abab@^2.0.3", "abab@^2.0.5":
|
||||
"integrity" "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q=="
|
||||
@@ -3063,6 +3063,11 @@
|
||||
"resolved" "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz"
|
||||
"version" "2.0.16"
|
||||
|
||||
"colormaster@1.2.1":
|
||||
"integrity" "sha512-+G9g1fOW+TC4+y5IWYI4f7mb+l+oEto/N3qJTFrJNaayFpidZUWhZJGxoSJjhNMIiTttcU1kf4XSyfxHyHkQYw=="
|
||||
"resolved" "https://registry.npmjs.org/colormaster/-/colormaster-1.2.1.tgz"
|
||||
"version" "1.2.1"
|
||||
|
||||
"combined-stream@^1.0.8":
|
||||
"integrity" "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="
|
||||
"resolved" "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz"
|
||||
@@ -7430,13 +7435,14 @@
|
||||
"strip-ansi" "^6.0.1"
|
||||
"text-table" "^0.2.0"
|
||||
|
||||
"react-dom@^17.0.2 || ^18.2.0", "react-dom@^18.2.0":
|
||||
"integrity" "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="
|
||||
"resolved" "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz"
|
||||
"version" "18.2.0"
|
||||
"react-dom@^17.0.2", "react-dom@^17.0.2 || ^18.2.0":
|
||||
"integrity" "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA=="
|
||||
"resolved" "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz"
|
||||
"version" "17.0.2"
|
||||
dependencies:
|
||||
"loose-envify" "^1.1.0"
|
||||
"scheduler" "^0.23.0"
|
||||
"object-assign" "^4.1.1"
|
||||
"scheduler" "^0.20.2"
|
||||
|
||||
"react-error-overlay@^6.0.11":
|
||||
"integrity" "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg=="
|
||||
@@ -7513,12 +7519,13 @@
|
||||
optionalDependencies:
|
||||
"fsevents" "^2.3.2"
|
||||
|
||||
"react@^17.0.2 || ^18.2.0", "react@^18.2.0", "react@>= 16":
|
||||
"integrity" "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="
|
||||
"resolved" "https://registry.npmjs.org/react/-/react-18.2.0.tgz"
|
||||
"version" "18.2.0"
|
||||
"react@^17.0.2", "react@^17.0.2 || ^18.2.0", "react@>= 16", "react@17.0.2":
|
||||
"integrity" "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA=="
|
||||
"resolved" "https://registry.npmjs.org/react/-/react-17.0.2.tgz"
|
||||
"version" "17.0.2"
|
||||
dependencies:
|
||||
"loose-envify" "^1.1.0"
|
||||
"object-assign" "^4.1.1"
|
||||
|
||||
"readable-stream@^2.0.1":
|
||||
"integrity" "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw=="
|
||||
@@ -7853,12 +7860,13 @@
|
||||
dependencies:
|
||||
"xmlchars" "^2.2.0"
|
||||
|
||||
"scheduler@^0.23.0":
|
||||
"integrity" "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw=="
|
||||
"resolved" "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz"
|
||||
"version" "0.23.0"
|
||||
"scheduler@^0.20.2":
|
||||
"integrity" "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ=="
|
||||
"resolved" "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz"
|
||||
"version" "0.20.2"
|
||||
dependencies:
|
||||
"loose-envify" "^1.1.0"
|
||||
"object-assign" "^4.1.1"
|
||||
|
||||
"schema-utils@^2.6.5":
|
||||
"integrity" "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg=="
|
||||
|
||||
Reference in New Issue
Block a user