mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f45dad610 | ||
|
|
0e1ee0dde2 | ||
|
|
6c7b63cbdf | ||
|
|
bca7010394 | ||
|
|
3565a5bf94 | ||
|
|
f089911e02 | ||
|
|
992af2b5ca | ||
|
|
f85fc124d9 | ||
|
|
961e75a12d | ||
|
|
3be592eeb8 | ||
|
|
5f094f3d95 | ||
|
|
a16fd53958 | ||
|
|
72d425715f | ||
|
|
4a6aed5dd0 | ||
|
|
f76964a7b7 | ||
|
|
161f7041ec | ||
|
|
334e8130eb | ||
|
|
fdb7e97b11 | ||
|
|
22b3769c20 | ||
|
|
cd96870f07 | ||
|
|
a76854d152 | ||
|
|
32319b2f6c | ||
|
|
38add10053 | ||
|
|
21dc29d1d1 |
14
README.md
14
README.md
@@ -17,9 +17,7 @@ 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
|
||||
@@ -28,12 +26,12 @@ Please upgrade to Obsidian v0.12.19 or higher to get the latest release.
|
||||
- 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.
|
||||
- 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 +57,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
|
||||
|
||||
@@ -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" : {
|
||||
|
||||
@@ -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|
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "1.7.11",
|
||||
"minAppVersion": "0.15.7",
|
||||
"version": "1.7.16",
|
||||
"minAppVersion": "0.15.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
"authorUrl": "https://zsolt.blog",
|
||||
|
||||
10
package.json
10
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,12 +18,12 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/lz-string": "^1.3.34",
|
||||
"@zsviczian/excalidraw": "0.12.0-obsidian-4",
|
||||
"@zsviczian/excalidraw": "0.12.0-obsidian-7",
|
||||
"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"
|
||||
},
|
||||
@@ -41,7 +41,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",
|
||||
|
||||
@@ -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 (
|
||||
!(
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
//debug,
|
||||
embedFontsInSVG,
|
||||
errorlog,
|
||||
getEmbeddedFilenameParts,
|
||||
getPNG,
|
||||
getSVG,
|
||||
isVersionNewerThanOther,
|
||||
@@ -1140,7 +1141,7 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
*/
|
||||
setView(view: ExcalidrawView | "first" | "active"): ExcalidrawView {
|
||||
if (view == "active") {
|
||||
const v = this.plugin.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
const v = app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (!(v instanceof ExcalidrawView)) {
|
||||
return;
|
||||
}
|
||||
@@ -1148,7 +1149,7 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
}
|
||||
if (view == "first") {
|
||||
const leaves =
|
||||
this.plugin.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
if (!leaves || leaves.length == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -1282,7 +1283,7 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
* @returns
|
||||
*/
|
||||
viewToggleFullScreen(forceViewMode: boolean = false): void {
|
||||
if (this.plugin.app.isMobile) {
|
||||
if (app.isMobile) {
|
||||
errorMessage("mobile not supported", "viewToggleFullScreen()");
|
||||
return;
|
||||
}
|
||||
@@ -1535,6 +1536,42 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
return largestElement;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
const groupId = elements.map(el=>el.groupIds).reduce((prev,cur)=>cur.filter(v=>prev.includes(v)));
|
||||
return groupId.length > 0 ? groupId[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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[] {
|
||||
if(!element || !elements) return [];
|
||||
const container = (element.type === "text" && element.containerId)
|
||||
? elements.filter(el=>el.id === element.containerId)
|
||||
: [];
|
||||
if(element.groupIds.length === 0) {
|
||||
if(container.length === 1) return [element,container[0]];
|
||||
return [element];
|
||||
}
|
||||
|
||||
if(container.length === 1) {
|
||||
return elements.filter(el=>
|
||||
el.groupIds.some(id=>element.groupIds.includes(id)) ||
|
||||
el === container[0]
|
||||
);
|
||||
}
|
||||
|
||||
return elements.filter(el=>el.groupIds.some(id=>element.groupIds.includes(id)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param element
|
||||
* @param a
|
||||
@@ -1595,7 +1632,7 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
return null;
|
||||
}
|
||||
const leaf = getNewOrAdjacentLeaf(this.plugin, this.targetView.leaf);
|
||||
leaf.openFile(file, {active: false});
|
||||
leaf.openFile(file, {active: true});
|
||||
return leaf;
|
||||
};
|
||||
|
||||
@@ -1925,7 +1962,8 @@ async function getTemplate(
|
||||
}> {
|
||||
const app = plugin.app;
|
||||
const vault = app.vault;
|
||||
const templatePath = normalizePath(fileWithPath);
|
||||
const filenameParts = getEmbeddedFilenameParts(fileWithPath);
|
||||
const templatePath = normalizePath(filenameParts.filepath);
|
||||
const file = app.metadataCache.getFirstLinkpathDest(templatePath, "");
|
||||
let hasSVGwithBitmap = false;
|
||||
if (file && file instanceof TFile) {
|
||||
@@ -1982,8 +2020,18 @@ async function getTemplate(
|
||||
}, depth);
|
||||
}
|
||||
|
||||
let groupElements:ExcalidrawElement[] = scene.elements;
|
||||
if(filenameParts.hasGroupref) {
|
||||
const el = filenameParts.hasSectionref
|
||||
? getTextElementsMatchingQuery(scene.elements,["# "+filenameParts.sectionref],true)
|
||||
: scene.elements.filter((el: ExcalidrawElement)=>el.id===filenameParts.blockref);
|
||||
if(el.length > 0) {
|
||||
groupElements = plugin.ea.getElementsInTheSameGroupWithElement(el[0],scene.elements)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
elements: scene.elements,
|
||||
elements: groupElements,
|
||||
appState: scene.appState,
|
||||
frontmatter: data.substring(0, trimLocation),
|
||||
files: scene.files,
|
||||
@@ -2062,6 +2110,7 @@ export async function createSVG(
|
||||
: null;
|
||||
let elements = template?.elements ?? [];
|
||||
elements = elements.concat(automateElements);
|
||||
padding = padding ?? plugin.settings.exportPaddingSVG;
|
||||
const svg = await getSVG(
|
||||
{
|
||||
//createAndOpenDrawing
|
||||
@@ -2081,8 +2130,29 @@ export async function createSVG(
|
||||
exportSettings?.withBackground ?? plugin.settings.exportWithBackground,
|
||||
withTheme: exportSettings?.withTheme ?? plugin.settings.exportWithTheme,
|
||||
},
|
||||
padding ?? plugin.settings.exportPaddingSVG,
|
||||
padding,
|
||||
);
|
||||
const filenameParts = getEmbeddedFilenameParts(templatePath);
|
||||
if(
|
||||
!filenameParts.hasGroupref &&
|
||||
(filenameParts.hasBlockref || filenameParts.hasSectionref)
|
||||
) {
|
||||
let el = filenameParts.hasSectionref
|
||||
? getTextElementsMatchingQuery(elements,["# "+filenameParts.sectionref],true)
|
||||
: elements.filter((el: ExcalidrawElement)=>el.id===filenameParts.blockref);
|
||||
if(el.length>0) {
|
||||
const containerId = el[0].containerId;
|
||||
if(containerId) {
|
||||
el = el.concat(elements.filter((el: ExcalidrawElement)=>el.id === containerId));
|
||||
}
|
||||
const elBB = plugin.ea.getBoundingBox(el);
|
||||
const drawingBB = plugin.ea.getBoundingBox(elements);
|
||||
svg.viewBox.baseVal.x = elBB.topX - drawingBB.topX;
|
||||
svg.viewBox.baseVal.y = elBB.topY - drawingBB.topY;
|
||||
svg.viewBox.baseVal.width = elBB.width + 2*padding;
|
||||
svg.viewBox.baseVal.height = elBB.height + 2*padding;
|
||||
}
|
||||
}
|
||||
if (template?.hasSVGwithBitmap) {
|
||||
svg.setAttribute("hasbitmap", "true");
|
||||
}
|
||||
@@ -2210,3 +2280,35 @@ export const search = async (view: ExcalidrawView) => {
|
||||
|
||||
ea.targetView.selectElementsMatchingQuery(elements, query);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param elements
|
||||
* @param query
|
||||
* @param exactMatch - when searching for section header exactMatch should be set to true
|
||||
* @returns the elements matching the query
|
||||
*/
|
||||
export const getTextElementsMatchingQuery = (
|
||||
elements: ExcalidrawElement[],
|
||||
query: string[],
|
||||
exactMatch: boolean = false, //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/530
|
||||
): ExcalidrawElement[] => {
|
||||
if (!elements || elements.length === 0 || !query || query.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return elements.filter((el: any) =>
|
||||
el.type === "text" &&
|
||||
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" https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/530
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1320,6 +1331,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 } from "./ExcalidrawAutomate";
|
||||
import { t } from "./lang/helpers";
|
||||
import {
|
||||
ExcalidrawData,
|
||||
REG_LINKINDEX_HYPERLINK,
|
||||
REGEX_LINK,
|
||||
AutoexportPreference,
|
||||
} from "./ExcalidrawData";
|
||||
import {
|
||||
checkAndCreateFolder,
|
||||
@@ -60,6 +62,7 @@ import {
|
||||
debug,
|
||||
embedFontsInSVG,
|
||||
errorlog,
|
||||
getEmbeddedFilenameParts,
|
||||
getExportTheme,
|
||||
getLinkParts,
|
||||
getPNG,
|
||||
@@ -68,7 +71,9 @@ import {
|
||||
getSVGPadding,
|
||||
getWithBackground,
|
||||
hasExportTheme,
|
||||
isVersionNewerThanOther,
|
||||
scaleLoadedImage,
|
||||
setDocLeftHandedMode,
|
||||
svgToBase64,
|
||||
viewportCoordsToSceneCoords,
|
||||
} from "./utils/Utils";
|
||||
@@ -87,6 +92,9 @@ import { ObsidianMenu } from "./menu/ObsidianMenu";
|
||||
import { ToolsPanel } from "./menu/ToolsPanel";
|
||||
import { ScriptEngine } from "./Scripts";
|
||||
import { getTextElementAtPointer, getImageElementAtPointer, getElementWithLinkAtPointer } from "./utils/GetElementAtPointer";
|
||||
import { execArgv } from "process";
|
||||
import { findLastIndex } from "@zsviczian/excalidraw/types/utils";
|
||||
import { fileOpen } from "@zsviczian/excalidraw/types/data/filesystem";
|
||||
|
||||
|
||||
export enum TextMode {
|
||||
@@ -129,7 +137,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 +235,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 +248,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
saving: false,
|
||||
forceSaving: false,
|
||||
hoverSleep: false,
|
||||
wheelTimeout: null,
|
||||
};
|
||||
|
||||
public plugin: ExcalidrawPlugin;
|
||||
@@ -273,7 +283,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 +323,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,
|
||||
@@ -487,10 +497,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 +953,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 +965,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();
|
||||
@@ -1015,6 +1048,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
});
|
||||
|
||||
this.setupAutosaveTimer();
|
||||
super.onload();
|
||||
}
|
||||
|
||||
//this is to solve sliding panes bug
|
||||
@@ -1203,6 +1237,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 +1304,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 +1348,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);
|
||||
}
|
||||
@@ -1595,7 +1635,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 +1675,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 +1747,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 +1766,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 +1833,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 +1931,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);
|
||||
}
|
||||
|
||||
@@ -2231,12 +2303,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({
|
||||
@@ -2337,7 +2414,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,
|
||||
@@ -2421,6 +2498,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 = "";
|
||||
@@ -2584,7 +2663,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
mouseEvent = e.nativeEvent;
|
||||
},
|
||||
onMouseOver: () => {
|
||||
clearHoverPreview();
|
||||
this.clearHoverPreview();
|
||||
},
|
||||
onDragOver: (e: any) => {
|
||||
const action = dropAction(e.dataTransfer);
|
||||
@@ -2620,7 +2699,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;
|
||||
@@ -2933,6 +3012,14 @@ export default class ExcalidrawView extends TextFileView {
|
||||
originalText: string,
|
||||
isDeleted: boolean,
|
||||
): [string, string, string] => {
|
||||
const FORBIDDEN_TEXT = `{"type":"excalidraw/clipboard","elements":[{"`;
|
||||
if(text.startsWith(FORBIDDEN_TEXT)) {
|
||||
return [
|
||||
"PASTING EXCALIDRAW ELEMENTS AS A TEXT ELEMENT IS NOT ALLOWED",
|
||||
"PASTING EXCALIDRAW ELEMENTS AS A TEXT ELEMENT IS NOT ALLOWED",
|
||||
null
|
||||
];
|
||||
}
|
||||
const api = this.excalidrawAPI;
|
||||
if (!api) {
|
||||
return [null, null, null];
|
||||
@@ -3100,7 +3187,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) {
|
||||
@@ -3161,6 +3248,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
|
||||
@@ -3172,7 +3260,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}),
|
||||
);
|
||||
React.useEffect(() => {
|
||||
if (toolsPanelRef.current) {
|
||||
if (toolsPanelRef?.current) {
|
||||
observer.current.observe(toolsPanelRef.current.containerRef.current);
|
||||
}
|
||||
return () => {
|
||||
@@ -3184,9 +3272,11 @@ 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, () => {});
|
||||
*/
|
||||
ReactDOM.render(reactElement, this.contentEl, () => {});
|
||||
}
|
||||
|
||||
private updateContainerSize(containerId?: string, delay: boolean = false) {
|
||||
@@ -3260,44 +3350,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) {
|
||||
@@ -3333,12 +3438,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",
|
||||
@@ -3346,12 +3470,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;
|
||||
@@ -3359,6 +3483,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
commitToHistory?: boolean;
|
||||
},
|
||||
restore: boolean = false,
|
||||
awaitFrame: boolean = false,
|
||||
) {
|
||||
const api = this.excalidrawAPI;
|
||||
if (!api) {
|
||||
@@ -3396,6 +3521,9 @@ export default class ExcalidrawView extends TextFileView {
|
||||
warningUnknowSeriousError();
|
||||
}
|
||||
}
|
||||
/**REACT 18
|
||||
if(awaitFrame) await awaitNextAnimationFrame(); //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/747
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import ExcalidrawPlugin from "./main";
|
||||
import {getIMGFilename,} from "./utils/FileUtils";
|
||||
import {
|
||||
embedFontsInSVG,
|
||||
getEmbeddedFilenameParts,
|
||||
getExportTheme,
|
||||
getQuickImagePreview,
|
||||
getSVGPadding,
|
||||
@@ -20,7 +21,6 @@ import {
|
||||
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,
|
||||
@@ -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(
|
||||
|
||||
@@ -38,6 +38,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,6 +17,77 @@ 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.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.
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -16,8 +16,8 @@ export class ReleaseNotes extends Modal {
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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;",
|
||||
@@ -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",
|
||||
@@ -41,7 +42,13 @@ export default {
|
||||
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",
|
||||
@@ -336,7 +343,9 @@ 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",
|
||||
COMPATIBILITY_HEAD: "Compatibility features",
|
||||
|
||||
185
src/main.ts
185
src/main.ts
@@ -100,6 +100,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" {
|
||||
@@ -311,10 +312,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) {
|
||||
@@ -347,7 +348,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)
|
||||
@@ -454,7 +455,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;
|
||||
@@ -834,7 +835,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({
|
||||
@@ -974,6 +975,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"),
|
||||
@@ -1029,20 +1044,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"),
|
||||
@@ -1343,7 +1420,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
|
||||
@@ -1364,43 +1441,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;
|
||||
@@ -1424,19 +1464,19 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
[".svg", ".png", ".excalidraw"].forEach(async (ext: string) => {
|
||||
const oldIMGpath = getIMGPathFromExcalidrawFile(oldPath, ext);
|
||||
const imgFile = self.app.vault.getAbstractFileByPath(
|
||||
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);
|
||||
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 (
|
||||
@@ -1469,7 +1509,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) => {
|
||||
@@ -1484,7 +1524,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({
|
||||
@@ -1499,27 +1539,27 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
setTimeout(() => {
|
||||
[".svg", ".png", ".excalidraw"].forEach(async (ext: string) => {
|
||||
const imgPath = getIMGPathFromExcalidrawFile(file.path, ext);
|
||||
const imgFile = self.app.vault.getAbstractFileByPath(
|
||||
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) => {
|
||||
@@ -1588,7 +1628,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
};
|
||||
self.registerEvent(
|
||||
self.app.workspace.on(
|
||||
app.workspace.on(
|
||||
"active-leaf-change",
|
||||
activeLeafChangeEventHandler,
|
||||
),
|
||||
@@ -1596,7 +1636,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;
|
||||
@@ -1605,7 +1645,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,
|
||||
);
|
||||
}
|
||||
@@ -1802,9 +1842,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -1849,7 +1889,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") {
|
||||
@@ -1863,10 +1905,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> {
|
||||
@@ -1945,7 +1994,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,
|
||||
@@ -1972,7 +2021,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;
|
||||
}
|
||||
|
||||
@@ -2024,7 +2073,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}
|
||||
|
||||
@@ -219,7 +219,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);
|
||||
@@ -443,7 +443,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();
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -120,7 +120,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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -395,6 +395,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 +415,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 = {
|
||||
@@ -566,12 +570,57 @@ export const isVersionNewerThanOther = (version: string, otherVersion: string):
|
||||
)
|
||||
}
|
||||
|
||||
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.11": "0.15.6",
|
||||
"1.7.13": "0.15.6",
|
||||
"1.7.8": "0.15.5",
|
||||
"1.7.7": "0.15.4",
|
||||
"1.7.6": "0.15.3",
|
||||
|
||||
49
yarn.lock
49
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-4":
|
||||
"integrity" "sha512-x16SkkNewR+Mbfn0mULjLDMfvxJ4iXBtwO2T1PWajNcn2QwHnByn4KTFUsuTD0PfQ/ZTgTLeLUztpAZ8cted1w=="
|
||||
"resolved" "https://registry.npmjs.org/@zsviczian/excalidraw/-/excalidraw-0.12.0-obsidian-4.tgz"
|
||||
"version" "0.12.0-obsidian-4"
|
||||
"@zsviczian/excalidraw@0.12.0-obsidian-7":
|
||||
"integrity" "sha512-U/2FvzZdzSJp5Aurs3wMYp4dSC5BZpa6Yv0L6pXlEfaMVuLMFPp+mrJBt+d7xhGHRREd/o/tjxHdt3vIjHmAXA=="
|
||||
"resolved" "https://registry.npmjs.org/@zsviczian/excalidraw/-/excalidraw-0.12.0-obsidian-7.tgz"
|
||||
"version" "0.12.0-obsidian-7"
|
||||
|
||||
"abab@^2.0.3", "abab@^2.0.5":
|
||||
"integrity" "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q=="
|
||||
@@ -7430,13 +7430,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 +7514,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 +7855,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