mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
12 Commits
2.1.6.1-be
...
2.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44a3b30e3b | ||
|
|
bb9389c7dd | ||
|
|
ba4bfe9de7 | ||
|
|
3a73b14ebb | ||
|
|
31f54db433 | ||
|
|
26812dd297 | ||
|
|
ae4f4b4f08 | ||
|
|
4ac0a4c565 | ||
|
|
69c9f824a0 | ||
|
|
6a2220c960 | ||
|
|
aa501c2843 | ||
|
|
efce44f0a7 |
@@ -3,7 +3,7 @@
|
||||
The Obsidian-Excalidraw plugin integrates [Excalidraw](https://excalidraw.com/), a feature rich sketching tool, into Obsidian. You can store and edit Excalidraw files in your vault, you can embed drawings into your documents, and you can link to documents and other drawings to/and from Excalidraw. For a showcase of Excalidraw features, please read my blog post [here](https://www.zsolt.blog/2021/03/showcasing-excalidraw.html) and/or watch the videos below.
|
||||
|
||||
## Video Walkthrough
|
||||
|
||||
<a href="https://youtu.be/P_Q6avJGoWI" target="_blank"><img src="https://github.com/zsviczian/obsidian-excalidraw-plugin/assets/14358394/da34bb33-7610-45e6-b36f-cb7a02a9141b" width="300"/></a>
|
||||
<a href="https://youtu.be/o0exK-xFP3k" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/156931370-aa4d88de-c4a8-46cc-aeb2-dc09aa0bea39.jpg" width="300"/></a>
|
||||
<a href="https://youtu.be/QKnQgSjJVuc" target="_blank"><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/thumbnail-getting-started.jpg" width="300"/></a>
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||

|
||||
|
||||
This script allows users to streamline their Obsidian-Excalidraw workflows by enabling the selection of elements based on similar properties. Users can precisely define which attributes such as stroke color, fill style, font family, and more, should match for selection. It's perfect for large canvases where manual selection would be cumbersome. Users can either run the script to find and select matching elements across the entire scene, or define a specific group of elements to apply the selection criteria within a defined timeframe. This script enhances control and efficiency in your Excalidraw experience.
|
||||
This script enables the selection of elements based on matching properties. Select the attributes (such as stroke color, fill style, font family, etc) that should match for selection. It's perfect for large scenes where manual selection of elements would be cumbersome. You can either run the script to select matching elements across the entire scene, or define a specific group of elements to apply the selection criteria to.
|
||||
|
||||
```js */
|
||||
|
||||
let config = window.ExcalidrawSelectConfig;
|
||||
config = config && (Date.now() - config.timestamp < 60000) ? config : null;
|
||||
config = Boolean(config) && (Date.now() - config.timestamp < 60000) ? config : null;
|
||||
|
||||
let elements = ea.getViewSelectedElements();
|
||||
if(!config && (elements.length !==1)) {
|
||||
@@ -27,7 +27,7 @@ const fragWithHTML = (html) => createFragment((frag) => (frag.createDiv().innerH
|
||||
// RUN
|
||||
//--------------------------
|
||||
const run = () => {
|
||||
selectedElements = ea.getViewElements().filter(el=>
|
||||
selectedElements = elements.filter(el=>
|
||||
((typeof config.angle === "undefined") || (el.angle === config.angle)) &&
|
||||
((typeof config.backgroundColor === "undefined") || (el.backgroundColor === config.backgroundColor)) &&
|
||||
((typeof config.fillStyle === "undefined") || (el.fillStyle === config.fillStyle)) &&
|
||||
@@ -61,7 +61,7 @@ const showInstructions = () => {
|
||||
instructionsModal.contentEl.createEl("p", {text: "Step 2: Select an action:"});
|
||||
instructionsModal.contentEl.createEl("ul", {}, el => {
|
||||
el.createEl("li", {text: "Click 'RUN' to find matching elements throughout the entire scene."});
|
||||
el.createEl("li", {text: "Click 'SELECT' to first choose a specific group of elements. Then run the 'Select Similar Elements' script once more on that group within 1 minute."});
|
||||
el.createEl("li", {text: "Click 'SELECT' to 1) first choose a specific group of elements in the scene, then 2) run the 'Select Similar Elements' once more within 1 minute to apply the filter criteria only to that group of elements."});
|
||||
});
|
||||
instructionsModal.contentEl.createEl("p", {text: "Note: If you choose 'SELECT', make sure to click the 'Select Similar Elements' script again within 1 minute to apply your selection criteria to the group of elements you chose."});
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@ The script will convert your drawing into a slideshow presentation.
|
||||
|
||||
```javascript
|
||||
*/
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.9.23")) {
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.1.7")) {
|
||||
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
|
||||
return;
|
||||
}
|
||||
@@ -190,7 +190,7 @@ let preventFullscreenExit = true;
|
||||
const gotoFullscreen = async () => {
|
||||
if(isFullscreen) return;
|
||||
preventFullscreenExit = true;
|
||||
if(app.isMobile) {
|
||||
if(ea.DEVICE.isMobile) {
|
||||
ea.viewToggleFullScreen();
|
||||
} else {
|
||||
await contentEl.webkitRequestFullscreen();
|
||||
@@ -206,8 +206,8 @@ const gotoFullscreen = async () => {
|
||||
const exitFullscreen = async () => {
|
||||
if(!isFullscreen) return;
|
||||
preventFullscreenExit = true;
|
||||
if(!app.isMobile && ownerDocument?.fullscreenElement) await ownerDocument.exitFullscreen();
|
||||
if(app.isMobile) ea.viewToggleFullScreen();
|
||||
if(!ea.DEVICE.isMobile && ownerDocument?.fullscreenElement) await ownerDocument.exitFullscreen();
|
||||
if(ea.DEVICE.isMobile) ea.viewToggleFullScreen();
|
||||
if(toggleFullscreenButton) toggleFullscreenButton.innerHTML = SVG_MAXIMIZE;
|
||||
await waitForExcalidrawResize();
|
||||
resetControlPanelElPosition();
|
||||
@@ -649,7 +649,7 @@ const initializeEventListners = () => {
|
||||
controlPanelEl.removeEventListener('mouseenter', onMouseEnter, false);
|
||||
controlPanelEl.removeEventListener('mouseleave', onMouseLeave, false);
|
||||
controlPanelEl.parentElement?.removeChild(controlPanelEl);
|
||||
if(!app.isMobile) {
|
||||
if(!ea.DEVICE.isMobile) {
|
||||
contentEl.removeEventListener('webkitfullscreenchange', fullscreenListener);
|
||||
contentEl.removeEventListener('fullscreenchange', fullscreenListener);
|
||||
}
|
||||
@@ -664,7 +664,7 @@ const initializeEventListners = () => {
|
||||
return true;
|
||||
};
|
||||
|
||||
if(!app.isMobile) {
|
||||
if(!ea.DEVICE.isMobile) {
|
||||
contentEl.addEventListener('webkitfullscreenchange', fullscreenListener);
|
||||
contentEl.addEventListener('fullscreenchange', fullscreenListener);
|
||||
}
|
||||
@@ -727,7 +727,7 @@ const exitPresentation = async (openForEdit = false) => {
|
||||
//Resets pointer offsets. Ugly solution.
|
||||
//During testing offsets were wrong after presentation, but don't know why.
|
||||
//This should solve it even if they are wrong.
|
||||
hostView.refresh();
|
||||
hostView.refreshCanvasOffset();
|
||||
excalidrawAPI.setActiveTool({type: "selection"});
|
||||
})
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -508,7 +508,7 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Select%20Similar%20Elements.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Select%20Similar%20Elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script allows you to streamline your Obsidian-Excalidraw workflows by enabling the selection of elements based on similar properties. you can precisely define which attributes such as stroke color, fill style, font family, and more, should match for selection. It's perfect for large canvases where manual selection would be cumbersome. You can either run the script to find and select matching elements across the entire scene, or define a specific group of elements to apply the selection criteria within a defined timeframe. This script enhances control and efficiency in your Excalidraw experience.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-select-similar-elements.png'></td></tr></table>
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Select%20Similar%20Elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script allows you to streamline your Obsidian-Excalidraw workflows by enabling the selection of elements based on similar properties. you can precisely define which attributes such as stroke color, fill style, font family, and more, should match for selection. It's perfect for large canvases where manual selection would be cumbersome. You can either run the script to find and select matching elements across the entire scene, or define a specific group of elements to apply the selection criteria within a defined timeframe.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-select-similar-elements.png'></td></tr></table>
|
||||
|
||||
## Set background color of unclosed line object by adding a shadow clone
|
||||
```excalidraw-script-install
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.0.1-beta-2",
|
||||
"version": "2.1.8.2-beta-1",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.1.6",
|
||||
"version": "2.2.0",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@zsviczian/excalidraw": "0.17.1-obsidian-20",
|
||||
"@zsviczian/excalidraw": "0.17.1-obsidian-22",
|
||||
"chroma-js": "^2.4.2",
|
||||
"clsx": "^2.0.0",
|
||||
"colormaster": "^1.2.1",
|
||||
|
||||
@@ -10,9 +10,10 @@ const o0 = Decoration.line({ attributes: {class: "ex-opacity-0"} });
|
||||
export const HideTextBetweenCommentsExtension = ViewPlugin.fromClass(
|
||||
class {
|
||||
view: EditorView;
|
||||
decorations: DecorationSet;
|
||||
decorations: DecorationSet;
|
||||
reExcalidrawData = /^%%(?:\r\n|\r|\n)# Excalidraw Data$/gm;
|
||||
reTextElements = /^%%(?:\r\n|\r|\n)# Text Elements$/gm;
|
||||
reDrawing = /^%%(?:\r\n|\r|\n)# Drawing$/gm;
|
||||
reDrawing = /^%%(?:\r\n|\r|\n)##? Drawing$/gm;
|
||||
linecount = 0;
|
||||
isExcalidraw = false;
|
||||
|
||||
@@ -32,11 +33,15 @@ export const HideTextBetweenCommentsExtension = ViewPlugin.fromClass(
|
||||
|
||||
const text = doc.toString();
|
||||
|
||||
let start = text.search(this.reTextElements);
|
||||
let start = text.search(this.reExcalidrawData);
|
||||
if(start == -1) {
|
||||
start = text.search(this.reTextElements);
|
||||
}
|
||||
if(start == -1) {
|
||||
start = text.search(this.reDrawing);
|
||||
if(start == -1) return Decoration.none;
|
||||
}
|
||||
if(start == -1) return Decoration.none;
|
||||
|
||||
const startLine = doc.lineAt(start).number;
|
||||
const endLine = doc.lines;
|
||||
let builder = new RangeSetBuilder<Decoration>()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//https://stackoverflow.com/questions/2068344/how-do-i-get-a-youtube-video-thumbnail-from-the-youtube-api
|
||||
//https://img.youtube.com/vi/uZz5MgzWXiM/maxresdefault.jpg
|
||||
|
||||
import { ExcalidrawElement, ExcalidrawImageElement, FileId } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ExcalidrawElement, FileId } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { BinaryFileData, DataURL } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { App, MarkdownRenderer, Notice, TFile } from "obsidian";
|
||||
import {
|
||||
@@ -23,7 +23,7 @@ import { ExportSettings } from "./ExcalidrawView";
|
||||
import { t } from "./lang/helpers";
|
||||
import { tex2dataURL } from "./LaTeX";
|
||||
import ExcalidrawPlugin from "./main";
|
||||
import { blobToBase64, getDataURLFromURL, getMimeType, getPDFDoc, getURLImageExtension, readLocalFileBinary } from "./utils/FileUtils";
|
||||
import { blobToBase64, getDataURLFromURL, getMimeType, getPDFDoc, getURLImageExtension, hasExcalidrawEmbeddedImagesTreeChanged, readLocalFileBinary } from "./utils/FileUtils";
|
||||
import {
|
||||
errorlog,
|
||||
getDataURL,
|
||||
@@ -42,6 +42,8 @@ import {
|
||||
import { ValueOf } from "./types";
|
||||
import { getMermaidImageElements, getMermaidText, shouldRenderMermaid } from "./utils/MermaidUtils";
|
||||
import { mermaidToExcalidraw } from "src/constants/constants";
|
||||
import { ImageKey, imageCache } from "./utils/ImageCache";
|
||||
import { PreviewImageType } from "./utils/UtilTypes";
|
||||
|
||||
//An ugly workaround for the following situation.
|
||||
//File A is a markdown file that has an embedded Excalidraw file B
|
||||
@@ -164,7 +166,7 @@ export class EmbeddedFile {
|
||||
constructor(plugin: ExcalidrawPlugin, hostPath: string, imgPath: string, colorMapJSON?: string) {
|
||||
this.plugin = plugin;
|
||||
this.resetImage(hostPath, imgPath);
|
||||
if(this.file && (this.plugin.ea.isExcalidrawFile(this.file) || this.file.extension.toLowerCase() === "svg")) {
|
||||
if(this.file && (this.plugin.isExcalidrawFile(this.file) || this.file.extension.toLowerCase() === "svg")) {
|
||||
try {
|
||||
this.colorMap = colorMapJSON ? JSON.parse(colorMapJSON.toLocaleLowerCase()) : null;
|
||||
} catch (error) {
|
||||
@@ -229,7 +231,7 @@ export class EmbeddedFile {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return this.mtime != this.file.stat.mtime;
|
||||
return this.mtime !== this.file.stat.mtime;
|
||||
}
|
||||
|
||||
public setImage(
|
||||
@@ -358,22 +360,46 @@ export class EmbeddedFilesLoader {
|
||||
withTheme: !!forceTheme,
|
||||
isMask,
|
||||
};
|
||||
const svg = replaceSVGColors(
|
||||
await createSVG(
|
||||
file?.path,
|
||||
true,
|
||||
exportSettings,
|
||||
this,
|
||||
forceTheme,
|
||||
null,
|
||||
null,
|
||||
elements,
|
||||
this.plugin,
|
||||
depth+1,
|
||||
getExportPadding(this.plugin, file),
|
||||
),
|
||||
inFile instanceof EmbeddedFile ? inFile.colorMap : null
|
||||
) as SVGSVGElement;
|
||||
|
||||
const shouldUseCache = file && imageCache.isReady();
|
||||
const cacheKey:ImageKey = {
|
||||
filepath: file.path,
|
||||
blockref: null,
|
||||
sectionref: null,
|
||||
isDark,
|
||||
previewImageType: PreviewImageType.SVG,
|
||||
scale: 1,
|
||||
isTransparent: !exportSettings.withBackground,
|
||||
hasBlockref: false,
|
||||
hasGroupref: false,
|
||||
hasTaskbone: false,
|
||||
hasArearef: false,
|
||||
hasFrameref: false,
|
||||
hasSectionref: false,
|
||||
linkpartReference: null,
|
||||
linkpartAlias: null,
|
||||
}
|
||||
|
||||
const maybeSVG = await imageCache.getImageFromCache(cacheKey);
|
||||
|
||||
const svg = (maybeSVG && (maybeSVG instanceof SVGSVGElement))
|
||||
? maybeSVG
|
||||
: replaceSVGColors(
|
||||
await createSVG(
|
||||
file?.path,
|
||||
true,
|
||||
exportSettings,
|
||||
this,
|
||||
forceTheme,
|
||||
null,
|
||||
null,
|
||||
elements,
|
||||
this.plugin,
|
||||
depth+1,
|
||||
getExportPadding(this.plugin, file),
|
||||
),
|
||||
inFile instanceof EmbeddedFile ? inFile.colorMap : null
|
||||
) as SVGSVGElement;
|
||||
|
||||
//https://stackoverflow.com/questions/51154171/remove-css-filter-on-child-elements
|
||||
const imageList = svg.querySelectorAll(
|
||||
@@ -382,7 +408,8 @@ export class EmbeddedFilesLoader {
|
||||
if (imageList.length > 0) {
|
||||
hasSVGwithBitmap = true;
|
||||
}
|
||||
if (hasSVGwithBitmap && isDark) {
|
||||
|
||||
if (hasSVGwithBitmap && isDark && !Boolean(maybeSVG)) {
|
||||
imageList.forEach((i) => {
|
||||
const id = i.parentElement?.id;
|
||||
svg.querySelectorAll(`use[href='#${id}']`).forEach((u) => {
|
||||
@@ -393,6 +420,9 @@ export class EmbeddedFilesLoader {
|
||||
if (!hasSVGwithBitmap && svg.getAttribute("hasbitmap")) {
|
||||
hasSVGwithBitmap = true;
|
||||
}
|
||||
if(shouldUseCache && !Boolean(maybeSVG)) {
|
||||
imageCache.addImageToCache(cacheKey,"", svg);
|
||||
}
|
||||
const dURL = svgToBase64(svg.outerHTML) as DataURL;
|
||||
return {dataURL: dURL as DataURL, hasSVGwithBitmap};
|
||||
};
|
||||
@@ -526,7 +556,8 @@ export class EmbeddedFilesLoader {
|
||||
public async loadSceneFiles(
|
||||
excalidrawData: ExcalidrawData,
|
||||
addFiles: (files: FileData[], isDark: boolean, final?: boolean) => void,
|
||||
depth:number
|
||||
depth:number,
|
||||
isThemeChange:boolean = false,
|
||||
) {
|
||||
|
||||
if(depth > 7) {
|
||||
@@ -563,7 +594,8 @@ export class EmbeddedFilesLoader {
|
||||
}
|
||||
//files.push(fileData);
|
||||
}
|
||||
} /*else if (embeddedFile.isSVGwithBitmap) {
|
||||
} else if (embeddedFile.isSVGwithBitmap && (depth !== 0 || isThemeChange)) {
|
||||
//this will reload the image in light/dark mode when switching themes
|
||||
const fileData = {
|
||||
mimeType: embeddedFile.mimeType,
|
||||
id: entry.value[0],
|
||||
@@ -580,7 +612,7 @@ export class EmbeddedFilesLoader {
|
||||
catch(e) {
|
||||
errorlog({ where: "EmbeddedFileLoader.loadSceneFiles", error: e });
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
let equation;
|
||||
@@ -785,7 +817,7 @@ export class EmbeddedFilesLoader {
|
||||
? fileCache.frontmatter[FRONTMATTER_KEYS["md-css"].name] ?? ""
|
||||
: "";
|
||||
let frontmatterCSSisAfile = false;
|
||||
if (style && style != "") {
|
||||
if (style && style !== "") {
|
||||
const f = plugin.app.metadataCache.getFirstLinkpathDest(style, file.path);
|
||||
if (f) {
|
||||
style = await plugin.app.vault.read(f);
|
||||
|
||||
@@ -35,12 +35,9 @@ import {
|
||||
REG_LINKINDEX_INVALIDCHARS,
|
||||
THEME_FILTER,
|
||||
mermaidToExcalidraw,
|
||||
MD_TEXTELEMENTS,
|
||||
MD_DRAWING,
|
||||
} from "src/constants/constants";
|
||||
import { blobToBase64, checkAndCreateFolder, getDrawingFilename, getListOfTemplateFiles, getNewUniqueFilepath, } from "src/utils/FileUtils";
|
||||
import { blobToBase64, checkAndCreateFolder, getDrawingFilename, getExcalidrawEmbeddedFilesFiletree, getListOfTemplateFiles, getNewUniqueFilepath, hasExcalidrawEmbeddedImagesTreeChanged, } from "src/utils/FileUtils";
|
||||
import {
|
||||
arrayToMap,
|
||||
//debug,
|
||||
embedFontsInSVG,
|
||||
errorlog,
|
||||
@@ -51,15 +48,15 @@ import {
|
||||
getSVG,
|
||||
isMaskFile,
|
||||
isVersionNewerThanOther,
|
||||
log,
|
||||
scaleLoadedImage,
|
||||
wrapTextAtCharLength,
|
||||
arrayToMap,
|
||||
} from "src/utils/Utils";
|
||||
import { getAttachmentsFolderAndFilePath, getLeaf, getNewOrAdjacentLeaf, isObsidianThemeDark, mergeMarkdownFiles, openLeaf } from "src/utils/ObsidianUtils";
|
||||
import { AppState, BinaryFileData, DataURL, ExcalidrawImperativeAPI, Point } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { EmbeddedFile, EmbeddedFilesLoader, FileData } from "src/EmbeddedFileLoader";
|
||||
import { tex2dataURL } from "src/LaTeX";
|
||||
import { GenericInputPrompt, NewFileActions, Prompt } from "src/dialogs/Prompt";
|
||||
import { GenericInputPrompt, NewFileActions } from "src/dialogs/Prompt";
|
||||
import { t } from "src/lang/helpers";
|
||||
import { ScriptEngine } from "src/Scripts";
|
||||
import { ConnectionPoint, DeviceType } from "src/types";
|
||||
@@ -81,7 +78,7 @@ import { TInput } from "colormaster/types";
|
||||
import {ConversionResult, svgToExcalidraw} from "src/svgToExcalidraw/parser"
|
||||
import { ROUNDNESS } from "src/constants/constants";
|
||||
import { ClipboardData } from "@zsviczian/excalidraw/types/excalidraw/clipboard";
|
||||
import { emulateKeysForLinkClick, KeyEvent, PaneTarget } from "src/utils/ModifierkeyHelper";
|
||||
import { emulateKeysForLinkClick, PaneTarget } from "src/utils/ModifierkeyHelper";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import PolyBool from "polybooljs";
|
||||
import { EmbeddableMDCustomProps } from "./dialogs/EmbeddableSettings";
|
||||
@@ -91,9 +88,8 @@ import {
|
||||
extractCodeBlocks as _extractCodeBlocks,
|
||||
} from "./utils/AIUtils";
|
||||
import { EXCALIDRAW_AUTOMATE_INFO, EXCALIDRAW_SCRIPTENGINE_INFO } from "./dialogs/SuggesterInfo";
|
||||
import { CropImage } from "./utils/CropImage";
|
||||
import { has } from "./svgToExcalidraw/attributes";
|
||||
import { getFrameBasedOnFrameNameOrId } from "./utils/ExcalidrawViewUtils";
|
||||
import { log } from "./utils/DebugHelper";
|
||||
|
||||
extendPlugins([
|
||||
HarmonyPlugin,
|
||||
@@ -134,7 +130,7 @@ export class ExcalidrawAutomate {
|
||||
|
||||
public help(target: Function | string) {
|
||||
if (!target) {
|
||||
console.log("Usage: ea.help(ea.functionName) or ea.help('propertyName') or ea.help('utils.functionName') - notice property name and utils function name is in quotes");
|
||||
log("Usage: ea.help(ea.functionName) or ea.help('propertyName') or ea.help('utils.functionName') - notice property name and utils function name is in quotes");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -153,14 +149,14 @@ export class ExcalidrawAutomate {
|
||||
}
|
||||
|
||||
if(!funcInfo) {
|
||||
console.log("Usage: ea.help(ea.functionName) or ea.help('propertyName') or ea.help('utils.functionName') - notice property name and utils function name is in quotes");
|
||||
log("Usage: ea.help(ea.functionName) or ea.help('propertyName') or ea.help('utils.functionName') - notice property name and utils function name is in quotes");
|
||||
return;
|
||||
}
|
||||
|
||||
let isMissing = true;
|
||||
if (funcInfo.code) {
|
||||
isMissing = false;
|
||||
console.log(`Declaration: ${funcInfo.code}`);
|
||||
log(`Declaration: ${funcInfo.code}`);
|
||||
}
|
||||
if (funcInfo.desc) {
|
||||
isMissing = false;
|
||||
@@ -171,10 +167,10 @@ export class ExcalidrawAutomate {
|
||||
.replace(/<a onclick='window\.open\("(.*?)"\)'>(.*?)<\/a>/g, (_, href, text) => `%c\u200b${text}%c\u200b (link: ${href})`); // Zero-width non-joiner
|
||||
|
||||
const styles = Array.from({ length: (formattedDesc.match(/%c/g) || []).length }, (_, i) => i % 2 === 0 ? 'color: #007bff;' : '');
|
||||
console.log(`Description: ${formattedDesc}`, ...styles);
|
||||
log(`Description: ${formattedDesc}`, ...styles);
|
||||
}
|
||||
if (isMissing) {
|
||||
console.log("Description not available for this function.");
|
||||
log("Description not available for this function.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,6 +248,22 @@ export class ExcalidrawAutomate {
|
||||
return getListOfTemplateFiles(this.plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retruns the embedded images in the scene recursively. If excalidrawFile is not provided,
|
||||
* the function will use ea.targetView.file
|
||||
* @param excalidrawFile
|
||||
* @returns TFile[] of all nested images and Excalidraw drawings recursively
|
||||
*/
|
||||
public getEmbeddedImagesFiletree(excalidrawFile?: TFile): TFile[] {
|
||||
if(!excalidrawFile && this.targetView && this.targetView.file) {
|
||||
excalidrawFile = this.targetView.file;
|
||||
}
|
||||
if(!excalidrawFile) {
|
||||
return [];
|
||||
}
|
||||
return getExcalidrawEmbeddedFilesFiletree(excalidrawFile, this.plugin);
|
||||
}
|
||||
|
||||
public async getAttachmentFilepath(filename: string): Promise<string> {
|
||||
if (!this.targetView || !this.targetView?.file) {
|
||||
errorMessage("targetView not set", "getAttachmentFolderAndFilePath()");
|
||||
@@ -706,7 +718,7 @@ export class ExcalidrawAutomate {
|
||||
|
||||
const generateMD = ():string => {
|
||||
const textElements = this.getElements().filter(el => el.type === "text") as ExcalidrawTextElement[];
|
||||
let outString = `${MD_TEXTELEMENTS}\n`;
|
||||
let outString = `# Excalidraw Data\n## Text Elements\n`;
|
||||
textElements.forEach(te=> {
|
||||
outString += `${te.rawText ?? (te.originalText ?? te.text)} ^${te.id}\n\n`;
|
||||
});
|
||||
@@ -717,7 +729,7 @@ export class ExcalidrawAutomate {
|
||||
})
|
||||
|
||||
outString += Object.keys(this.imagesDict).length > 0
|
||||
? "\n# Embedded files\n"
|
||||
? `\n## Embedded Files\n`
|
||||
: "";
|
||||
|
||||
Object.keys(this.imagesDict).forEach((key: FileId)=> {
|
||||
@@ -2629,7 +2641,7 @@ export class ExcalidrawAutomate {
|
||||
importSVG(svgString:string):boolean {
|
||||
const res:ConversionResult = svgToExcalidraw(svgString);
|
||||
if(res.hasErrors) {
|
||||
new Notice (`There were errors while parsing the given SVG:\n${[...res.errors].map((el) => el.innerHTML)}`);
|
||||
new Notice (`There were errors while parsing the given SVG:\n${res.errors}`);
|
||||
return false;
|
||||
}
|
||||
this.copyViewElementsToEAforEditing(res.content);
|
||||
@@ -2762,9 +2774,9 @@ async function getTemplate(
|
||||
textMode,
|
||||
);
|
||||
|
||||
let trimLocation = data.search(new RegExp(`^${MD_TEXTELEMENTS}$`,"m"));
|
||||
let trimLocation = data.search(/^##? Text Elements$/m);
|
||||
if (trimLocation == -1) {
|
||||
trimLocation = data.search(`${MD_DRAWING}\n`);
|
||||
trimLocation = data.search(/##? Drawing\n/);
|
||||
}
|
||||
|
||||
let scene = excalidrawData.scene;
|
||||
|
||||
@@ -18,9 +18,8 @@ import {
|
||||
ERROR_IFRAME_CONVERSION_CANCELED,
|
||||
JSON_parse,
|
||||
FRONTMATTER_KEYS,
|
||||
MD_TEXTELEMENTS,
|
||||
MD_DRAWING,
|
||||
MD_ELEMENTLINKS,
|
||||
refreshTextDimensions,
|
||||
getContainerElement,
|
||||
} from "./constants/constants";
|
||||
import { _measureText } from "./ExcalidrawAutomate";
|
||||
import ExcalidrawPlugin from "./main";
|
||||
@@ -28,11 +27,10 @@ import { TextMode } from "./ExcalidrawView";
|
||||
import {
|
||||
addAppendUpdateCustomData,
|
||||
compress,
|
||||
debug,
|
||||
decompress,
|
||||
//getBakPath,
|
||||
getBinaryFileFromDataURL,
|
||||
getContainerElement,
|
||||
_getContainerElement,
|
||||
getExportTheme,
|
||||
getLinkParts,
|
||||
hasExportTheme,
|
||||
@@ -40,18 +38,21 @@ import {
|
||||
LinkParts,
|
||||
updateFrontmatterInString,
|
||||
wrapTextAtCharLength,
|
||||
arrayToMap,
|
||||
} from "./utils/Utils";
|
||||
import { cleanBlockRef, cleanSectionHeading, getAttachmentsFolderAndFilePath, isObsidianThemeDark } from "./utils/ObsidianUtils";
|
||||
import {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawImageElement,
|
||||
ExcalidrawTextElement,
|
||||
FileId,
|
||||
} from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { BinaryFiles, DataURL, SceneData } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { EmbeddedFile, MimeType } from "./EmbeddedFileLoader";
|
||||
import { ConfirmationPrompt } from "./dialogs/Prompt";
|
||||
import { getMermaidImageElements, getMermaidText, shouldRenderMermaid } from "./utils/MermaidUtils";
|
||||
import { add } from "@zsviczian/excalidraw/types/excalidraw/ga";
|
||||
import { debug } from "./utils/DebugHelper";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
|
||||
type SceneDataWithFiles = SceneData & { files: BinaryFiles };
|
||||
|
||||
@@ -118,12 +119,12 @@ export const REGEX_LINK = {
|
||||
};
|
||||
|
||||
//added \n at and of DRAWING_REG: https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/357
|
||||
const DRAWING_REG = /\n# Drawing\n[^`]*(```json\n)([\s\S]*?)```\n/gm; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/182
|
||||
const DRAWING_REG_FALLBACK = /\n# Drawing\n(```json\n)?(.*)(```)?(%%)?/gm;
|
||||
const DRAWING_REG = /\n##? Drawing\n[^`]*(```json\n)([\s\S]*?)```\n/gm; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/182
|
||||
const DRAWING_REG_FALLBACK = /\n##? Drawing\n(```json\n)?(.*)(```)?(%%)?/gm;
|
||||
export const DRAWING_COMPRESSED_REG =
|
||||
/(\n# Drawing\n[^`]*(?:```compressed\-json\n))([\s\S]*?)(```\n)/gm; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/182
|
||||
/(\n##? Drawing\n[^`]*(?:```compressed\-json\n))([\s\S]*?)(```\n)/gm; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/182
|
||||
const DRAWING_COMPRESSED_REG_FALLBACK =
|
||||
/(\n# Drawing\n(?:```compressed\-json\n)?)(.*)((```)?(%%)?)/gm;
|
||||
/(\n##? Drawing\n(?:```compressed\-json\n)?)(.*)((```)?(%%)?)/gm;
|
||||
export const REG_LINKINDEX_HYPERLINK = /^\w+:\/\//;
|
||||
|
||||
const isCompressedMD = (data: string): boolean => {
|
||||
@@ -205,10 +206,10 @@ export function getMarkdownDrawingSection(
|
||||
compressed: boolean,
|
||||
) {
|
||||
return compressed
|
||||
? `# Drawing\n\x60\x60\x60compressed-json\n${compress(
|
||||
? `## Drawing\n\x60\x60\x60compressed-json\n${compress(
|
||||
jsonString,
|
||||
)}\n\x60\x60\x60\n%%`
|
||||
: `# Drawing\n\x60\x60\x60json\n${jsonString}\n\x60\x60\x60\n%%`;
|
||||
: `## Drawing\n\x60\x60\x60json\n${jsonString}\n\x60\x60\x60\n%%`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -242,33 +243,97 @@ const wrap = (text: string, lineLen: number) =>
|
||||
lineLen ? wrapTextAtCharLength(text, lineLen, false, 0) : text;
|
||||
|
||||
//WITHSECTION refers to back of the card note (see this.inputEl.onkeyup in SelectCard.ts)
|
||||
const RE_TEXTELEMENTS_WITHSECTION_OK = new RegExp(`^#\n%%\n${MD_TEXTELEMENTS}(?:\n|$)`, "m");
|
||||
const RE_TEXTELEMENTS_WITHSECTION_NOTOK = new RegExp(`#\n%%\n${MD_TEXTELEMENTS}(?:\n|$)`, "m");
|
||||
const RE_TEXTELEMENTS_NOSECTION_OK = new RegExp(`^(%%\n)?${MD_TEXTELEMENTS}(?:\n|$)`, "m");
|
||||
const RE_EXCALIDRAWDATA_WITHSECTION_OK = new RegExp(`^#\n%%\n# Excalidraw Data(?:\n|$)`, "m");
|
||||
const RE_EXCALIDRAWDATA_WITHSECTION_NOTOK = new RegExp(`#\n%%\n# Excalidraw Data(?:\n|$)`, "m");
|
||||
const RE_EXCALIDRAWDATA_NOSECTION_OK = new RegExp(`^(%%\n)?# Excalidraw Data(?:\n|$)`, "m");
|
||||
|
||||
//WITHSECTION refers to back of the card note (see this.inputEl.onkeyup in SelectCard.ts)
|
||||
const RE_TEXTELEMENTS_WITHSECTION_OK = new RegExp(`^#\n%%\n##? Text Elements(?:\n|$)`, "m");
|
||||
const RE_TEXTELEMENTS_WITHSECTION_NOTOK = new RegExp(`#\n%%\n##? Text Elements(?:\n|$)`, "m");
|
||||
const RE_TEXTELEMENTS_NOSECTION_OK = new RegExp(`^(%%\n)?##? Text Elements(?:\n|$)`, "m");
|
||||
|
||||
|
||||
//The issue is that when editing in markdown embeds the user can delete the last enter causing two sections
|
||||
//to collide. This is particularly problematic when the user is editing the lest section before # Text Elements
|
||||
const RE_TEXTELEMENTS_FALLBACK_1 = new RegExp(`(.*)%%\n${MD_TEXTELEMENTS}(?:\n|$)`, "m");
|
||||
const RE_TEXTELEMENTS_FALLBACK_2 = new RegExp(`(.*)${MD_TEXTELEMENTS}(?:\n|$)`, "m");
|
||||
//to collide. This is particularly problematic when the user is editing the last section before # Text Elements
|
||||
const RE_EXCALIDRAWDATA_FALLBACK_1 = new RegExp(`(.*)%%\n# Excalidraw Data(?:\n|$)`, "m");
|
||||
const RE_EXCALIDRAWDATA_FALLBACK_2 = new RegExp(`(.*)# Excalidraw Data(?:\n|$)`, "m");
|
||||
|
||||
const RE_TEXTELEMENTS_FALLBACK_1 = new RegExp(`(.*)%%\n##? Text Elements(?:\n|$)`, "m");
|
||||
const RE_TEXTELEMENTS_FALLBACK_2 = new RegExp(`(.*)##? Text Elements(?:\n|$)`, "m");
|
||||
|
||||
|
||||
const RE_DRAWING = new RegExp(`(%%\n)?${MD_DRAWING}\n`);
|
||||
const RE_DRAWING = new RegExp(`(%%\n)?##? Drawing\n`);
|
||||
|
||||
export const getExcalidrawMarkdownHeaderSection = (data:string, keys?:[string,string][]):string => {
|
||||
//The base case scenario is at the top, continued with fallbacks in order of likelihood and file structure
|
||||
//change history for sake of backward compatibility
|
||||
|
||||
/* Expected markdown structure:
|
||||
bla bla bla
|
||||
#
|
||||
%%
|
||||
# Excalidraw Data
|
||||
*/
|
||||
let trimLocation = data.search(RE_EXCALIDRAWDATA_WITHSECTION_OK);
|
||||
let shouldFixTrailingHashtag = false;
|
||||
if(trimLocation > 0) {
|
||||
trimLocation += 2; //accounts for the "#\n" which I want to leave there untouched
|
||||
}
|
||||
|
||||
/* Expected markdown structure (this happens when the user deletes the last empty line of the last back-of-the-card note):
|
||||
bla bla bla#
|
||||
%%
|
||||
# Excalidraw Data
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
trimLocation = data.search(RE_EXCALIDRAWDATA_WITHSECTION_NOTOK);
|
||||
if(trimLocation > 0) {
|
||||
shouldFixTrailingHashtag = true;
|
||||
}
|
||||
}
|
||||
/* Expected markdown structure
|
||||
a)
|
||||
bla bla bla
|
||||
%%
|
||||
# Excalidraw Data
|
||||
b)
|
||||
bla bla bla
|
||||
# Excalidraw Data
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
trimLocation = data.search(RE_EXCALIDRAWDATA_NOSECTION_OK);
|
||||
}
|
||||
/* Expected markdown structure:
|
||||
bla bla bla%%
|
||||
# Excalidraw Data
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
const res = data.match(RE_EXCALIDRAWDATA_FALLBACK_1);
|
||||
if(res && Boolean(res[1])) {
|
||||
trimLocation = res.index + res[1].length;
|
||||
}
|
||||
}
|
||||
/* Expected markdown structure:
|
||||
bla bla bla# Excalidraw Data
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
const res = data.match(RE_EXCALIDRAWDATA_FALLBACK_2);
|
||||
if(res && Boolean(res[1])) {
|
||||
trimLocation = res.index + res[1].length;
|
||||
}
|
||||
}
|
||||
/* Expected markdown structure:
|
||||
bla bla bla
|
||||
#
|
||||
%%
|
||||
# Text Elements
|
||||
*/
|
||||
let trimLocation = data.search(RE_TEXTELEMENTS_WITHSECTION_OK);
|
||||
let shouldFixTrailingHashtag = false;
|
||||
if(trimLocation > 0) {
|
||||
trimLocation += 2;
|
||||
if(trimLocation === -1) {
|
||||
trimLocation = data.search(RE_TEXTELEMENTS_WITHSECTION_OK);
|
||||
if(trimLocation > 0) {
|
||||
trimLocation += 2; //accounts for the "#\n" which I want to leave there untouched
|
||||
}
|
||||
}
|
||||
|
||||
/* Expected markdown structure:
|
||||
bla bla bla#
|
||||
%%
|
||||
@@ -343,7 +408,7 @@ export const getExcalidrawMarkdownHeaderSection = (data:string, keys?:[string,st
|
||||
export class ExcalidrawData {
|
||||
public textElements: Map<
|
||||
string,
|
||||
{ raw: string; parsed: string; wrapAt: number | null }
|
||||
{ raw: string; parsed: string}
|
||||
> = null;
|
||||
public elementLinks: Map<string, string> = null;
|
||||
public scene: any = null;
|
||||
@@ -544,10 +609,10 @@ export class ExcalidrawData {
|
||||
this.selectedElementIds = {};
|
||||
this.textElements = new Map<
|
||||
string,
|
||||
{ raw: string; parsed: string; wrapAt: number }
|
||||
{ raw: string; parsed: string}
|
||||
>();
|
||||
this.elementLinks = new Map<string, string>();
|
||||
if (this.file != file) {
|
||||
if (this.file !== file) {
|
||||
//this is a reload - files, equations and mermaids will take care of reloading when needed
|
||||
this.files.clear();
|
||||
this.equations.clear();
|
||||
@@ -642,7 +707,28 @@ export class ExcalidrawData {
|
||||
//link was updated due to filename changes
|
||||
//The .excalidraw JSON is modified to reflect the MD in case of difference
|
||||
//Read the text elements into the textElements Map
|
||||
let position = data.search(RE_TEXTELEMENTS_NOSECTION_OK);
|
||||
let position = data.search(RE_EXCALIDRAWDATA_NOSECTION_OK);
|
||||
if (position === -1) {
|
||||
//resillience in case back of the note was saved right on top of text elements
|
||||
// # back of note section
|
||||
// ....# Excalidraw Data
|
||||
// ....
|
||||
// --------------
|
||||
// instead of
|
||||
// --------------
|
||||
// # back of note section
|
||||
// ....
|
||||
// # Excalidraw Data
|
||||
position = data.search(RE_EXCALIDRAWDATA_FALLBACK_2);
|
||||
}
|
||||
|
||||
if(position === -1) {
|
||||
// # back of note section
|
||||
// ....
|
||||
// # Text Elements
|
||||
position = data.search(RE_TEXTELEMENTS_NOSECTION_OK);
|
||||
}
|
||||
|
||||
if (position === -1) {
|
||||
//resillience in case back of the note was saved right on top of text elements
|
||||
// # back of note section
|
||||
@@ -662,10 +748,12 @@ export class ExcalidrawData {
|
||||
return true; //Text Elements header does not exist
|
||||
}
|
||||
data = data.slice(position);
|
||||
const normalMatch = data.match(new RegExp(`^((%%\n)?${MD_TEXTELEMENTS}(?:\n|$))`, "m"));
|
||||
const normalMatch = data.match(/^((%%\n)?# Excalidraw Data\n## Text Elements(?:\n|$))/m)
|
||||
??data.match(/^((%%\n)?##? Text Elements(?:\n|$))/m);
|
||||
|
||||
const textElementsMatch = normalMatch
|
||||
? normalMatch[0]
|
||||
: data.match(new RegExp(`(.*${MD_TEXTELEMENTS}(?:\n|$))`, "m"))[0];
|
||||
: data.match(/(.*##? Text Elements(?:\n|$))/m)[0];
|
||||
|
||||
data = data.slice(textElementsMatch.length);
|
||||
this.textElementCommentedOut = textElementsMatch.startsWith("%%\n");
|
||||
@@ -674,9 +762,13 @@ export class ExcalidrawData {
|
||||
|
||||
//load element links
|
||||
const elementLinkMap = new Map<string,string>();
|
||||
const elementLinksData = data.substring(
|
||||
data.indexOf(`${MD_ELEMENTLINKS}\n`) + `${MD_ELEMENTLINKS}\n`.length,
|
||||
);
|
||||
const indexOfNewElementLinks = data.indexOf("## Element Links\n");
|
||||
const lengthOfNewElementLinks = 17; //`## Element Links\n`.length
|
||||
const indexOfOldElementLinks = data.indexOf("# Element Links\n");
|
||||
const lengthOfOldElementLinks = 16; //`# Element Links\n`.length
|
||||
const elementLinksData = indexOfNewElementLinks>-1
|
||||
? data.substring(indexOfNewElementLinks + lengthOfNewElementLinks)
|
||||
: data.substring(indexOfOldElementLinks + lengthOfOldElementLinks);
|
||||
//Load Embedded files
|
||||
const RE_ELEMENT_LINKS = /^(.{8}):\s*(\[\[[^\]]*]])$/gm;
|
||||
const linksRes = elementLinksData.matchAll(RE_ELEMENT_LINKS);
|
||||
@@ -686,7 +778,7 @@ export class ExcalidrawData {
|
||||
|
||||
//iterating through all the text elements in .md
|
||||
//Text elements always contain the raw value
|
||||
const BLOCKREF_LEN: number = " ^12345678\n\n".length;
|
||||
const BLOCKREF_LEN: number = 12; // " ^12345678\n\n".length;
|
||||
const RE_TEXT_ELEMENT_LINK = /^%%\*\*\*>>>text element-link:(\[\[[^<*\]]*]])<<<\*\*\*%%/gm;
|
||||
let res = data.matchAll(/\s\^(.{8})[\n]+/g);
|
||||
while (!(parts = res.next()).done) {
|
||||
@@ -704,9 +796,8 @@ export class ExcalidrawData {
|
||||
}
|
||||
this.elementLinks.set(id, text);
|
||||
} else {
|
||||
const wrapAt = estimateMaxLineLen(textEl.text, textEl.originalText);
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/566
|
||||
const elementLinkRes = text.matchAll(RE_TEXT_ELEMENT_LINK);
|
||||
const elementLinkRes = text.matchAll(RE_TEXT_ELEMENT_LINK);
|
||||
const elementLink = elementLinkRes.next();
|
||||
if(!elementLink.done) {
|
||||
text = text.replace(RE_TEXT_ELEMENT_LINK,"");
|
||||
@@ -721,7 +812,6 @@ export class ExcalidrawData {
|
||||
this.textElements.set(id, {
|
||||
raw: text,
|
||||
parsed: parseRes.parsed,
|
||||
wrapAt,
|
||||
});
|
||||
if (parseRes.link) {
|
||||
textEl.link = parseRes.link;
|
||||
@@ -747,11 +837,15 @@ export class ExcalidrawData {
|
||||
}
|
||||
}
|
||||
|
||||
const indexOfEmbeddedFiles = data.indexOf("# Embedded files\n");
|
||||
if(indexOfEmbeddedFiles>-1) {
|
||||
data = data.substring(
|
||||
indexOfEmbeddedFiles + "# Embedded files\n".length,
|
||||
);
|
||||
const indexOfNewEmbeddedFiles = data.indexOf("## Embedded Files\n");
|
||||
const embeddedFilesNewLength = 18; //"## Embedded Files\n".length
|
||||
const indexOfOldEmbeddedFiles = data.indexOf("# Embedded files\n");
|
||||
const embeddedFilesOldLength = 17; //"# Embedded files\n".length
|
||||
|
||||
if(indexOfNewEmbeddedFiles>-1 || indexOfOldEmbeddedFiles>-1) {
|
||||
data = indexOfNewEmbeddedFiles>-1
|
||||
? data.substring(indexOfNewEmbeddedFiles + embeddedFilesNewLength)
|
||||
: data.substring(indexOfOldEmbeddedFiles + embeddedFilesOldLength);
|
||||
//Load Embedded files
|
||||
const REG_FILEID_FILEPATH = /([\w\d]*):\s*\[\[([^\]]*)]]\s?(\{[^}]*})?\n/gm;
|
||||
res = data.matchAll(REG_FILEID_FILEPATH);
|
||||
@@ -816,7 +910,7 @@ export class ExcalidrawData {
|
||||
this.file = file;
|
||||
this.textElements = new Map<
|
||||
string,
|
||||
{ raw: string; parsed: string; wrapAt: number }
|
||||
{ raw: string; parsed: string}
|
||||
>();
|
||||
this.elementLinks = new Map<string, string>();
|
||||
this.setShowLinkBrackets();
|
||||
@@ -847,34 +941,6 @@ export class ExcalidrawData {
|
||||
await this.updateSceneTextElements(forceupdate);
|
||||
}
|
||||
|
||||
//update a single text element in the scene if the newText is different
|
||||
public updateTextElement(
|
||||
sceneTextElement: any,
|
||||
newText: string,
|
||||
newOriginalText: string,
|
||||
forceUpdate: boolean = false,
|
||||
containerType?: string,
|
||||
) {
|
||||
if (forceUpdate || newText != sceneTextElement.text) {
|
||||
const measure = _measureText(
|
||||
newText,
|
||||
sceneTextElement.fontSize,
|
||||
sceneTextElement.fontFamily,
|
||||
sceneTextElement.lineHeight??getDefaultLineHeight(sceneTextElement.fontFamily),
|
||||
);
|
||||
sceneTextElement.text = newText;
|
||||
sceneTextElement.originalText = newOriginalText;
|
||||
|
||||
if (!sceneTextElement.containerId || containerType==="arrow") {
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/376
|
||||
//I leave the setting of text width to excalidraw, when text is in a container
|
||||
//because text width is fixed to the container width
|
||||
sceneTextElement.width = measure.w;
|
||||
}
|
||||
sceneTextElement.height = measure.h;
|
||||
sceneTextElement.baseline = measure.baseline;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the TextElements in the Excalidraw scene based on textElements MAP in ExcalidrawData
|
||||
@@ -885,26 +951,27 @@ export class ExcalidrawData {
|
||||
private async updateSceneTextElements(forceupdate: boolean = false) {
|
||||
//update text in scene based on textElements Map
|
||||
//first get scene text elements
|
||||
const texts = this.scene.elements?.filter((el: any) => el.type === "text");
|
||||
const elementsMap = arrayToMap(this.scene.elements);
|
||||
const texts = this.scene.elements?.filter((el: any) => el.type === "text") as Mutable<ExcalidrawTextElement>[];
|
||||
for (const te of texts) {
|
||||
const container = getContainerElement(te,this.scene);
|
||||
const container = getContainerElement(te, elementsMap);
|
||||
const originalText =
|
||||
(await this.getText(te.id)) ?? te.originalText ?? te.text;
|
||||
const wrapAt = this.textElements.get(te.id)?.wrapAt;
|
||||
const {text, x, y, width, height} = refreshTextDimensions(
|
||||
te,
|
||||
container,
|
||||
elementsMap,
|
||||
originalText,
|
||||
)
|
||||
try { //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1062
|
||||
this.updateTextElement(
|
||||
te,
|
||||
wrapAt ? wrapText(
|
||||
originalText,
|
||||
getFontString({fontSize: te.fontSize, fontFamily: te.fontFamily}),
|
||||
getBoundTextMaxWidth(container as any)
|
||||
) : originalText,
|
||||
originalText,
|
||||
forceupdate,
|
||||
container?.type,
|
||||
); //(await this.getText(te.id))??te.text serves the case when the whole #Text Elements section is deleted by accident
|
||||
te.originalText = originalText;
|
||||
te.text = text;
|
||||
te.x = x;
|
||||
te.y = y;
|
||||
te.width = width;
|
||||
te.height = height;
|
||||
} catch(e) {
|
||||
debug({where: "ExcalidrawData.updateSceneTextElements", fn: this.updateSceneTextElements, textElement: te});
|
||||
debug(`ExcalidrawData.updateSceneTextElements, textElement: ${te?.id}`, te, this.updateSceneTextElements);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -921,7 +988,6 @@ export class ExcalidrawData {
|
||||
this.textElements.set(id, {
|
||||
raw: text.raw,
|
||||
parsed: (await this.parse(text.raw)).parsed,
|
||||
wrapAt: text.wrapAt,
|
||||
});
|
||||
}
|
||||
//console.log("parsed",this.textElements.get(id).parsed);
|
||||
@@ -998,21 +1064,18 @@ export class ExcalidrawData {
|
||||
this.textElements.set(id, {
|
||||
raw: text.raw,
|
||||
parsed: text.parsed,
|
||||
wrapAt: text.wrapAt,
|
||||
});
|
||||
this.textElements.delete(te.id); //delete the old ID from the Map
|
||||
}
|
||||
if (!this.textElements.has(id)) {
|
||||
const raw = te.rawText && te.rawText !== "" ? te.rawText : te.text; //this is for compatibility with drawings created before the rawText change on ExcalidrawTextElement
|
||||
const wrapAt = estimateMaxLineLen(te.text, te.originalText);
|
||||
this.textElements.set(id, { raw, parsed: null, wrapAt });
|
||||
this.parseasync(id, raw, wrapAt);
|
||||
this.textElements.set(id, { raw, parsed: null});
|
||||
this.parseasync(id, raw);
|
||||
}
|
||||
} else if (!this.textElements.has(te.id)) {
|
||||
const raw = te.rawText && te.rawText !== "" ? te.rawText : te.text; //this is for compatibility with drawings created before the rawText change on ExcalidrawTextElement
|
||||
const wrapAt = estimateMaxLineLen(te.text, te.originalText);
|
||||
this.textElements.set(id, { raw, parsed: null, wrapAt });
|
||||
this.parseasync(id, raw, wrapAt);
|
||||
this.textElements.set(id, { raw, parsed: null});
|
||||
this.parseasync(id, raw);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1060,22 +1123,19 @@ export class ExcalidrawData {
|
||||
? el[0].rawText
|
||||
: (el[0].originalText ?? el[0].text);
|
||||
if (text !== (el[0].originalText ?? el[0].text)) {
|
||||
const wrapAt = estimateMaxLineLen(el[0].text, el[0].originalText);
|
||||
this.textElements.set(key, {
|
||||
raw,
|
||||
parsed: (await this.parse(raw)).parsed,
|
||||
wrapAt,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async parseasync(key: string, raw: string, wrapAt: number) {
|
||||
private async parseasync(key: string, raw: string) {
|
||||
this.textElements.set(key, {
|
||||
raw,
|
||||
parsed: (await this.parse(raw)).parsed,
|
||||
wrapAt,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1266,7 +1326,7 @@ export class ExcalidrawData {
|
||||
disableCompression: boolean = false;
|
||||
generateMD(deletedElements: ExcalidrawElement[] = []): string {
|
||||
let outString = this.textElementCommentedOut ? "%%\n" : "";
|
||||
outString += `${MD_TEXTELEMENTS}\n`;
|
||||
outString += `# Excalidraw Data\n## Text Elements\n`;
|
||||
const textElementLinks = new Map<string, string>();
|
||||
for (const key of this.textElements.keys()) {
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/566
|
||||
@@ -1282,7 +1342,7 @@ export class ExcalidrawData {
|
||||
}
|
||||
|
||||
if (this.elementLinks.size > 0 || textElementLinks.size > 0) {
|
||||
outString += `${MD_ELEMENTLINKS}\n`;
|
||||
outString += `## Element Links\n`;
|
||||
for (const key of this.elementLinks.keys()) {
|
||||
outString += `${key}: ${this.elementLinks.get(key)}\n`;
|
||||
}
|
||||
@@ -1295,7 +1355,7 @@ export class ExcalidrawData {
|
||||
// deliberately not adding mermaids to here. It is enough to have the mermaidText in the image element's customData
|
||||
outString +=
|
||||
this.equations.size > 0 || this.files.size > 0
|
||||
? "# Embedded files\n"
|
||||
? "## Embedded Files\n"
|
||||
: "";
|
||||
if (this.equations.size > 0) {
|
||||
for (const key of this.equations.keys()) {
|
||||
@@ -1542,12 +1602,12 @@ export class ExcalidrawData {
|
||||
* @param id
|
||||
* @returns
|
||||
*/
|
||||
public getParsedText(id: string): [parseResultWrapped: string, parseResultOriginal: string, link: string] {
|
||||
public getParsedText(id: string): string {
|
||||
const t = this.textElements.get(id);
|
||||
if (!t) {
|
||||
return [null, null, null];
|
||||
return null;
|
||||
}
|
||||
return [wrap(t.parsed, t.wrapAt), t.parsed, null];
|
||||
return t.parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1564,24 +1624,23 @@ export class ExcalidrawData {
|
||||
* @param rawText
|
||||
* @param rawOriginalText
|
||||
* @param updateSceneCallback
|
||||
* @returns [parseResultWrapped: string, parseResultOriginal: string, link: string]
|
||||
* @returns [parseResultOriginal: string, link: string]
|
||||
*/
|
||||
public setTextElement(
|
||||
elementID: string,
|
||||
rawText: string,
|
||||
rawOriginalText: string,
|
||||
updateSceneCallback: Function,
|
||||
): [parseResultWrapped: string, parseResultOriginal: string, link: string] {
|
||||
const maxLineLen = estimateMaxLineLen(rawText, rawOriginalText);
|
||||
|
||||
): [parseResultOriginal: string, link: string] {
|
||||
//const maxLineLen = estimateMaxLineLen(rawText, rawOriginalText);
|
||||
const [parseResult, link] = this.quickParse(rawOriginalText); //will return the parsed result if raw text does not include transclusion
|
||||
if (parseResult) {
|
||||
//No transclusion
|
||||
this.textElements.set(elementID, {
|
||||
raw: rawOriginalText,
|
||||
parsed: parseResult,
|
||||
wrapAt: maxLineLen,
|
||||
});
|
||||
return [wrap(parseResult, maxLineLen), parseResult, link];
|
||||
return [parseResult, link];
|
||||
}
|
||||
//transclusion needs to be resolved asynchornously
|
||||
this.parse(rawOriginalText).then((parseRes) => {
|
||||
@@ -1589,35 +1648,28 @@ export class ExcalidrawData {
|
||||
this.textElements.set(elementID, {
|
||||
raw: rawOriginalText,
|
||||
parsed: parsedText,
|
||||
wrapAt: maxLineLen,
|
||||
});
|
||||
if (parsedText) {
|
||||
updateSceneCallback(wrap(parsedText, maxLineLen), parsedText);
|
||||
updateSceneCallback(parsedText);
|
||||
}
|
||||
});
|
||||
return [null, null, null];
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
public async addTextElement(
|
||||
elementID: string,
|
||||
rawText: string,
|
||||
rawOriginalText: string,
|
||||
): Promise<[string, string, string]> {
|
||||
let wrapAt: number = estimateMaxLineLen(rawText, rawOriginalText);
|
||||
if (this.textElements.has(elementID)) {
|
||||
wrapAt = this.textElements.get(elementID).wrapAt;
|
||||
}
|
||||
): Promise<{parseResult: string, link:string}> {
|
||||
const parseResult = await this.parse(rawOriginalText);
|
||||
this.textElements.set(elementID, {
|
||||
raw: rawOriginalText,
|
||||
parsed: parseResult.parsed,
|
||||
wrapAt,
|
||||
});
|
||||
return [
|
||||
wrap(parseResult.parsed, wrapAt),
|
||||
parseResult.parsed,
|
||||
parseResult.link,
|
||||
];
|
||||
return {
|
||||
parseResult: parseResult.parsed,
|
||||
link: parseResult.link,
|
||||
};
|
||||
}
|
||||
|
||||
public deleteTextElement(id: string) {
|
||||
@@ -1631,7 +1683,8 @@ export class ExcalidrawData {
|
||||
: this.plugin.settings.defaultMode;
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["default-mode"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["default-mode"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["default-mode"].name] !== "undefined")
|
||||
) {
|
||||
mode = fileCache.frontmatter[FRONTMATTER_KEYS["default-mode"].name];
|
||||
}
|
||||
@@ -1651,7 +1704,8 @@ export class ExcalidrawData {
|
||||
let opacity = this.plugin.settings.linkOpacity;
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["linkbutton-opacity"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["linkbutton-opacity"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["linkbutton-opacity"].name] !== "undefined")
|
||||
) {
|
||||
opacity = fileCache.frontmatter[FRONTMATTER_KEYS["linkbutton-opacity"].name];
|
||||
}
|
||||
@@ -1662,7 +1716,8 @@ export class ExcalidrawData {
|
||||
const fileCache = this.app.metadataCache.getFileCache(this.file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["onload-script"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["onload-script"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["onload-script"].name] !== "undefined")
|
||||
) {
|
||||
return fileCache.frontmatter[FRONTMATTER_KEYS["onload-script"].name];
|
||||
}
|
||||
@@ -1674,13 +1729,13 @@ export class ExcalidrawData {
|
||||
const fileCache = this.app.metadataCache.getFileCache(this.file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["link-prefix"].name] != null
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["link-prefix"].name] !== "undefined")
|
||||
) {
|
||||
this.linkPrefix = fileCache.frontmatter[FRONTMATTER_KEYS["link-prefix"].name];
|
||||
this.linkPrefix = fileCache.frontmatter[FRONTMATTER_KEYS["link-prefix"].name]??"";
|
||||
} else {
|
||||
this.linkPrefix = this.plugin.settings.linkPrefix;
|
||||
}
|
||||
return linkPrefix != this.linkPrefix;
|
||||
return linkPrefix !== this.linkPrefix;
|
||||
}
|
||||
|
||||
private setUrlPrefix(): boolean {
|
||||
@@ -1688,20 +1743,21 @@ export class ExcalidrawData {
|
||||
const fileCache = this.app.metadataCache.getFileCache(this.file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["url-prefix"].name] != null
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["url-prefix"].name] !== "undefined")
|
||||
) {
|
||||
this.urlPrefix = fileCache.frontmatter[FRONTMATTER_KEYS["url-prefix"].name];
|
||||
this.urlPrefix = fileCache.frontmatter[FRONTMATTER_KEYS["url-prefix"].name]??"";
|
||||
} else {
|
||||
this.urlPrefix = this.plugin.settings.urlPrefix;
|
||||
}
|
||||
return urlPrefix != this.urlPrefix;
|
||||
return urlPrefix !== this.urlPrefix;
|
||||
}
|
||||
|
||||
private setAutoexportPreferences() {
|
||||
const fileCache = this.app.metadataCache.getFileCache(this.file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["autoexport"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["autoexport"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["autoexport"].name] !== "undefined")
|
||||
) {
|
||||
switch ((fileCache.frontmatter[FRONTMATTER_KEYS["autoexport"].name]).toLowerCase()) {
|
||||
case "none": this.autoexportPreference = AutoexportPreference.none; break;
|
||||
@@ -1720,16 +1776,28 @@ export class ExcalidrawData {
|
||||
const fileCache = this.app.metadataCache.getFileCache(this.file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["iframe-theme"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["embeddable-theme"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["embeddable-theme"].name] !== "undefined")
|
||||
) {
|
||||
this.embeddableTheme = fileCache.frontmatter[FRONTMATTER_KEYS["iframe-theme"].name].toLowerCase();
|
||||
this.embeddableTheme = fileCache.frontmatter[FRONTMATTER_KEYS["embeddable-theme"].name].toLowerCase();
|
||||
if (!EMBEDDABLE_THEME_FRONTMATTER_VALUES.includes(this.embeddableTheme)) {
|
||||
this.embeddableTheme = "default";
|
||||
}
|
||||
} else {
|
||||
this.embeddableTheme = this.plugin.settings.iframeMatchExcalidrawTheme ? "auto" : "default";
|
||||
if ( //backwards compatibility
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["iframe-theme"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["iframe-theme"].name] !== "undefined")
|
||||
) {
|
||||
this.embeddableTheme = fileCache.frontmatter[FRONTMATTER_KEYS["iframe-theme"].name].toLowerCase();
|
||||
if (!EMBEDDABLE_THEME_FRONTMATTER_VALUES.includes(this.embeddableTheme)) {
|
||||
this.embeddableTheme = "default";
|
||||
}
|
||||
} else {
|
||||
this.embeddableTheme = this.plugin.settings.iframeMatchExcalidrawTheme ? "auto" : "default";
|
||||
}
|
||||
}
|
||||
return embeddableTheme != this.embeddableTheme;
|
||||
return embeddableTheme !== this.embeddableTheme;
|
||||
}
|
||||
|
||||
private setShowLinkBrackets(): boolean {
|
||||
@@ -1737,14 +1805,15 @@ export class ExcalidrawData {
|
||||
const fileCache = this.app.metadataCache.getFileCache(this.file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["link-brackets"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["link-brackets"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["link-brackets"].name] !== "undefined")
|
||||
) {
|
||||
this.showLinkBrackets =
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["link-brackets"].name] != false;
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["link-brackets"].name] !== false;
|
||||
} else {
|
||||
this.showLinkBrackets = this.plugin.settings.showLinkBrackets;
|
||||
}
|
||||
return showLinkBrackets != this.showLinkBrackets;
|
||||
return showLinkBrackets !== this.showLinkBrackets;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1977,7 +2046,7 @@ export const getTransclusion = async (
|
||||
{ isCancelled: () => false },
|
||||
file,
|
||||
)
|
||||
).blocks.filter((block: any) => block.node.type != "comment");
|
||||
).blocks.filter((block: any) => block.node.type !== "comment");
|
||||
if (!blocks) {
|
||||
return { contents: linkParts.original.trim(), lineNum: 0 };
|
||||
}
|
||||
|
||||
20
src/ExcalidrawLib.d.ts
vendored
20
src/ExcalidrawLib.d.ts
vendored
@@ -1,7 +1,7 @@
|
||||
import { RestoredDataState } from "@zsviczian/excalidraw/types/excalidraw/data/restore";
|
||||
import { ImportedDataState } from "@zsviczian/excalidraw/types/excalidraw/data/types";
|
||||
import { BoundingBox } from "@zsviczian/excalidraw/types/excalidraw/element/bounds";
|
||||
import { ElementsMap, ExcalidrawBindableElement, ExcalidrawElement, ExcalidrawFrameElement, ExcalidrawTextElement, FontFamilyValues, FontString, NonDeleted, NonDeletedExcalidrawElement, Theme } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ElementsMap, ExcalidrawBindableElement, ExcalidrawElement, ExcalidrawFrameElement, ExcalidrawTextContainer, ExcalidrawTextElement, FontFamilyValues, FontString, NonDeleted, NonDeletedExcalidrawElement, Theme } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { AppState, BinaryFiles, ExportOpts, Point, Zoom } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
|
||||
@@ -87,6 +87,24 @@ declare namespace ExcalidrawLib {
|
||||
elements: ExcalidrawElement[] | readonly NonDeleted<ExcalidrawElement>[],
|
||||
): BoundingBox;
|
||||
|
||||
function getContainerElement(
|
||||
element: ExcalidrawTextElement | null,
|
||||
elementsMap: ElementsMap,
|
||||
): ExcalidrawTextContainer | null;
|
||||
|
||||
function refreshTextDimensions(
|
||||
textElement: ExcalidrawTextElement,
|
||||
container: ExcalidrawTextContainer | null,
|
||||
elementsMap: ElementsMap,
|
||||
text: string,
|
||||
): {
|
||||
text: string,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
};
|
||||
|
||||
function getMaximumGroups(
|
||||
elements: ExcalidrawElement[],
|
||||
elementsMap: ElementsMap,
|
||||
|
||||
@@ -51,6 +51,8 @@ import {
|
||||
fileid,
|
||||
sceneCoordsToViewportCoords,
|
||||
MD_EX_SECTIONS,
|
||||
refreshTextDimensions,
|
||||
getContainerElement,
|
||||
} from "./constants/constants";
|
||||
import ExcalidrawPlugin from "./main";
|
||||
import {
|
||||
@@ -99,9 +101,10 @@ import {
|
||||
fragWithHTML,
|
||||
isMaskFile,
|
||||
shouldEmbedScene,
|
||||
getContainerElement,
|
||||
_getContainerElement,
|
||||
arrayToMap,
|
||||
} from "./utils/Utils";
|
||||
import { cleanBlockRef, cleanSectionHeading, getLeaf, getParentOfClass, obsidianPDFQuoteWithRef, openLeaf } from "./utils/ObsidianUtils";
|
||||
import { cleanBlockRef, cleanSectionHeading, getAttachmentsFolderAndFilePath, getLeaf, getParentOfClass, obsidianPDFQuoteWithRef, openLeaf } from "./utils/ObsidianUtils";
|
||||
import { splitFolderAndFilename } from "./utils/FileUtils";
|
||||
import { ConfirmationPrompt, GenericInputPrompt, NewFileActions, Prompt, linkPrompt } from "./dialogs/Prompt";
|
||||
import { ClipboardData } from "@zsviczian/excalidraw/types/excalidraw/clipboard";
|
||||
@@ -132,7 +135,7 @@ import { useDefaultExcalidrawFrame } from "./utils/CustomEmbeddableUtils";
|
||||
import { UniversalInsertFileModal } from "./dialogs/UniversalInsertFileModal";
|
||||
import { getMermaidText, shouldRenderMermaid } from "./utils/MermaidUtils";
|
||||
import { nanoid } from "nanoid";
|
||||
import { CustomMutationObserver, isDebugMode } from "./utils/DebugHelper";
|
||||
import { CustomMutationObserver, debug, log} from "./utils/DebugHelper";
|
||||
import { extractCodeBlocks, postOpenAI } from "./utils/AIUtils";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import { SelectCard } from "./dialogs/SelectCard";
|
||||
@@ -343,6 +346,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
private hoverPreviewTarget: EventTarget = null;
|
||||
private viewModeEnabled:boolean = false;
|
||||
private lastMouseEvent: any = null;
|
||||
private editingTextElementId: string = null; //storing to handle on-screen keyboard hide events
|
||||
|
||||
id: string = (this.leaf as any).id;
|
||||
|
||||
@@ -680,7 +684,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
|
||||
try {
|
||||
const allowSave = this.isDirty() || forcesave; //removed this.semaphores.autosaving
|
||||
if(isDebugMode) console.log({allowSave, isDirty: this.isDirty(), autosaving: this.semaphores.autosaving, forcesave});
|
||||
debug({where: "ExcalidrawView.save", allowSave, isDirty: this.isDirty(), autosaving: this.semaphores.autosaving, forcesave});
|
||||
|
||||
if (allowSave) {
|
||||
const scene = this.getScene();
|
||||
@@ -799,7 +803,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
const header = getExcalidrawMarkdownHeaderSection(this.data, keys);
|
||||
|
||||
if (!this.excalidrawData.disableCompression) {
|
||||
this.excalidrawData.disableCompression =
|
||||
this.excalidrawData.disableCompression = this.plugin.settings.decompressForMDView &&
|
||||
this.isEditedAsMarkdownInOtherView();
|
||||
}
|
||||
const result = header + this.excalidrawData.generateMD(
|
||||
@@ -964,7 +968,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
if (!linkText || partsArray.length === 0) {
|
||||
//the container link takes precedence over the text link
|
||||
if(selectedTextElement?.containerId) {
|
||||
const container = getContainerElement(selectedTextElement, {elements: this.excalidrawAPI.getSceneElements()});
|
||||
const container = _getContainerElement(selectedTextElement, {elements: this.excalidrawAPI.getSceneElements()});
|
||||
if(container) {
|
||||
linkText = container.link;
|
||||
}
|
||||
@@ -1302,8 +1306,13 @@ export default class ExcalidrawView extends TextFileView {
|
||||
);
|
||||
|
||||
const self = this;
|
||||
app.workspace.onLayoutReady(async () => {
|
||||
this.canvasNodeFactory.initialize();
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
debug(`ExcalidrawView.onload app.workspace.onLayoutReady, file: ${self.file?.name}, isActiveLeaf: ${self.app.workspace.activeLeaf === self.leaf}, is activeExcalidrawView set: ${Boolean(self.plugin.activeExcalidrawView)}`);
|
||||
//implemented to overcome issue that activeLeafChangeEventHandler is not called when view is initialized from a saved workspace, since Obsidian 1.6.0
|
||||
if (self.app.workspace.activeLeaf === self.leaf) {
|
||||
self.plugin.activeLeafChangeEventHandler(self.leaf);
|
||||
}
|
||||
self.canvasNodeFactory.initialize();
|
||||
self.contentEl.addClass("excalidraw-view");
|
||||
//https://github.com/zsviczian/excalibrain/issues/28
|
||||
await self.addSlidingPanesListner(); //awaiting this because when using workspaces, onLayoutReady comes too early
|
||||
@@ -1319,7 +1328,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
};
|
||||
|
||||
self.onKeyDown = (e: KeyboardEvent) => {
|
||||
this.modifierKeyDown = {
|
||||
self.modifierKeyDown = {
|
||||
shiftKey: e.shiftKey,
|
||||
ctrlKey: e.ctrlKey,
|
||||
altKey: e.altKey,
|
||||
@@ -1387,7 +1396,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
return;
|
||||
}
|
||||
const { offsetLeft, offsetTop } = target;
|
||||
if (offsetLeft !== self.offsetLeft || offsetTop != self.offsetTop) {
|
||||
if (offsetLeft !== self.offsetLeft || offsetTop !== self.offsetTop) {
|
||||
if (self.excalidrawAPI) {
|
||||
self.refreshCanvasOffset();
|
||||
}
|
||||
@@ -1395,7 +1404,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
self.offsetTop = offsetTop;
|
||||
}
|
||||
};
|
||||
this.parentMoveObserver = isDebugMode
|
||||
this.parentMoveObserver = this.plugin.settings.isDebugMode
|
||||
? new CustomMutationObserver(observerFn, "parentMoveObserver")
|
||||
: new MutationObserver(observerFn)
|
||||
|
||||
@@ -1832,7 +1841,11 @@ export default class ExcalidrawView extends TextFileView {
|
||||
this.lastSaveTimestamp = this.file.stat.mtime;
|
||||
this.lastLoadedFile = this.file;
|
||||
data = this.data = data.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
|
||||
app.workspace.onLayoutReady(async () => {
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
debug(`ExcalidrawView.setViewData app.workspace.onLayoutReady, file: ${this.file?.name}, isActiveLeaf: ${this.app.workspace.activeLeaf === this.leaf}`);
|
||||
let counter = 0;
|
||||
while (!this.file && counter++<50) await sleep(50);
|
||||
if(!this.file) return;
|
||||
this.compatibilityMode = this.file.extension === "excalidraw";
|
||||
await this.plugin.loadSettings();
|
||||
if (this.compatibilityMode) {
|
||||
@@ -1960,7 +1973,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
|
||||
public activeLoader: EmbeddedFilesLoader = null;
|
||||
private nextLoader: EmbeddedFilesLoader = null;
|
||||
public async loadSceneFiles() {
|
||||
public async loadSceneFiles(isThemeChange: boolean = false) {
|
||||
if (!this.excalidrawAPI) {
|
||||
return;
|
||||
}
|
||||
@@ -1996,7 +2009,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
return false;
|
||||
})
|
||||
}
|
||||
},0
|
||||
},0,isThemeChange,
|
||||
);
|
||||
};
|
||||
if (!this.activeLoader) {
|
||||
@@ -2262,9 +2275,9 @@ export default class ExcalidrawView extends TextFileView {
|
||||
this.initializeToolsIconPanelAfterLoading();
|
||||
}
|
||||
|
||||
public setDirty(debug?:number) {
|
||||
public setDirty(location?:number) {
|
||||
if(this.semaphores.saving) return; //do not set dirty if saving
|
||||
if(isDebugMode) console.log(debug);
|
||||
debug(`ExcalidrawView.setDirty location:${location}`);
|
||||
this.semaphores.dirty = this.file?.path;
|
||||
this.diskIcon.querySelector("svg").addClass("excalidraw-dirty");
|
||||
if(!this.semaphores.viewunload && this.toolsPanelRef?.current) {
|
||||
@@ -2353,7 +2366,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
|
||||
public async openAsMarkdown(eState?: any) {
|
||||
if (this.plugin.settings.compress === true) {
|
||||
if (this.plugin.settings.compress && this.plugin.settings.decompressForMDView) {
|
||||
this.excalidrawData.disableCompression = true;
|
||||
await this.save(true, true);
|
||||
}
|
||||
@@ -2362,8 +2375,10 @@ export default class ExcalidrawView extends TextFileView {
|
||||
|
||||
public async convertExcalidrawToMD() {
|
||||
await this.save();
|
||||
const file = await this.plugin.convertSingleExcalidrawToMD(this.file);
|
||||
await sleep(250); //dirty hack to wait for Obsidian metadata to be updated
|
||||
this.plugin.openDrawing(
|
||||
await this.plugin.convertSingleExcalidrawToMD(this.file),
|
||||
file,
|
||||
"active-pane",
|
||||
true
|
||||
);
|
||||
@@ -2786,26 +2801,32 @@ export default class ExcalidrawView extends TextFileView {
|
||||
if (!api) {
|
||||
return false;
|
||||
}
|
||||
const elementsMap = arrayToMap(api.getSceneElements());
|
||||
const textElements = newElements.filter((el) => el.type == "text");
|
||||
for (let i = 0; i < textElements.length; i++) {
|
||||
const [parseResultWrapped, parseResult, link] =
|
||||
const textElement = textElements[i] as Mutable<ExcalidrawTextElement>;
|
||||
const {parseResult, link} =
|
||||
await this.excalidrawData.addTextElement(
|
||||
textElements[i].id,
|
||||
textElement.id,
|
||||
//@ts-ignore
|
||||
textElements[i].text,
|
||||
textElement.text,
|
||||
//@ts-ignore
|
||||
textElements[i].rawText, //TODO: implement originalText support in ExcalidrawAutomate
|
||||
textElement.rawText, //TODO: implement originalText support in ExcalidrawAutomate
|
||||
);
|
||||
if (link) {
|
||||
//@ts-ignore
|
||||
textElements[i].link = link;
|
||||
textElement.link = link;
|
||||
}
|
||||
if (this.textMode == TextMode.parsed) {
|
||||
this.excalidrawData.updateTextElement(
|
||||
textElements[i],
|
||||
parseResultWrapped,
|
||||
parseResult,
|
||||
const {text, x, y, width, height} = refreshTextDimensions(
|
||||
textElement,null,elementsMap,parseResult
|
||||
);
|
||||
textElement.text = text;
|
||||
textElement.originalText = parseResult;
|
||||
textElement.x = x;
|
||||
textElement.y = y;
|
||||
textElement.width = width;
|
||||
textElement.height = height;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2956,6 +2977,12 @@ export default class ExcalidrawView extends TextFileView {
|
||||
api.refresh();
|
||||
};
|
||||
|
||||
// depricated. kept for backward compatibility. e.g. used by the Slideshow plugin
|
||||
// 2024.05.03
|
||||
public refresh() {
|
||||
this.refreshCanvasOffset();
|
||||
}
|
||||
|
||||
private clearHoverPreview() {
|
||||
if (this.hoverPreviewTarget) {
|
||||
const event = new MouseEvent("click", {
|
||||
@@ -3446,7 +3473,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
private async onThemeChange (newTheme: string) {
|
||||
//debug({where:"ExcalidrawView.onThemeChange",file:this.file.name,before:"this.loadSceneFiles",newTheme});
|
||||
this.excalidrawData.scene.appState.theme = newTheme;
|
||||
this.loadSceneFiles();
|
||||
this.loadSceneFiles(true);
|
||||
this.toolsPanelRef?.current?.setTheme(newTheme);
|
||||
//Timeout is to allow appState to update
|
||||
setTimeout(()=>setDynamicStyle(this.plugin.ea,this,this.previousBackgroundColor,this.plugin.settings.dynamicStyling));
|
||||
@@ -3709,15 +3736,35 @@ export default class ExcalidrawView extends TextFileView {
|
||||
if(localFileDragAction === "embeddable") {
|
||||
insertEmbeddableToView(getEA(this), pos, link.file);
|
||||
} else {
|
||||
if(link.file.extension === "pdf") {
|
||||
const insertPDFModal = new InsertPDFModal(this.plugin, this);
|
||||
insertPDFModal.open(link.file);
|
||||
return false;
|
||||
}
|
||||
insertImageToView(getEA(this), pos, link.file);
|
||||
}
|
||||
} else {
|
||||
const extension = getURLImageExtension(link.url);
|
||||
if(
|
||||
localFileDragAction === "image-import" &&
|
||||
(IMAGE_TYPES.contains(extension) || extension === "excalidraw")
|
||||
) {
|
||||
return true; //excalidarw to continue processing
|
||||
if(localFileDragAction === "image-import") {
|
||||
if (IMAGE_TYPES.contains(extension)) {
|
||||
(async () => {
|
||||
const {folder:_, filepath} = await getAttachmentsFolderAndFilePath(this.app, this.file.path,event.dataTransfer.files[i].name);
|
||||
const file = await this.app.vault.createBinary(filepath, await event.dataTransfer.files[i].arrayBuffer())
|
||||
insertImageToView(getEA(this), pos, file);
|
||||
})();
|
||||
return false;
|
||||
} else if(extension === "excalidraw") {
|
||||
return true; //excalidarw to continue processing
|
||||
} else {
|
||||
(async () => {
|
||||
const {folder:_, filepath} = await getAttachmentsFolderAndFilePath(this.app, this.file.path,event.dataTransfer.files[i].name);
|
||||
const file = await this.app.vault.createBinary(filepath, await event.dataTransfer.files[i].arrayBuffer());
|
||||
const modal = new UniversalInsertFileModal(this.plugin, this);
|
||||
modal.open(file, pos);
|
||||
//insertEmbeddableToView(getEA(this), pos, file);
|
||||
})();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if(localFileDragAction === "embeddable" || !IMAGE_TYPES.contains(extension)) {
|
||||
insertEmbeddableToView(getEA(this), pos, null, link.url);
|
||||
@@ -3841,6 +3888,8 @@ export default class ExcalidrawView extends TextFileView {
|
||||
return true;
|
||||
}
|
||||
|
||||
//returns the raw text of the element which is the original text without parsing
|
||||
//in compatibility mode, returns the original text, and for backward compatibility the text if originalText is not available
|
||||
private onBeforeTextEdit (textElement: ExcalidrawTextElement) {
|
||||
clearTimeout(this.isEditingTextResetTimer);
|
||||
this.isEditingTextResetTimer = null;
|
||||
@@ -3855,19 +3904,20 @@ export default class ExcalidrawView extends TextFileView {
|
||||
return raw;
|
||||
}
|
||||
|
||||
|
||||
private onBeforeTextSubmit (
|
||||
textElement: ExcalidrawTextElement,
|
||||
text: string,
|
||||
originalText: string,
|
||||
nextText: string,
|
||||
nextOriginalText: string,
|
||||
isDeleted: boolean,
|
||||
): [string, string, string] {
|
||||
): {updatedNextOriginalText: string, nextLink: string} {
|
||||
const api = this.excalidrawAPI;
|
||||
if (!api) {
|
||||
return [null, null, null];
|
||||
return {updatedNextOriginalText: null, nextLink: null};
|
||||
}
|
||||
|
||||
// 1. Set the isEditingText flag to true to prevent autoresize on mobile
|
||||
// 1500ms is an empirical number, the onscreen keyboard usually disappears in 1-2 seconds
|
||||
// 1500ms is an empirical number, the on-screen keyboard usually disappears in 1-2 seconds
|
||||
this.semaphores.isEditingText = true;
|
||||
if(this.isEditingTextResetTimer) {
|
||||
clearTimeout(this.isEditingTextResetTimer);
|
||||
@@ -3882,7 +3932,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
if (isDeleted) {
|
||||
this.excalidrawData.deleteTextElement(textElement.id);
|
||||
this.setDirty(7);
|
||||
return [null, null, null];
|
||||
return {updatedNextOriginalText: null, nextLink: null};
|
||||
}
|
||||
|
||||
// 3. Check if the user accidently pasted Excalidraw data from the clipboard
|
||||
@@ -3890,7 +3940,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
// textElements cache and update the text element in the scene with a warning.
|
||||
const FORBIDDEN_TEXT = `{"type":"excalidraw/clipboard","elements":[{"`;
|
||||
const WARNING = t("WARNING_PASTING_ELEMENT_AS_TEXT");
|
||||
if(text.startsWith(FORBIDDEN_TEXT)) {
|
||||
if(nextOriginalText.startsWith(FORBIDDEN_TEXT)) {
|
||||
setTimeout(()=>{
|
||||
const elements = this.excalidrawAPI.getSceneElements();
|
||||
const el = elements.filter((el:ExcalidrawElement)=>el.id === textElement.id);
|
||||
@@ -3898,23 +3948,23 @@ export default class ExcalidrawView extends TextFileView {
|
||||
const clone = cloneElement(el[0]);
|
||||
clone.rawText = WARNING;
|
||||
elements[elements.indexOf(el[0])] = clone;
|
||||
this.excalidrawData.setTextElement(clone.id,WARNING,WARNING,()=>{});
|
||||
this.excalidrawData.setTextElement(clone.id,WARNING,()=>{});
|
||||
this.updateScene({elements});
|
||||
api.history.clear();
|
||||
}
|
||||
});
|
||||
return [WARNING,WARNING,null];
|
||||
return {updatedNextOriginalText:WARNING, nextLink:null};
|
||||
}
|
||||
|
||||
const containerId = textElement.containerId;
|
||||
|
||||
const REG_TRANSCLUSION = /^!\[\[([^|\]]*)?.*?]]$|^!\[[^\]]*?]\((.*?)\)$/g;
|
||||
// 4. Check if the text matches the transclusion pattern and if so,
|
||||
// check if the link in the transclusion can be resolved to a file in the vault
|
||||
// if the link can be resolved, check if the file is a markdown file but not an
|
||||
// Excalidraw file. If so, create a timeout to remove the text element from the
|
||||
// scene and invoke the UniversalInsertFileModal with the file.
|
||||
const match = originalText.trim().matchAll(REG_TRANSCLUSION).next(); //reset the iterator
|
||||
// check if the link in the transclusion can be resolved to a file in the vault.
|
||||
// If the link is an image or a PDF file, replace the text element with the image or the PDF.
|
||||
// If the link is an embedded markdown file, then display a message, but otherwise transclude the text step 5.
|
||||
// 1 2
|
||||
const REG_TRANSCLUSION = /^!\[\[([^|\]]*)?.*?]]$|^!\[[^\]]*?]\((.*?)\)$/g;
|
||||
const match = nextOriginalText.trim().matchAll(REG_TRANSCLUSION).next(); //reset the iterator
|
||||
if(match?.value?.[0]) {
|
||||
const link = match.value[1] ?? match.value[2];
|
||||
const file = this.app.metadataCache.getFirstLinkpathDest(link, this.file.path);
|
||||
@@ -3942,7 +3992,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
this.setDirty(9);
|
||||
}
|
||||
});
|
||||
return [null, null, null];
|
||||
return {updatedNextOriginalText: null, nextLink: null};
|
||||
} else {
|
||||
new Notice(t("USE_INSERT_FILE_MODAL"),5000);
|
||||
}
|
||||
@@ -3952,8 +4002,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
// 5. Check if the user made changes to the text, or
|
||||
// the text is missing from ExcalidrawData textElements cache (recently copy/pasted)
|
||||
if (
|
||||
text !== textElement.text ||
|
||||
originalText !== textElement.originalText ||
|
||||
nextOriginalText !== textElement.originalText ||
|
||||
!this.excalidrawData.getRawText(textElement.id)
|
||||
) {
|
||||
//the user made changes to the text or the text is missing from Excalidraw Data (recently copy/pasted)
|
||||
@@ -3962,24 +4011,25 @@ export default class ExcalidrawView extends TextFileView {
|
||||
|
||||
// setTextElement will invoke this callback function in case quick parse was not possible, the parsed text contains transclusions
|
||||
// in this case I need to update the scene asynchronously when parsing is complete
|
||||
const callback = async (wrappedParsedText:string, parsedText:string) => {
|
||||
const callback = async (parsedText:string) => {
|
||||
//this callback function will only be invoked if quick parse fails, i.e. there is a transclusion in the raw text
|
||||
if(this.textMode === TextMode.raw) return;
|
||||
|
||||
const elements = this.excalidrawAPI.getSceneElements();
|
||||
const elementsMap = arrayToMap(elements);
|
||||
const el = elements.filter((el:ExcalidrawElement)=>el.id === textElement.id);
|
||||
if(el.length === 1) {
|
||||
const container = getContainerElement(el[0],elementsMap);
|
||||
const clone = cloneElement(el[0]);
|
||||
const containerType = el[0].containerId
|
||||
? api.getSceneElements().filter((e:ExcalidrawElement)=>e.id===el[0].containerId)?.[0]?.type
|
||||
: undefined;
|
||||
this.excalidrawData.updateTextElement(
|
||||
clone,
|
||||
wrappedParsedText,
|
||||
parsedText,
|
||||
true,
|
||||
containerType
|
||||
);
|
||||
const {text, x, y, width, height} = refreshTextDimensions(el[0], container, elementsMap, parsedText);
|
||||
|
||||
clone.x = x;
|
||||
clone.y = y;
|
||||
clone.width = width;
|
||||
clone.height = height;
|
||||
clone.originalText = parsedText;
|
||||
clone.text = text;
|
||||
|
||||
elements[elements.indexOf(el[0])] = clone;
|
||||
this.updateScene({elements});
|
||||
if(clone.containerId) this.updateContainerSize(clone.containerId);
|
||||
@@ -3988,11 +4038,10 @@ export default class ExcalidrawView extends TextFileView {
|
||||
api.history.clear();
|
||||
};
|
||||
|
||||
const [parseResultWrapped, parseResultOriginal, link] =
|
||||
const [parseResultOriginal, link] =
|
||||
this.excalidrawData.setTextElement(
|
||||
textElement.id,
|
||||
text,
|
||||
originalText,
|
||||
nextOriginalText,
|
||||
callback,
|
||||
);
|
||||
|
||||
@@ -4001,34 +4050,35 @@ export default class ExcalidrawView extends TextFileView {
|
||||
// because the parsed text will have a different size than the raw text had
|
||||
// - depending on the textMode, return the text with markdown markup or the parsed text
|
||||
// if quick parse was not successful return [null, null, null] to indicate that the no changes were made to the text element
|
||||
if (parseResultWrapped) {
|
||||
if (parseResultOriginal) {
|
||||
//there were no transclusions in the raw text, quick parse was successful
|
||||
if (containerId) {
|
||||
this.updateContainerSize(containerId, true);
|
||||
}
|
||||
if (this.textMode === TextMode.raw) {
|
||||
return [text, originalText, link];
|
||||
return {updatedNextOriginalText: nextOriginalText, nextLink: link};
|
||||
} //text is displayed in raw, no need to clear the history, undo will not create problems
|
||||
if (text === parseResultWrapped) {
|
||||
if (nextOriginalText === parseResultOriginal) {
|
||||
if (link) {
|
||||
//don't forget the case: link-prefix:"" && link-brackets:true
|
||||
return [parseResultWrapped, parseResultOriginal, link];
|
||||
return {updatedNextOriginalText: parseResultOriginal, nextLink: link};
|
||||
}
|
||||
return [null, null, null];
|
||||
return {updatedNextOriginalText: null, nextLink: null};
|
||||
} //There were no links to parse, raw text and parsed text are equivalent
|
||||
api.history.clear();
|
||||
return [parseResultWrapped, parseResultOriginal, link];
|
||||
return {updatedNextOriginalText: parseResultOriginal, nextLink:link};
|
||||
}
|
||||
return [null, null, null];
|
||||
return {updatedNextOriginalText: null, nextLink: null};
|
||||
}
|
||||
// even if the text did not change, container sizes might need to be updated
|
||||
if (containerId) {
|
||||
this.updateContainerSize(containerId, true);
|
||||
}
|
||||
if (this.textMode === TextMode.parsed) {
|
||||
return this.excalidrawData.getParsedText(textElement.id);
|
||||
const parseResultOriginal = this.excalidrawData.getParsedText(textElement.id);
|
||||
return {updatedNextOriginalText: parseResultOriginal, nextLink: textElement.link};
|
||||
}
|
||||
return [null, null, null];
|
||||
return {updatedNextOriginalText: null, nextLink: null};
|
||||
}
|
||||
|
||||
private async onLinkOpen(element: ExcalidrawElement, e: any): Promise<void> {
|
||||
@@ -4274,14 +4324,13 @@ export default class ExcalidrawView extends TextFileView {
|
||||
const selectedTextElements = this.getViewSelectedElements().filter(el=>el.type === "text");
|
||||
if(selectedTextElements.length===1) {
|
||||
const selectedTextElement = selectedTextElements[0] as ExcalidrawTextElement;
|
||||
this.excalidrawData.getParsedText(selectedTextElement.id);
|
||||
const containerElement = (this.getViewElements() as ExcalidrawElement[]).find(el=>el.id === selectedTextElement.containerId);
|
||||
|
||||
//if the text element in the container no longer has a link associated with it...
|
||||
if(
|
||||
containerElement &&
|
||||
selectedTextElement.link &&
|
||||
this.excalidrawData.getParsedText(selectedTextElement.id)[1] === selectedTextElement.rawText
|
||||
this.excalidrawData.getParsedText(selectedTextElement.id) === selectedTextElement.rawText
|
||||
) {
|
||||
contextMenuActions.push([
|
||||
renderContextMenuAction(
|
||||
@@ -4540,17 +4589,17 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
|
||||
const json = response.json;
|
||||
if (isDebugMode) console.log(response);
|
||||
debug("ExcalidrawView.ttdDialog", response);
|
||||
|
||||
if (json?.error) {
|
||||
console.log(response);
|
||||
log(response);
|
||||
return {
|
||||
error: new Error(json.error.message),
|
||||
};
|
||||
}
|
||||
|
||||
if(!json?.choices?.[0]?.message?.content) {
|
||||
console.log(response);
|
||||
log(response);
|
||||
return {
|
||||
error: new Error("Generation failed... see console log for details"),
|
||||
};
|
||||
@@ -4559,7 +4608,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
let generatedResponse = extractCodeBlocks(json.choices[0]?.message?.content)[0]?.data;
|
||||
|
||||
if(!generatedResponse) {
|
||||
console.log(response);
|
||||
log(response);
|
||||
return {
|
||||
error: new Error("Generation failed... see console log for details"),
|
||||
};
|
||||
@@ -4697,7 +4746,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
const height = this.contentEl.clientHeight;
|
||||
if(width === 0 || height === 0) return;
|
||||
|
||||
//this is an aweful hack to prevent the keyboard pushing the canvas out of view.
|
||||
//this is an aweful hack to prevent the on-screen keyboard pushing the canvas out of view.
|
||||
//The issue is that contrary to Excalidraw.com where the page is simply pushed up, in
|
||||
//Obsidian the leaf has a fixed top. As a consequence the top of excalidrawWrapperDiv does not get pushed out of view
|
||||
//but shirnks. But the text area is positioned relative to excalidrawWrapperDiv and consequently does not fit, which
|
||||
@@ -4708,8 +4757,12 @@ export default class ExcalidrawView extends TextFileView {
|
||||
//I found that adding and removing this style solves the issue.
|
||||
//...again, just aweful, but works.
|
||||
const st = this.excalidrawAPI.getAppState();
|
||||
const isKeyboardOutEvent = st.editingElement?.type === "text";
|
||||
const isKeyboardBackEvent = this.semaphores.isEditingText && !isKeyboardOutEvent;
|
||||
//isEventOnSameElement attempts to solve https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1729
|
||||
//the issue is that when the user hides the keyboard with the keyboard hide button and not tapping on the screen, then editingElement is not null
|
||||
const isEventOnSameElement = this.editingTextElementId === st.editingElement?.id;
|
||||
const isKeyboardOutEvent:Boolean = st.editingElement && st.editingElement.type === "text" && !isEventOnSameElement;
|
||||
const isKeyboardBackEvent:Boolean = (this.semaphores.isEditingText || isEventOnSameElement) && !isKeyboardOutEvent;
|
||||
this.editingTextElementId = isKeyboardOutEvent ? st.editingElement.id : null;
|
||||
if(isKeyboardOutEvent) {
|
||||
const self = this;
|
||||
const appToolHeight = (self.contentEl.querySelector(".Island.App-toolbar") as HTMLElement)?.clientHeight ?? 0;
|
||||
@@ -4846,10 +4899,10 @@ export default class ExcalidrawView extends TextFileView {
|
||||
onBeforeTextEdit: (textElement: ExcalidrawTextElement) => this.onBeforeTextEdit(textElement),
|
||||
onBeforeTextSubmit: (
|
||||
textElement: ExcalidrawTextElement,
|
||||
text: string,
|
||||
originalText: string,
|
||||
nextText: string,
|
||||
nextOriginalText: string,
|
||||
isDeleted: boolean,
|
||||
): [string, string, string] => this.onBeforeTextSubmit(textElement, text, originalText, isDeleted),
|
||||
): {updatedNextOriginalText: string, nextLink: string} => this.onBeforeTextSubmit(textElement, nextText, nextOriginalText, isDeleted),
|
||||
onLinkOpen: (element: ExcalidrawElement, e: any) => this.onLinkOpen(element, e),
|
||||
onLinkHover: (element: NonDeletedExcalidrawElement, event: React.PointerEvent<HTMLCanvasElement>) => this.onLinkHover(element, event),
|
||||
onContextMenu,
|
||||
|
||||
@@ -86,7 +86,7 @@ const _getPNG = async ({imgAttributes,filenameParts,theme,cacheReady,img,file,ex
|
||||
? 2
|
||||
: 1;
|
||||
|
||||
const cacheKey = {...filenameParts, isDark: theme==="dark", previewImageType: PreviewImageType.PNG, scale};
|
||||
const cacheKey = {...filenameParts, isDark: theme==="dark", previewImageType: PreviewImageType.PNG, scale, isTransparent: !exportSettings.withBackground};
|
||||
|
||||
if(cacheReady) {
|
||||
const src = await imageCache.getImageFromCache(cacheKey);
|
||||
@@ -163,7 +163,7 @@ const _getSVGIMG = async ({filenameParts,theme,cacheReady,img,file,exportSetting
|
||||
exportSettings: ExportSettings,
|
||||
loader: EmbeddedFilesLoader,
|
||||
}):Promise<HTMLImageElement> => {
|
||||
const cacheKey = {...filenameParts, isDark: theme==="dark", previewImageType: PreviewImageType.SVGIMG, scale:1};
|
||||
const cacheKey = {...filenameParts, isDark: theme==="dark", previewImageType: PreviewImageType.SVGIMG, scale:1, isTransparent: !exportSettings.withBackground};
|
||||
if(cacheReady) {
|
||||
const src = await imageCache.getImageFromCache(cacheKey);
|
||||
if(src && typeof src === "string") {
|
||||
@@ -220,13 +220,13 @@ const _getSVGNative = async ({filenameParts,theme,cacheReady,containerElement,fi
|
||||
exportSettings: ExportSettings,
|
||||
loader: EmbeddedFilesLoader,
|
||||
}):Promise<HTMLDivElement> => {
|
||||
const cacheKey = {...filenameParts, isDark: theme==="dark", previewImageType: PreviewImageType.SVG, scale:1};
|
||||
const cacheKey = {...filenameParts, isDark: theme==="dark", previewImageType: PreviewImageType.SVG, scale:1, isTransparent: !exportSettings.withBackground};
|
||||
let maybeSVG;
|
||||
if(cacheReady) {
|
||||
maybeSVG = await imageCache.getImageFromCache(cacheKey);
|
||||
}
|
||||
|
||||
let svg = maybeSVG && (maybeSVG instanceof SVGSVGElement)
|
||||
let svg = (maybeSVG && (maybeSVG instanceof SVGSVGElement))
|
||||
? maybeSVG
|
||||
: convertSVGStringToElement((await createSVG(
|
||||
filenameParts.hasGroupref || filenameParts.hasBlockref || filenameParts.hasSectionref || filenameParts.hasFrameref
|
||||
@@ -714,7 +714,7 @@ const tmpObsidianWYSIWYG = async (
|
||||
internalEmbedDiv.appendChild(imgDiv);
|
||||
}, 500);
|
||||
}
|
||||
const observer = isDebugMode
|
||||
const observer = isDebugMode()
|
||||
? new CustomMutationObserver(markdownObserverFn, "markdowPostProcessorObserverFn")
|
||||
: new MutationObserver(markdownObserverFn);
|
||||
observer.observe(internalEmbedDiv, {
|
||||
@@ -808,10 +808,10 @@ const legacyExcalidrawPopoverObserverFn: MutationCallback = async (m) => {
|
||||
if (!plugin.hover.linkText) {
|
||||
return;
|
||||
}
|
||||
if (m.length != 1) {
|
||||
if (m.length !== 1) {
|
||||
return;
|
||||
}
|
||||
if (m[0].addedNodes.length != 1) {
|
||||
if (m[0].addedNodes.length !== 1) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
@@ -848,7 +848,7 @@ const legacyExcalidrawPopoverObserverFn: MutationCallback = async (m) => {
|
||||
node.appendChild(div);
|
||||
};
|
||||
|
||||
export const legacyExcalidrawPopoverObserver = isDebugMode
|
||||
export const legacyExcalidrawPopoverObserver = isDebugMode()
|
||||
? new CustomMutationObserver(legacyExcalidrawPopoverObserverFn, "legacyExcalidrawPopoverObserverFn")
|
||||
: new MutationObserver(legacyExcalidrawPopoverObserverFn);
|
||||
|
||||
|
||||
@@ -9,13 +9,13 @@ export let EXCALIDRAW_PLUGIN: ExcalidrawPlugin = null;
|
||||
export const setExcalidrawPlugin = (plugin: ExcalidrawPlugin) => {
|
||||
EXCALIDRAW_PLUGIN = plugin;
|
||||
};
|
||||
export const MD_TEXTELEMENTS = "# Text Elements";
|
||||
export const MD_JSON_START = "```json\n";
|
||||
export const MD_JSON_END = "```";
|
||||
export const MD_DRAWING = "# Drawing";
|
||||
export const MD_ELEMENTLINKS = "# Element Links";
|
||||
export const MD_EMBEDFILES = "# Embed files";
|
||||
export const MD_EX_SECTIONS = [MD_TEXTELEMENTS, MD_DRAWING, MD_ELEMENTLINKS, MD_EMBEDFILES];
|
||||
const MD_EXCALIDRAW = "# Excalidraw Data";
|
||||
const MD_TEXTELEMENTS = "## Text Elements";
|
||||
const MD_ELEMENTLINKS = "## Element Links";
|
||||
const MD_EMBEDFILES = "## Embedded Files";
|
||||
const MD_DRAWING = "## Drawing";
|
||||
|
||||
export const MD_EX_SECTIONS = [MD_EXCALIDRAW, MD_TEXTELEMENTS, MD_ELEMENTLINKS, MD_EMBEDFILES, MD_DRAWING];
|
||||
|
||||
export const ERROR_IFRAME_CONVERSION_CANCELED = "iframe conversion canceled";
|
||||
|
||||
@@ -94,6 +94,8 @@ export const {
|
||||
mutateElement,
|
||||
restore,
|
||||
mermaidToExcalidraw,
|
||||
getContainerElement,
|
||||
refreshTextDimensions,
|
||||
} = excalidrawLib;
|
||||
|
||||
export function JSON_parse(x: string): any {
|
||||
@@ -176,7 +178,8 @@ export const FRONTMATTER_KEYS:{[key:string]: {name: string, type: string, depric
|
||||
"border-color": {name: "excalidraw-border-color", type: "text"},
|
||||
"md-css": {name: "excalidraw-css", type: "text"},
|
||||
"autoexport": {name: "excalidraw-autoexport", type: "text"},
|
||||
"iframe-theme": {name: "excalidraw-iframe-theme", type: "text"},
|
||||
"iframe-theme": {name: "excalidraw-iframe-theme", type: "text", depricated: true},
|
||||
"embeddable-theme": {name: "excalidraw-embeddable-theme", type: "text"},
|
||||
"open-as-markdown": {name: "excalidraw-open-md", type: "checkbox"},
|
||||
};
|
||||
|
||||
@@ -197,7 +200,7 @@ export const FRONTMATTER = [
|
||||
"tags: [excalidraw]",
|
||||
"",
|
||||
"---",
|
||||
"==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠==",
|
||||
"==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== You can decompress Drawing data with the command palette: 'Decompress current Excalidraw file'. For more info check in plugin settings under 'Saving'",
|
||||
"",
|
||||
"",
|
||||
].join("\n");
|
||||
|
||||
@@ -6,7 +6,7 @@ If you'd like to learn more, please subscribe to my YouTube channel: [Visual PKM
|
||||
Thank you & Enjoy!
|
||||
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/o0exK-xFP3k" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
<iframe src="https://www.youtube.com/embed/P_Q6avJGoWI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
`;
|
||||
|
||||
@@ -16,6 +16,86 @@ export const RELEASE_NOTES: { [k: string]: string } = {
|
||||
I develop this plugin as a hobby, spending my free time doing this. If you find it valuable, then please say THANK YOU or...
|
||||
|
||||
<div class="ex-coffee-div"><a href="https://ko-fi.com/zsolt"><img src="https://cdn.ko-fi.com/cdn/kofi3.png?v=3" height=45></a></div>
|
||||
`,
|
||||
"2.2.0":`
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/dV0NEOwn5NM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
⚠️⚠️⚠️ BREAKING CHANGE ⚠️⚠️⚠️
|
||||
Files you save with 2.2.0 are not backward compatible with earlier plugin versions!
|
||||
|
||||
## New from excalidraw.com
|
||||
- Wrapable text elements (without the need for transparent sticky notes!)
|
||||
|
||||
## New
|
||||
- File format. I nested all Excalidraw markup under ${String.fromCharCode(96)}# Excalidraw Data${String.fromCharCode(96)}. Here's the new structure.
|
||||
${String.fromCharCode(96,96,96)}markdown
|
||||
---
|
||||
excalidraw-plugin: parsed
|
||||
other-frontmatter-properties: values
|
||||
---
|
||||
back of the note bla bla bla
|
||||
|
||||
# Excalidraw Data
|
||||
## Text Element
|
||||
## Element Links
|
||||
## Embedded Files
|
||||
%%
|
||||
## Drawing
|
||||
%%
|
||||
${String.fromCharCode(96,96,96)}
|
||||
- When opening Excalidraw in Markdown ${String.fromCharCode(96)}# Excalidraw Data${String.fromCharCode(96)} will be folded.
|
||||
- New command palette action: ${String.fromCharCode(96)}Open the back-of-the-note of the selected Excalidraw image${String.fromCharCode(96)}. The action is only visible when selecting an embedded Excalidraw drawing in the Scene. On a desktop, the command will open the back of the selected card in a popout window, and on a mobile, in a new tab.
|
||||
|
||||
## Fixed
|
||||
- Drag and drop from Finder/Explorer (OS external). Images will retain their filenames. PDFs will be imported to the Vault. [#1779](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1779)
|
||||
`,
|
||||
"2.1.8":`
|
||||
## Fixed
|
||||
- Fixing issues that surfaced after upgrading to Obsidian 1.6.0
|
||||
- Fixed Excalidraw hover previews
|
||||
- Cursor for editing links, text elements, and frame names became virtually invisible if Obsidian was in dark mode and Excalidraw in light mode and vica versa.
|
||||
- Rendering Excalidraw drawings in Markdown views, right after Obsidian loaded did not work.
|
||||
- I implemented more graceful fail if you submitted a malformed SVG for SVG to Excalidraw conversation. [#1768](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1768)
|
||||
|
||||
## New
|
||||
- New setting under "Save" in plugin settings to NOT decompress JSON when switching to Markdown view mode. For details see description under "Save" settings. The benefit is smaller file size and fewer results in the Obsidian search. If you want to edit the JSON, you can always manually decompress JSON in markdown view mode with the command palette action "Excalidraw: Decompress JSON".
|
||||
`,
|
||||
"2.1.7:":`
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/P_Q6avJGoWI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## Updates from Excalidraw.com
|
||||
- Improved undo management.
|
||||
- Improved handle to scale images from the side.
|
||||
- Changed arrow binding behavior.
|
||||
- Many other minor fixes and improvements.
|
||||
|
||||
## New
|
||||
- Introduced image caching for nested (embedded) Excalidraw drawings on the scene. This enhancement should lead to improved scene loading times, especially when dealing with numerous embedded Excalidraw drawings.
|
||||
- Added new OCR Command Palette actions. Users can now re-run OCR and run OCR for selected elements.
|
||||
|
||||
## Fixed
|
||||
- Fixed an issue where cropping an embeddable PDF frame in the Excalidraw Scene caused distortion based on the embeddable element's aspect ratio. ([#1756](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1756))
|
||||
- Removed the listing of ${String.fromCharCode(96)}# Embedded files${String.fromCharCode(96)} section when adding a "Back of the note card". ([#1754](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1754))
|
||||
- Resolved the issue where closing the on-screen keyboard with the keyboard hide button of your phone, instead of tapping somewhere else on the Excalidraw scene, did not resize the scene correctly. ([#1729](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1729))
|
||||
- Fixed the problem where pasting a text element as text into markdown incorrectly pasted the text to the end of the MD note, with line breaks as rendered on screen in Excalidraw. Also addressed the issue where pasting an image element as an image resulted in it being pasted to the end of the document. ([#1749](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1749))
|
||||
- Corrected the color inversion of embedded images when changing the theme from light to dark, then back from dark to light, and again from light to dark on the third change.
|
||||
- Addressed the problem where cropping an image while unlocking and rotating it in the cropper did not reflect the rotation. Note that rotating the image in Cropper required switching to markdown view mode, changing the "locked": true property to false, then switching back to Excalidraw mode. This issue likely impacted only a very few power users. ([#1745](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1745))
|
||||
|
||||
## New in ExcalidrawAutomate
|
||||
${String.fromCharCode(96,96,96)}ts
|
||||
/**
|
||||
* Retruns the embedded images in the scene recursively. If excalidrawFile is not provided,
|
||||
* the function will use ea.targetView.file
|
||||
* @param excalidrawFile
|
||||
* @returns TFile[] of all nested images and Excalidraw drawings recursively
|
||||
*/
|
||||
public getEmbeddedImagesFiletree(excalidrawFile?: TFile): TFile[];
|
||||
${String.fromCharCode(96,96,96)}
|
||||
|
||||
`,
|
||||
"2.1.6":`
|
||||
## Two minor fixes
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { MarkdownRenderer, Modal, Notice, request } from "obsidian";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { errorlog, escapeRegExp, log } from "../utils/Utils";
|
||||
import { errorlog, escapeRegExp } from "../utils/Utils";
|
||||
import { log } from "src/utils/DebugHelper";
|
||||
|
||||
const URL =
|
||||
"https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/index-new.md";
|
||||
|
||||
@@ -571,6 +571,13 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
"If no template is set, it returns null.",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "getEmbeddedImagesFiletree",
|
||||
code: "getEmbeddedImagesFiletree(excalidrawFile?: TFile): TFile[]",
|
||||
desc: "Retruns the embedded images in the scene recursively. If excalidrawFile is not provided, " +
|
||||
"the function will use ea.targetView.file",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "getAttachmentFilepath",
|
||||
code: "async getAttachmentFilepath(filename: string): Promise<string>",
|
||||
@@ -841,9 +848,9 @@ export const FRONTMATTER_KEYS_INFO: SuggesterInfo[] = [
|
||||
after: ": png",
|
||||
},
|
||||
{
|
||||
field: "iframe-theme",
|
||||
field: "embeddable-theme",
|
||||
code: null,
|
||||
desc: "Override iFrame theme plugin-settings for this file. 'match' will match the Excalidraw theme, 'default' will match the obsidian theme. Valid values are\ndark\nlight\nauto\ndefault",
|
||||
desc: "Override embeddable's theme plugin-settings for this file. 'auto' will match the Excalidraw theme, 'default' will match the Obsidian theme. Valid values are\ndark\nlight\nauto\ndefault",
|
||||
after: ": auto",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -19,7 +19,7 @@ export class UniversalInsertFileModal extends Modal {
|
||||
private plugin: ExcalidrawPlugin,
|
||||
private view: ExcalidrawView,
|
||||
) {
|
||||
super(app);
|
||||
super(plugin.app);
|
||||
const appState = (view.excalidrawAPI as ExcalidrawImperativeAPI).getAppState();
|
||||
const containerRect = view.containerEl.getBoundingClientRect();
|
||||
const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
|
||||
|
||||
@@ -9,6 +9,7 @@ export default {
|
||||
// main.ts
|
||||
CONVERT_URL_TO_FILE: "Save image from URL to local file",
|
||||
UNZIP_CURRENT_FILE: "Decompress current Excalidraw file",
|
||||
ZIP_CURRENT_FILE: "Compress current Excalidraw file",
|
||||
PUBLISH_SVG_CHECK: "Obsidian Publish: Find SVG and PNG exports that are out of date",
|
||||
EMBEDDABLE_PROPERTIES: "Embeddable Properties",
|
||||
EMBEDDABLE_RELATIVE_ZOOM: "Scale selected embeddable elements to 100% relative to the current canvas zoom",
|
||||
@@ -35,6 +36,7 @@ export default {
|
||||
TRANSCLUDE: "Embed a drawing",
|
||||
TRANSCLUDE_MOST_RECENT: "Embed the most recently edited drawing",
|
||||
TOGGLE_LEFTHANDED_MODE: "Toggle left-handed mode",
|
||||
FLIP_IMAGE: "Open the back-of-the-note of the selected excalidraw image",
|
||||
NEW_IN_NEW_PANE: "Create new drawing - IN AN ADJACENT WINDOW",
|
||||
NEW_IN_NEW_TAB: "Create new drawing - IN A NEW TAB",
|
||||
NEW_IN_ACTIVE_PANE: "Create new drawing - IN THE CURRENT ACTIVE WINDOW",
|
||||
@@ -74,9 +76,12 @@ export default {
|
||||
`Insert LaTeX formula (e.g. \\binom{n}{k} = \\frac{n!}{k!(n-k)!}). ${labelALT()}+CLICK to watch a help video.`,
|
||||
ENTER_LATEX: "Enter a valid LaTeX expression",
|
||||
READ_RELEASE_NOTES: "Read latest release notes",
|
||||
RUN_OCR: "OCR: Grab text from freedraw scribble and pictures to clipboard",
|
||||
RUN_OCR: "OCR full drawing: Grab text from freedraw + images to clipboard and doc.props",
|
||||
RERUN_OCR: "OCR full drawing re-run: Grab text from freedraw + images to clipboard and doc.props",
|
||||
RUN_OCR_ELEMENTS: "OCR selected elements: Grab text from freedraw + images to clipboard",
|
||||
TRAY_MODE: "Toggle property-panel tray-mode",
|
||||
SEARCH: "Search for text in drawing",
|
||||
CROP_PAGE: "Crop and mask selected page",
|
||||
CROP_IMAGE: "Crop and mask image",
|
||||
ANNOTATE_IMAGE : "Annotate image in Excalidraw",
|
||||
INSERT_ACTIVE_PDF_PAGE_AS_IMAGE: "Insert active PDF page as image",
|
||||
@@ -215,6 +220,14 @@ export default {
|
||||
"once you switch back to Excalidraw view. " +
|
||||
"The setting only has effect 'point forward', meaning, existing drawings will not be affected by the setting " +
|
||||
"until you open them and save them.<br><b><u>Toggle ON:</u></b> Compress drawing JSON<br><b><u>Toggle OFF:</u></b> Leave drawing JSON uncompressed",
|
||||
DECOMPRESS_FOR_MD_NAME: "Decompress Excalidraw JSON in Markdown View",
|
||||
DECOMPRESS_FOR_MD_DESC:
|
||||
"By enabling this feature Excalidraw will automatically decompress the drawing JSON when you switch to Markdown view. " +
|
||||
"This will allow you to easily read and edit the JSON string. The drawing will be compressed again " +
|
||||
"once you switch back to Excalidraw view and save the drawing (CTRL+S).<br>" +
|
||||
"I recommend switching this feature off as it will result in smaller file sizes and avoiding unnecessary results in Obsidian search. " +
|
||||
"You can always use the 'Excalidraw: Decompress current Excalidraw file' command from the command palette "+
|
||||
"to manually decompress the drawing JSON when you need to read or edit it.",
|
||||
AUTOSAVE_INTERVAL_DESKTOP_NAME: "Interval for autosave on Desktop",
|
||||
AUTOSAVE_INTERVAL_DESKTOP_DESC:
|
||||
"The time interval between saves. Autosave will skip if there are no changes in the drawing. " +
|
||||
@@ -563,6 +576,9 @@ FILENAME_HEAD: "Filename",
|
||||
"Double files will be exported both if auto-export SVG or PNG (or both) are enabled, as well as when clicking export on a single image.",
|
||||
COMPATIBILITY_HEAD: "Compatibility features",
|
||||
COMPATIBILITY_DESC: "You should only enable these features if you have a strong reason for wanting to work with excalidraw.com files instead of markdown files. Many of the plugin features are not supported on legacy files. Typical usecase would be if you use set your vault up on top of a Visual Studio Code project folder and you have .excalidraw drawings you want to access from Visual Studio Code as well. Another usecase might be using Excalidraw in Logseq and Obsidian in parallel.",
|
||||
DEBUGMODE_NAME: "Enable debug messages",
|
||||
DEBUGMODE_DESC: "I recommend restarting Obsidian after enabling/disabling this setting. This enable debug messages in the console. This is useful for troubleshooting issues. " +
|
||||
"If you are experiencing problems with the plugin, please enable this setting, reproduce the issue, and include the console log in the issue you raise on <a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/issues'>GitHub</a>",
|
||||
SLIDING_PANES_NAME: "Sliding panes plugin support",
|
||||
SLIDING_PANES_DESC:
|
||||
"Need to restart Obsidian for this change to take effect.<br>" +
|
||||
|
||||
213
src/main.ts
213
src/main.ts
@@ -41,9 +41,9 @@ import {
|
||||
EXPORT_IMG_ICON,
|
||||
LOCALE,
|
||||
IMAGE_TYPES,
|
||||
MD_TEXTELEMENTS,
|
||||
setExcalidrawPlugin,
|
||||
DEVICE
|
||||
DEVICE,
|
||||
sceneCoordsToViewportCoords
|
||||
} from "./constants/constants";
|
||||
import {
|
||||
VIRGIL_FONT,
|
||||
@@ -96,7 +96,6 @@ import {
|
||||
import {
|
||||
getFontDataURL,
|
||||
errorlog,
|
||||
log,
|
||||
setLeftHandedMode,
|
||||
sleep,
|
||||
isVersionNewerThanOther,
|
||||
@@ -105,7 +104,7 @@ import {
|
||||
decompress,
|
||||
getImageSize,
|
||||
} from "./utils/Utils";
|
||||
import { extractSVGPNGFileName, getActivePDFPageNumberFromPDFView, getAttachmentsFolderAndFilePath, getNewOrAdjacentLeaf, getParentOfClass, isObsidianThemeDark, mergeMarkdownFiles, openLeaf } from "./utils/ObsidianUtils";
|
||||
import { editorInsertText, extractSVGPNGFileName, foldExcalidrawSection, getActivePDFPageNumberFromPDFView, getAttachmentsFolderAndFilePath, getNewOrAdjacentLeaf, getParentOfClass, isObsidianThemeDark, mergeMarkdownFiles, openLeaf } from "./utils/ObsidianUtils";
|
||||
import { ExcalidrawElement, ExcalidrawEmbeddableElement, ExcalidrawImageElement, ExcalidrawTextElement, FileId } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ScriptEngine } from "./Scripts";
|
||||
import {
|
||||
@@ -133,10 +132,11 @@ import { processLinkText } from "./utils/CustomEmbeddableUtils";
|
||||
import { getEA } from "src";
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import { CustomMutationObserver, durationTreshold, isDebugMode } from "./utils/DebugHelper";
|
||||
import { CustomMutationObserver, debug, durationTreshold, log } from "./utils/DebugHelper";
|
||||
import { carveOutImage, carveOutPDF, createImageCropperFile, CROPPED_PREFIX } from "./utils/CarveOut";
|
||||
import { ExcalidrawConfig } from "./utils/ExcalidrawConfig";
|
||||
import { EditorHandler } from "./CodeMirrorExtension/EditorHandler";
|
||||
import de from "./lang/locale/de";
|
||||
|
||||
declare const EXCALIDRAW_PACKAGES:string;
|
||||
declare const react:any;
|
||||
@@ -186,6 +186,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
private stylesManager:StylesManager;
|
||||
private textMeasureDiv:HTMLDivElement = null;
|
||||
public editorHandler: EditorHandler;
|
||||
public activeLeafChangeEventHandler: (leaf: WorkspaceLeaf) => Promise<void>;
|
||||
//if set, the next time this file is opened it will be opened as markdown
|
||||
public forceToOpenInMarkdownFilepath: string = null;
|
||||
|
||||
constructor(app: App, manifest: PluginManifest) {
|
||||
super(app, manifest);
|
||||
@@ -229,7 +232,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
|
||||
public registerEvent(event: any) {
|
||||
if(!isDebugMode) {
|
||||
if(!this.settings.isDebugMode) {
|
||||
super.registerEvent(event);
|
||||
return;
|
||||
}
|
||||
@@ -266,6 +269,11 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
addIcon(EXPORT_IMG_ICON_NAME, EXPORT_IMG_ICON);
|
||||
|
||||
await this.loadSettings({reEnableAutosave:true});
|
||||
if(!this.settings.onceOffCompressFlagReset) {
|
||||
this.settings.compress = true;
|
||||
this.settings.onceOffCompressFlagReset = true;
|
||||
await this.saveSettings();
|
||||
}
|
||||
this.excalidrawConfig = new ExcalidrawConfig(this);
|
||||
await loadMermaid();
|
||||
this.editorHandler = new EditorHandler(this);
|
||||
@@ -320,6 +328,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
const self = this;
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
debug(`ExcalidrawPlugin.onload.app.workspace.onLayoutReady`);
|
||||
this.scriptEngine = new ScriptEngine(self);
|
||||
imageCache.initializeDB(self);
|
||||
});
|
||||
@@ -329,6 +338,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
private setPropertyTypes() {
|
||||
const app = this.app;
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
debug(`ExcalidrawPlugin.setPropertyTypes app.workspace.onLayoutReady`);
|
||||
Object.keys(FRONTMATTER_KEYS).forEach((key) => {
|
||||
if(FRONTMATTER_KEYS[key].depricated === true) return;
|
||||
const {name, type} = FRONTMATTER_KEYS[key];
|
||||
@@ -339,6 +349,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
public initializeFonts() {
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
debug(`ExcalidrawPlugin.initializeFonts app.workspace.onLayoutReady`);
|
||||
const font = await getFontDataURL(
|
||||
this.app,
|
||||
this.settings.experimantalFourthFont,
|
||||
@@ -393,16 +404,17 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
private switchToExcalidarwAfterLoad() {
|
||||
const self = this;
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
debug(`ExcalidrawPlugin.switchToExcalidarwAfterLoad app.workspace.onLayoutReady`);
|
||||
let leaf: WorkspaceLeaf;
|
||||
for (leaf of this.app.workspace.getLeavesOfType("markdown")) {
|
||||
if (
|
||||
leaf.view instanceof MarkdownView &&
|
||||
self.isExcalidrawFile(leaf.view.file) &&
|
||||
fileShouldDefaultAsExcalidraw(leaf.view.file?.path, self.app)
|
||||
) {
|
||||
self.excalidrawFileModes[(leaf as any).id || leaf.view.file.path] =
|
||||
VIEW_TYPE_EXCALIDRAW;
|
||||
self.setExcalidrawView(leaf);
|
||||
if ( leaf.view instanceof MarkdownView && self.isExcalidrawFile(leaf.view.file)) {
|
||||
if (fileShouldDefaultAsExcalidraw(leaf.view.file?.path, self.app)) {
|
||||
self.excalidrawFileModes[(leaf as any).id || leaf.view.file.path] =
|
||||
VIEW_TYPE_EXCALIDRAW;
|
||||
self.setExcalidrawView(leaf);
|
||||
} else {
|
||||
foldExcalidrawSection(leaf.view);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -620,16 +632,19 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
* Displays a transcluded .excalidraw image in markdown preview mode
|
||||
*/
|
||||
private addMarkdownPostProcessor() {
|
||||
//Licat: Are you registering your post processors in onLayoutReady? You should register them in onload instead
|
||||
initializeMarkdownPostProcessor(this);
|
||||
this.registerMarkdownPostProcessor(markdownPostProcessor);
|
||||
|
||||
const self = this;
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
initializeMarkdownPostProcessor(self);
|
||||
self.registerMarkdownPostProcessor(markdownPostProcessor);
|
||||
debug(`ExcalidrawPlugin.addMarkdownPostProcessor app.workspace.onLayoutReady`);
|
||||
|
||||
// internal-link quick preview
|
||||
self.registerEvent(self.app.workspace.on("hover-link", hoverEvent));
|
||||
|
||||
//only add the legacy file observer if there are legacy files in the vault
|
||||
if(this.app.vault.getFiles().some(f=>f.extension === "excalidraw")) {
|
||||
if(self.app.vault.getFiles().some(f=>f.extension === "excalidraw")) {
|
||||
self.enableLegacyFilePopoverObserver();
|
||||
}
|
||||
});
|
||||
@@ -672,7 +687,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
});
|
||||
};
|
||||
|
||||
this.themeObserver = isDebugMode
|
||||
this.themeObserver = this.settings.isDebugMode
|
||||
? new CustomMutationObserver(themeObserverFn, "themeObserver")
|
||||
: new MutationObserver(themeObserverFn);
|
||||
|
||||
@@ -704,7 +719,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
*/
|
||||
private experimentalFileTypeDisplay() {
|
||||
const insertFiletype = (el: HTMLElement) => {
|
||||
if (el.childElementCount != 1) {
|
||||
if (el.childElementCount !== 1) {
|
||||
return;
|
||||
}
|
||||
const filename = el.getAttribute("data-path");
|
||||
@@ -738,12 +753,13 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
});
|
||||
};
|
||||
|
||||
this.fileExplorerObserver = isDebugMode
|
||||
this.fileExplorerObserver = this.settings.isDebugMode
|
||||
? new CustomMutationObserver(fileExplorerObserverFn, "fileExplorerObserver")
|
||||
: new MutationObserver(fileExplorerObserverFn);
|
||||
|
||||
const self = this;
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
debug(`ExcalidrawPlugin.experimentalFileTypeDisplay app.workspace.onLayoutReady`);
|
||||
document.querySelectorAll(".nav-file-title").forEach(insertFiletype); //apply filetype to files already displayed
|
||||
const container = document.querySelector(".nav-files-container");
|
||||
if (container) {
|
||||
@@ -868,9 +884,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
(async () => {
|
||||
const data = await this.app.vault.read(activeFile);
|
||||
const parts = data.split("%%\n# Drawing\n```compressed-json\n");
|
||||
const parts = data.split("\n## Drawing\n```compressed-json\n");
|
||||
if(parts.length!==2) return;
|
||||
const header = parts[0] + "%%\n# Drawing\n```json\n";
|
||||
const header = parts[0] + "\n## Drawing\n```json\n";
|
||||
const compressed = parts[1].split("\n```\n%%");
|
||||
if(compressed.length!==2) return;
|
||||
const decompressed = decompress(compressed[0]);
|
||||
@@ -1045,7 +1061,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
if (checking) {
|
||||
return (
|
||||
Boolean(this.app.workspace.getActiveViewOfType(MarkdownView)) &&
|
||||
this.lastActiveExcalidrawFilePath != null
|
||||
this.lastActiveExcalidrawFilePath !== null
|
||||
);
|
||||
}
|
||||
const file = this.app.vault.getAbstractFileByPath(
|
||||
@@ -1182,7 +1198,51 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
new Notice("Taskbone OCR is not enabled. Please go to plugins settings to enable it.",4000);
|
||||
return true;
|
||||
}
|
||||
this.taskbone.getTextForView(view, false);
|
||||
this.taskbone.getTextForView(view, {forceReScan: false});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "rerun-ocr",
|
||||
name: t("RERUN_OCR"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (checking) {
|
||||
return (
|
||||
Boolean(view)
|
||||
);
|
||||
}
|
||||
if (view) {
|
||||
if(!this.settings.taskboneEnabled) {
|
||||
new Notice("Taskbone OCR is not enabled. Please go to plugins settings to enable it.",4000);
|
||||
return true;
|
||||
}
|
||||
this.taskbone.getTextForView(view, {forceReScan: true});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "run-ocr-selectedelements",
|
||||
name: t("RUN_OCR_ELEMENTS"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (checking) {
|
||||
return (
|
||||
Boolean(view)
|
||||
);
|
||||
}
|
||||
if (view) {
|
||||
if(!this.settings.taskboneEnabled) {
|
||||
new Notice("Taskbone OCR is not enabled. Please go to plugins settings to enable it.",4000);
|
||||
return true;
|
||||
}
|
||||
this.taskbone.getTextForView(view, {forceReScan: false, selectedElementsOnly: true, addToFrontmatter: false});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -1531,6 +1591,39 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "flip-image",
|
||||
name: t("FLIP_IMAGE"),
|
||||
checkCallback: (checking:boolean) => {
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if(!view) return false;
|
||||
if(!view.excalidrawAPI) return false;
|
||||
const els = view.getViewSelectedElements().filter(el=>el.type==="image");
|
||||
if(els.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
const el = els[0] as ExcalidrawImageElement;
|
||||
let ef = view.excalidrawData.getFile(el.fileId);
|
||||
if(!ef) {
|
||||
return false;
|
||||
}
|
||||
if(!this.isExcalidrawFile(ef.file)) {
|
||||
return false;
|
||||
}
|
||||
if(checking) return true;
|
||||
this.forceToOpenInMarkdownFilepath = ef.file?.path;
|
||||
const appState = view.excalidrawAPI.getAppState();
|
||||
const {x:centerX,y:centerY} = sceneCoordsToViewportCoords({sceneX:el.x+el.width/2,sceneY:el.y+el.height/2},appState);
|
||||
const {width, height} = {width:600, height:600};
|
||||
const {x,y} = {
|
||||
x:centerX - width/2 + view.ownerWindow.screenX,
|
||||
y:centerY - height/2 + view.ownerWindow.screenY,
|
||||
}
|
||||
|
||||
this.openDrawing(ef.file, DEVICE.isMobile ? "new-tab":"popout-window", true, undefined, false, {x,y,width,height});
|
||||
}
|
||||
})
|
||||
|
||||
this.addCommand({
|
||||
id: "reset-image-to-100",
|
||||
name: t("RESET_IMG_TO_100"),
|
||||
@@ -2227,12 +2320,13 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
private registerMonkeyPatches() {
|
||||
const key = "https://github.com/zsviczian/obsidian-excalidraw-plugin/issues";
|
||||
|
||||
this.register(
|
||||
around(Workspace.prototype, {
|
||||
getActiveViewOfType(old) {
|
||||
return dedupe(key, old, function(...args) {
|
||||
const result = old && old.apply(this, args);
|
||||
const maybeEAView = app?.workspace?.activeLeaf?.view;
|
||||
const maybeEAView = self.app?.workspace?.activeLeaf?.view;
|
||||
if(!maybeEAView || !(maybeEAView instanceof ExcalidrawView)) return result;
|
||||
const error = new Error();
|
||||
const stackTrace = error.stack;
|
||||
@@ -2332,28 +2426,38 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
setViewState(next) {
|
||||
return function (state: ViewState, ...rest: any[]) {
|
||||
const markdownViewLoaded =
|
||||
self._loaded && // Don't force excalidraw mode during shutdown
|
||||
state.type === "markdown" && // If we have a markdown file
|
||||
state.state?.file;
|
||||
if (
|
||||
// Don't force excalidraw mode during shutdown
|
||||
self._loaded &&
|
||||
// If we have a markdown file
|
||||
state.type === "markdown" &&
|
||||
state.state?.file &&
|
||||
// And the current mode of the file is not set to markdown
|
||||
self.excalidrawFileModes[this.id || state.state.file] !==
|
||||
"markdown"
|
||||
markdownViewLoaded &&
|
||||
self.excalidrawFileModes[this.id || state.state.file] !== "markdown"
|
||||
) {
|
||||
if (fileShouldDefaultAsExcalidraw(state.state.file,this.app)) {
|
||||
const file = state.state.file;
|
||||
if ((self.forceToOpenInMarkdownFilepath !== file) && fileShouldDefaultAsExcalidraw(file,this.app)) {
|
||||
// If we have it, force the view type to excalidraw
|
||||
const newState = {
|
||||
...state,
|
||||
type: VIEW_TYPE_EXCALIDRAW,
|
||||
};
|
||||
|
||||
self.excalidrawFileModes[state.state.file] =
|
||||
self.excalidrawFileModes[file] =
|
||||
VIEW_TYPE_EXCALIDRAW;
|
||||
|
||||
return next.apply(this, [newState, ...rest]);
|
||||
}
|
||||
self.forceToOpenInMarkdownFilepath = null;
|
||||
}
|
||||
|
||||
if(markdownViewLoaded) {
|
||||
const leaf = this;
|
||||
setTimeout(async ()=> {
|
||||
if(!leaf || !leaf.view || !(leaf.view instanceof MarkdownView) ||
|
||||
!leaf.view.file || !self.isExcalidrawFile(leaf.view.file)
|
||||
) return;
|
||||
foldExcalidrawSection(leaf.view)
|
||||
},500);
|
||||
}
|
||||
|
||||
return next.apply(this, [state, ...rest]);
|
||||
@@ -2369,6 +2473,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
const self = this;
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
debug(`ExcalidrawPlugin.runStartupScript app.workspace.onLayoutReady: ${self.settings?.startupScriptPath}`);
|
||||
const path = self.settings.startupScriptPath.endsWith(".md")
|
||||
? self.settings.startupScriptPath
|
||||
: `${self.settings.startupScriptPath}.md`;
|
||||
@@ -2391,6 +2496,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
private registerEventListeners() {
|
||||
const self: ExcalidrawPlugin = this;
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
debug("ExcalidrawPlugin.registerEventListeners app.workspace.onLayoutReady");
|
||||
const onPasteHandler = (
|
||||
evt: ClipboardEvent,
|
||||
editor: Editor,
|
||||
@@ -2419,16 +2525,16 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
if(sourceFile && imageFile && imageFile instanceof TFile) {
|
||||
path = self.app.metadataCache.fileToLinktext(imageFile,sourceFile.path);
|
||||
}
|
||||
editor.insertText(getLink(self, {path}));
|
||||
editorInsertText(editor, getLink(self, {path}));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (element.type === "text") {
|
||||
editor.insertText(element.text);
|
||||
editorInsertText(editor, element.rawText);
|
||||
return;
|
||||
}
|
||||
if (element.link) {
|
||||
editor.insertText(`${element.link}`);
|
||||
editorInsertText(editor, `${element.link}`);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -2657,7 +2763,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
const scope = self.app.keymap.getRootScope();
|
||||
const handler_ctrlEnter = scope.register(["Mod"], "Enter", () => true);
|
||||
scope.keys.unshift(scope.keys.pop()); // Force our handler to the front of the list
|
||||
const handler_ctrlK = scope.register(["Mod"], "k", () => {return true});
|
||||
const handler_ctrlK = scope.register(["Mod"], "k", () => true);
|
||||
scope.keys.unshift(scope.keys.pop()); // Force our handler to the front of the list
|
||||
const handler_ctrlF = scope.register(["Mod"], "f", () => {
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
@@ -2687,8 +2793,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
}
|
||||
};
|
||||
this.activeLeafChangeEventHandler = activeLeafChangeEventHandler;
|
||||
self.registerEvent(
|
||||
app.workspace.on(
|
||||
this.app.workspace.on(
|
||||
"active-leaf-change",
|
||||
activeLeafChangeEventHandler,
|
||||
),
|
||||
@@ -2696,7 +2803,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
self.addFileSaveTriggerEventHandlers();
|
||||
|
||||
const metaCache: MetadataCache = app.metadataCache;
|
||||
const metaCache: MetadataCache = this.app.metadataCache;
|
||||
//@ts-ignore
|
||||
metaCache.getCachedFiles().forEach((filename: string) => {
|
||||
const fm = metaCache.getCache(filename)?.frontmatter;
|
||||
@@ -2705,7 +2812,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
filename.match(/\.excalidraw$/)
|
||||
) {
|
||||
self.updateFileCache(
|
||||
app.vault.getAbstractFileByPath(filename) as TFile,
|
||||
this.app.vault.getAbstractFileByPath(filename) as TFile,
|
||||
fm,
|
||||
);
|
||||
}
|
||||
@@ -2778,14 +2885,14 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
};
|
||||
|
||||
if (leftWorkspaceDrawer) {
|
||||
this.workspaceDrawerLeftObserver = isDebugMode
|
||||
this.workspaceDrawerLeftObserver = this.settings.isDebugMode
|
||||
? new CustomMutationObserver(action, "slidingDrawerLeftObserver")
|
||||
: new MutationObserver(action);
|
||||
this.workspaceDrawerLeftObserver.observe(leftWorkspaceDrawer, options);
|
||||
}
|
||||
|
||||
if (rightWorkspaceDrawer) {
|
||||
this.workspaceDrawerRightObserver = isDebugMode
|
||||
this.workspaceDrawerRightObserver = this.settings.isDebugMode
|
||||
? new CustomMutationObserver(action, "slidingDrawerRightObserver")
|
||||
: new MutationObserver(action);
|
||||
this.workspaceDrawerRightObserver.observe(
|
||||
@@ -2819,7 +2926,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
this.activeExcalidrawView.save();
|
||||
};
|
||||
|
||||
this.modalContainerObserver = isDebugMode
|
||||
this.modalContainerObserver = this.settings.isDebugMode
|
||||
? new CustomMutationObserver(modalContainerObserverFn, "modalContainerObserver")
|
||||
: new MutationObserver(modalContainerObserverFn);
|
||||
this.activeViewDoc = this.activeExcalidrawView.ownerDocument;
|
||||
@@ -3040,7 +3147,8 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
location: PaneTarget,
|
||||
active: boolean = false,
|
||||
subpath?: string,
|
||||
justCreated: boolean = false
|
||||
justCreated: boolean = false,
|
||||
popoutLocation?: {x?: number, y?: number, width?: number, height?: number},
|
||||
) {
|
||||
|
||||
const fnGetLeaf = ():WorkspaceLeaf => {
|
||||
@@ -3049,10 +3157,11 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
let leaf: WorkspaceLeaf;
|
||||
if(location === "popout-window") {
|
||||
leaf = app.workspace.openPopoutLeaf();
|
||||
//@ts-ignore (the api does not include x,y)
|
||||
leaf = this.app.workspace.openPopoutLeaf(popoutLocation);
|
||||
}
|
||||
if(location === "new-tab") {
|
||||
leaf = app.workspace.getLeaf('tab');
|
||||
leaf = this.app.workspace.getLeaf('tab');
|
||||
}
|
||||
if(!leaf) {
|
||||
leaf = this.app.workspace.getLeaf(false);
|
||||
@@ -3133,7 +3242,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
const textElements = excalidrawData.elements?.filter(
|
||||
(el: any) => el.type == "text",
|
||||
);
|
||||
let outString = `${MD_TEXTELEMENTS}\n`;
|
||||
let outString = `# Excalidraw Data\n## Text Elements\n`;
|
||||
let id: string;
|
||||
for (const te of textElements) {
|
||||
id = te.id;
|
||||
@@ -3212,6 +3321,12 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
} as ViewState,
|
||||
eState ? eState : { focus: true },
|
||||
);
|
||||
|
||||
const mdView = leaf.view;
|
||||
if(mdView instanceof MarkdownView) {
|
||||
foldExcalidrawSection(mdView);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async setExcalidrawView(leaf: WorkspaceLeaf) {
|
||||
|
||||
@@ -39,8 +39,6 @@ export class EmbeddableMenu {
|
||||
const ea = getEA(view) as ExcalidrawAutomate;
|
||||
ea.copyViewElementsToEAforEditing([element]);
|
||||
ea.getElement(element.id).link = link;
|
||||
//mutateElement (element,{link});
|
||||
//view.setDirty(99);
|
||||
view.excalidrawData.elementLinks.set(element.id, link);
|
||||
ea.addElementsToView(false, true, true);
|
||||
}
|
||||
@@ -105,6 +103,7 @@ export class EmbeddableMenu {
|
||||
if(!file) return;
|
||||
const isMD = file.extension==="md";
|
||||
const isExcalidrawFile = view.plugin.isExcalidrawFile(file);
|
||||
const isPDF = file.extension==="pdf";
|
||||
const { x, y } = sceneCoordsToViewportCoords( { sceneX: element.x, sceneY: element.y }, appState);
|
||||
const top = `${y-2.5*ROOTELEMENTSIZE-appState.offsetTop}px`;
|
||||
const left = `${x-appState.offsetLeft}px`;
|
||||
@@ -227,6 +226,19 @@ export class EmbeddableMenu {
|
||||
icon={ICONS.Properties}
|
||||
view={view}
|
||||
/>
|
||||
{isPDF && (
|
||||
<ActionButton
|
||||
key={"Crop"}
|
||||
title={t("CROP_PAGE")}
|
||||
action={() => {
|
||||
if(!element) return;
|
||||
//@ts-ignore
|
||||
view.app.commands.executeCommandById("obsidian-excalidraw-plugin:crop-image");
|
||||
}}
|
||||
icon={ICONS.Crop}
|
||||
view={view}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -381,7 +381,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
new Notice("Taskbone OCR is not enabled. Please go to plugins settings to enable it.",4000);
|
||||
return;
|
||||
}
|
||||
this.props.view.plugin.taskbone.getTextForView(this.props.view, isWinCTRLorMacCMD(e));
|
||||
this.props.view.plugin.taskbone.getTextForView(this.props.view, {forceReScan: isWinCTRLorMacCMD(e)});
|
||||
}}
|
||||
icon={ICONS.ocr}
|
||||
view={this.props.view}
|
||||
@@ -579,7 +579,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
title={t("CROP_IMAGE")}
|
||||
action={(e:React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
// @ts-ignore
|
||||
this.props.view.app.commands.executeCommandById("obsidian-excalidraw-plugin:crop-image")
|
||||
this.props.view.app.commands.executeCommandById("obsidian-excalidraw-plugin:crop-image");
|
||||
}}
|
||||
icon={ICONS.Crop}
|
||||
view={this.props.view}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { createPNG, ExcalidrawAutomate } from "../ExcalidrawAutomate";
|
||||
import { ExcalidrawAutomate, createPNG } from "../ExcalidrawAutomate";
|
||||
import {Notice, requestUrl} from "obsidian"
|
||||
import ExcalidrawPlugin from "../main"
|
||||
import {log} from "../utils/Utils"
|
||||
import ExcalidrawView, { ExportSettings } from "../ExcalidrawView"
|
||||
import FrontmatterEditor from "src/utils/Frontmatter";
|
||||
import { ExcalidrawElement, ExcalidrawImageElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ExcalidrawElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { EmbeddedFilesLoader } from "src/EmbeddedFileLoader";
|
||||
import { blobToBase64 } from "src/utils/FileUtils";
|
||||
import { getEA } from "src";
|
||||
import { log } from "src/utils/DebugHelper";
|
||||
|
||||
const TASKBONE_URL = "https://api.taskbone.com/"; //"https://excalidraw-preview.onrender.com/";
|
||||
const TASKBONE_OCR_FN = "execute?id=60f394af-85f6-40bc-9613-5d26dc283cbb";
|
||||
@@ -39,23 +40,9 @@ export default class Taskbone {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
public async getTextForView(view: ExcalidrawView, forceReScan: boolean) {
|
||||
await view.forceSave(true);
|
||||
const viewElements = view.excalidrawAPI.getSceneElements().filter((el:ExcalidrawElement) =>
|
||||
el.type==="freedraw" ||
|
||||
( el.type==="image" &&
|
||||
!this.plugin.isExcalidrawFile(view.excalidrawData.getFile(el.fileId)?.file)
|
||||
));
|
||||
if(viewElements.length === 0) {
|
||||
new Notice ("Aborting OCR because there are no image or freedraw elements on the canvas.",4000);
|
||||
return;
|
||||
}
|
||||
const fe = new FrontmatterEditor(view.data);
|
||||
if(fe.hasKey("taskbone-ocr") && !forceReScan) {
|
||||
new Notice ("The drawing has already been processed, you will find the result in the frontmatter in markdown view mode. If you ran the command from the Obsidian Panel in Excalidraw then you can CTRL(CMD)+click the command to force the rescaning.",4000)
|
||||
return;
|
||||
}
|
||||
const bb = this.plugin.ea.getBoundingBox(viewElements);
|
||||
public async getTextForElements(elements: ExcalidrawElement[], ea: ExcalidrawAutomate): Promise<string> {
|
||||
ea.copyViewElementsToEAforEditing(elements, true);
|
||||
const bb = ea.getBoundingBox(elements);
|
||||
const size = (bb.width*bb.height);
|
||||
const minRatio = Math.sqrt(360000/size);
|
||||
const maxRatio = Math.sqrt(size/16000000);
|
||||
@@ -79,26 +66,52 @@ export default class Taskbone {
|
||||
};
|
||||
|
||||
const img =
|
||||
await createPNG(
|
||||
view.file.path + "#^taskbone",
|
||||
await ea.createPNG(
|
||||
null,
|
||||
scale,
|
||||
exportSettings,
|
||||
loader,
|
||||
"light",
|
||||
null,
|
||||
null,
|
||||
[],
|
||||
this.plugin,
|
||||
0
|
||||
);
|
||||
)
|
||||
return await this.getTextForImage(img);
|
||||
}
|
||||
|
||||
const text = await this.getTextForImage(img);
|
||||
public async getTextForView(view: ExcalidrawView, {
|
||||
forceReScan,
|
||||
selectedElementsOnly = false,
|
||||
addToFrontmatter = true,
|
||||
}: {
|
||||
forceReScan: boolean,
|
||||
selectedElementsOnly?: boolean,
|
||||
addToFrontmatter?: boolean,
|
||||
}) {
|
||||
await view.forceSave(true);
|
||||
const ea = getEA(view) as ExcalidrawAutomate;
|
||||
const viewElements = (selectedElementsOnly ? ea.getViewSelectedElements() : ea.getViewElements())
|
||||
.filter((el:ExcalidrawElement) =>
|
||||
el.type==="freedraw" ||
|
||||
( el.type==="image" &&
|
||||
!this.plugin.isExcalidrawFile(view.excalidrawData.getFile(el.fileId)?.file)
|
||||
));
|
||||
if(viewElements.length === 0) {
|
||||
new Notice ("Aborting OCR because there are no image or freedraw elements on the canvas.",4000);
|
||||
return;
|
||||
}
|
||||
const fe = new FrontmatterEditor(view.data);
|
||||
if(addToFrontmatter && fe.hasKey("taskbone-ocr") && !forceReScan) {
|
||||
new Notice ("The drawing has already been processed, you will find the result in the frontmatter in markdown view mode. If you ran the command from the Obsidian Panel in Excalidraw then you can CTRL(CMD)+click the command to force the rescaning.",4000)
|
||||
return;
|
||||
}
|
||||
|
||||
const text = await this.getTextForElements(viewElements, ea);
|
||||
if(text) {
|
||||
fe.setKey("taskbone-ocr",text);
|
||||
view.data = fe.data;
|
||||
view.save(false);
|
||||
if(addToFrontmatter) {
|
||||
fe.setKey("taskbone-ocr",text);
|
||||
view.data = fe.data;
|
||||
view.save(false);
|
||||
}
|
||||
window.navigator.clipboard.writeText(text);
|
||||
new Notice("I placed the recognized in the drawing's frontmatter and onto the system clipboard.");
|
||||
new Notice(`I placed the recognized text onto the system clipboard${addToFrontmatter?" and to document properties":""}.`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,8 @@ export interface ExcalidrawSettings {
|
||||
templateFilePath: string;
|
||||
scriptFolderPath: string;
|
||||
compress: boolean;
|
||||
decompressForMDView: boolean;
|
||||
onceOffCompressFlagReset: boolean; //used to reset compress to true in 2.2.0
|
||||
autosave: boolean;
|
||||
autosaveInterval: number;
|
||||
autosaveIntervalDesktop: number;
|
||||
@@ -188,6 +190,7 @@ export interface ExcalidrawSettings {
|
||||
areaZoomLimit: number;
|
||||
longPressDesktop: number;
|
||||
longPressMobile: number;
|
||||
isDebugMode: boolean;
|
||||
}
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
@@ -199,7 +202,9 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
embedUseExcalidrawFolder: false,
|
||||
templateFilePath: "Excalidraw/Template.excalidraw",
|
||||
scriptFolderPath: "Excalidraw/Scripts",
|
||||
compress: false,
|
||||
compress: true,
|
||||
decompressForMDView: false,
|
||||
onceOffCompressFlagReset: false,
|
||||
autosave: true,
|
||||
autosaveInterval: 15000,
|
||||
autosaveIntervalDesktop: 15000,
|
||||
@@ -436,6 +441,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
areaZoomLimit: 1,
|
||||
longPressDesktop: 500,
|
||||
longPressMobile: 500,
|
||||
isDebugMode: false,
|
||||
};
|
||||
|
||||
export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
@@ -662,6 +668,18 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("DECOMPRESS_FOR_MD_NAME"))
|
||||
.setDesc(fragWithHTML(t("DECOMPRESS_FOR_MD_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.decompressForMDView)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.decompressForMDView = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("AUTOSAVE_INTERVAL_DESKTOP_NAME"))
|
||||
.setDesc(fragWithHTML(t("AUTOSAVE_INTERVAL_DESKTOP_DESC")))
|
||||
@@ -2461,6 +2479,19 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
cls: "excalidraw-setting-h1",
|
||||
});
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("DEBUGMODE_NAME"))
|
||||
.setDesc(fragWithHTML(t("DEBUGMODE_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.isDebugMode)
|
||||
.onChange((value) => {
|
||||
this.plugin.settings.isDebugMode = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("SLIDING_PANES_NAME"))
|
||||
.setDesc(fragWithHTML(t("SLIDING_PANES_DESC")))
|
||||
|
||||
@@ -4,37 +4,46 @@ import { createTreeWalker, walk } from "./walker";
|
||||
|
||||
export type ConversionResult = {
|
||||
hasErrors: boolean;
|
||||
errors: NodeListOf<Element> | null;
|
||||
errors: string;
|
||||
content: any; // Serialized Excalidraw JSON
|
||||
};
|
||||
|
||||
export const svgToExcalidraw = (svgString: string): ConversionResult => {
|
||||
const parser = new DOMParser();
|
||||
const svgDOM = parser.parseFromString(svgString, "image/svg+xml");
|
||||
try {
|
||||
const parser = new DOMParser();
|
||||
const svgDOM = parser.parseFromString(svgString, "image/svg+xml");
|
||||
|
||||
// was there a parsing error?
|
||||
const errorsElements = svgDOM.querySelectorAll("parsererror");
|
||||
const hasErrors = errorsElements.length > 0;
|
||||
let content = null;
|
||||
// was there a parsing error?
|
||||
const errorsElements = svgDOM.querySelectorAll("parsererror");
|
||||
const hasErrors = errorsElements.length > 0;
|
||||
let content = null;
|
||||
|
||||
if (hasErrors) {
|
||||
console.error(
|
||||
"There were errors while parsing the given SVG: ",
|
||||
[...errorsElements].map((el) => el.innerHTML),
|
||||
);
|
||||
} else {
|
||||
const tw = createTreeWalker(svgDOM);
|
||||
const scene = new ExcalidrawScene();
|
||||
const groups: Group[] = [];
|
||||
if (hasErrors) {
|
||||
console.error(
|
||||
"There were errors while parsing the given SVG: ",
|
||||
[...errorsElements].map((el) => el.innerHTML),
|
||||
);
|
||||
} else {
|
||||
const tw = createTreeWalker(svgDOM);
|
||||
const scene = new ExcalidrawScene();
|
||||
const groups: Group[] = [];
|
||||
|
||||
walk({ tw, scene, groups, root: svgDOM }, tw.nextNode());
|
||||
walk({ tw, scene, groups, root: svgDOM }, tw.nextNode());
|
||||
|
||||
content = scene.elements; //scene.toExJSON();
|
||||
content = scene.elements; //scene.toExJSON();
|
||||
}
|
||||
|
||||
return {
|
||||
hasErrors,
|
||||
errors: hasErrors ? `${[...errorsElements].map((el) => el.innerHTML)}` : "",
|
||||
content,
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return {
|
||||
hasErrors: true,
|
||||
errors: `${error}`,
|
||||
content:[],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
hasErrors,
|
||||
errors: hasErrors ? errorsElements : null,
|
||||
content,
|
||||
};
|
||||
};
|
||||
|
||||
18
src/types.d.ts
vendored
18
src/types.d.ts
vendored
@@ -58,9 +58,23 @@ declare module "obsidian" {
|
||||
},
|
||||
basePath: string;
|
||||
}
|
||||
interface Editor {
|
||||
insertText(data: string): void;
|
||||
interface FoldPosition {
|
||||
from: number;
|
||||
to: number;
|
||||
}
|
||||
|
||||
interface FoldInfo {
|
||||
folds: FoldPosition[];
|
||||
lines: number;
|
||||
}
|
||||
|
||||
interface MarkdownSubView {
|
||||
applyFoldInfo(foldInfo: FoldInfo): void;
|
||||
getFoldInfo(): FoldInfo | null;
|
||||
}
|
||||
/*interface Editor {
|
||||
insertText(data: string): void;
|
||||
}*/
|
||||
interface MetadataCache {
|
||||
getBacklinksForFile(file: TFile): any;
|
||||
getLinks(): { [id: string]: Array<{ link: string; displayText: string; original: string; position: any }> };
|
||||
|
||||
@@ -110,7 +110,7 @@ export class CanvasNodeFactory {
|
||||
}
|
||||
}
|
||||
};
|
||||
const observer = isDebugMode
|
||||
const observer = isDebugMode()
|
||||
? new CustomMutationObserver(nodeObserverFn, "CanvasNodeFactory")
|
||||
: new MutationObserver(nodeObserverFn);
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ export const carveOutPDF = async (sourceEA: ExcalidrawAutomate, embeddableEl: Ex
|
||||
|
||||
const targetEA = getEA(sourceEA.targetView) as ExcalidrawAutomate;
|
||||
|
||||
const {height, width} = embeddableEl;
|
||||
let {height, width} = embeddableEl;
|
||||
|
||||
if(!height || !width || height === 0 || width === 0) return;
|
||||
|
||||
@@ -77,8 +77,6 @@ export const carveOutPDF = async (sourceEA: ExcalidrawAutomate, embeddableEl: Ex
|
||||
const newImage = targetEA.getElement(imageId) as Mutable<ExcalidrawImageElement>;
|
||||
newImage.x = 0;
|
||||
newImage.y = 0;
|
||||
newImage.width = width;
|
||||
newImage.height = height;
|
||||
const angle = embeddableEl.angle;
|
||||
|
||||
const fname = pdfFile.basename;
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
export const isDebugMode = false;
|
||||
import { EXCALIDRAW_PLUGIN } from "src/constants/constants";
|
||||
|
||||
|
||||
export const durationTreshold = 0; //0.05; //ms
|
||||
export const isDebugMode = () => EXCALIDRAW_PLUGIN && EXCALIDRAW_PLUGIN.settings?.isDebugMode;
|
||||
|
||||
export const log = console.log.bind(window.console);
|
||||
export const debug = (...messages: unknown[]) => {
|
||||
if(isDebugMode()) {
|
||||
console.log(...messages);
|
||||
}
|
||||
};
|
||||
|
||||
export class CustomMutationObserver {
|
||||
private originalCallback: MutationCallback;
|
||||
|
||||
@@ -114,7 +114,8 @@ export const setDynamicStyle = (
|
||||
[`--h2-color`]: str(text),
|
||||
[`--h3-color`]: str(text),
|
||||
[`--h4-color`]: str(text),
|
||||
[`color`]: str(text),
|
||||
[`color`]: str(text),
|
||||
['--excalidraw-caret-color']: str(text),
|
||||
[`--select-highlight-color`]: str(gray1()),
|
||||
[`--color-gray-80`]: str(isDark?text.darkerBy(40):text.lighterBy(40)), //frame
|
||||
};
|
||||
|
||||
@@ -334,6 +334,7 @@ export const getPathWithoutExtension = (f:TFile): string => {
|
||||
const VAULT_BASE_URL = DEVICE.isDesktop
|
||||
? app.vault.adapter.url.pathToFileURL(app.vault.adapter.basePath).toString()
|
||||
: "";
|
||||
|
||||
export const getInternalLinkOrFileURLLink = (
|
||||
path: string, plugin:ExcalidrawPlugin, alias?: string, sourceFile?: TFile
|
||||
):{link: string, isInternal: boolean, file?: TFile, url?: string} => {
|
||||
@@ -345,7 +346,7 @@ export const getInternalLinkOrFileURLLink = (
|
||||
const vault = plugin.app.vault;
|
||||
const fileURLString = vault.adapter.url.pathToFileURL(path).toString();
|
||||
if (fileURLString.startsWith(VAULT_BASE_URL)) {
|
||||
const internalPath = normalizePath(fileURLString.substring(VAULT_BASE_URL.length));
|
||||
const internalPath = normalizePath(unescape(fileURLString.substring(VAULT_BASE_URL.length)));
|
||||
const file = vault.getAbstractFileByPath(internalPath);
|
||||
if(file && file instanceof TFile) {
|
||||
const link = plugin.app.metadataCache.fileToLinktext(
|
||||
@@ -440,3 +441,38 @@ export const fileShouldDefaultAsExcalidraw = (path:string, app:App):boolean => {
|
||||
cache.frontmatter[FRONTMATTER_KEYS["plugin"].name] &&
|
||||
!Boolean(cache.frontmatter[FRONTMATTER_KEYS["open-as-markdown"].name]);
|
||||
}
|
||||
|
||||
export const getExcalidrawEmbeddedFilesFiletree = (sourceFile: TFile, plugin: ExcalidrawPlugin):TFile[] => {
|
||||
if(!sourceFile || !plugin.isExcalidrawFile(sourceFile)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const fileList = new Set<TFile>();
|
||||
const app = plugin.app;
|
||||
|
||||
const addAttachedImages = (f:TFile) => Object
|
||||
.keys(app.metadataCache.resolvedLinks[f.path])
|
||||
.forEach(path => {
|
||||
const file = app.vault.getAbstractFileByPath(path);
|
||||
if (!file || !(file instanceof TFile)) return;
|
||||
const isExcalidraw = plugin.isExcalidrawFile(file);
|
||||
if (
|
||||
(file.extension === "md" && !isExcalidraw) ||
|
||||
fileList.has(file) //avoid infinite loops
|
||||
) {
|
||||
return;
|
||||
}
|
||||
fileList.add(file);
|
||||
if (isExcalidraw) {
|
||||
addAttachedImages(file);
|
||||
}
|
||||
});
|
||||
|
||||
addAttachedImages(sourceFile);
|
||||
return Array.from(fileList);
|
||||
}
|
||||
|
||||
export const hasExcalidrawEmbeddedImagesTreeChanged = (sourceFile: TFile, mtime:number, plugin: ExcalidrawPlugin):boolean => {
|
||||
const fileList = getExcalidrawEmbeddedFilesFiletree(sourceFile, plugin);
|
||||
return fileList.some(f=>f.stat.mtime > mtime);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { App, Notice, TFile } from "obsidian";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { convertSVGStringToElement } from "./Utils";
|
||||
import { FILENAMEPARTS, PreviewImageType } from "./UtilTypes";
|
||||
import { hasExcalidrawEmbeddedImagesTreeChanged } from "./FileUtils";
|
||||
|
||||
//@ts-ignore
|
||||
const DB_NAME = "Excalidraw " + app.appId;
|
||||
@@ -19,6 +20,7 @@ export type ImageKey = {
|
||||
isDark: boolean;
|
||||
previewImageType: PreviewImageType;
|
||||
scale: number;
|
||||
isTransparent: boolean;
|
||||
} & FILENAMEPARTS;
|
||||
|
||||
const getKey = (key: ImageKey): string =>
|
||||
@@ -29,7 +31,7 @@ const getKey = (key: ImageKey): string =>
|
||||
: key.previewImageType === PreviewImageType.PNG
|
||||
? 0
|
||||
: 2
|
||||
}#${key.scale}`; //key.isSVG ? 1 : 0
|
||||
}#${key.scale}${key.isTransparent?"#t":""}`; //key.isSVG ? 1 : 0
|
||||
|
||||
class ImageCache {
|
||||
private dbName: string;
|
||||
@@ -291,7 +293,10 @@ class ImageCache {
|
||||
|
||||
const file = this.app.vault.getAbstractFileByPath(key_.filepath.split("#")[0]);
|
||||
if (!file || !(file instanceof TFile)) return undefined;
|
||||
if (cachedData && cachedData.mtime === file.stat.mtime) {
|
||||
if (cachedData && cachedData.mtime >= file.stat.mtime) {
|
||||
if(hasExcalidrawEmbeddedImagesTreeChanged(file, cachedData.mtime, this.plugin)) {
|
||||
return undefined;
|
||||
}
|
||||
if(cachedData.svg) {
|
||||
return convertSVGStringToElement(cachedData.svg);
|
||||
}
|
||||
@@ -332,7 +337,7 @@ class ImageCache {
|
||||
} else {
|
||||
blob = image as Blob;
|
||||
}
|
||||
const data: FileCacheData = { mtime: file.stat.mtime, blob, svg};
|
||||
const data: FileCacheData = { mtime: Date.now(), blob, svg};
|
||||
const transaction = this.db.transaction(this.cacheStoreName, "readwrite");
|
||||
const store = transaction.objectStore(this.cacheStoreName);
|
||||
const key = getKey(key_);
|
||||
|
||||
@@ -37,7 +37,7 @@ export const modifierKeyTooltipMessages = ():ModifierKeyTooltipMessages => {
|
||||
// Add more messages for WebBrowserDragAction as needed
|
||||
},
|
||||
LocalFileDragAction: {
|
||||
"image-import": "Insert Image: import external or reuse existing if path in Vault",
|
||||
"image-import": "Import external file or reuse existing file if path is from the Vault",
|
||||
"image-url": `Insert Image: with local URI or internal-link if from Vault`,
|
||||
"link": "Insert Link: local URI or internal-link if from Vault",
|
||||
"embeddable": "Insert Interactive-Frame: local URI or internal-link if from Vault",
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import {
|
||||
App,
|
||||
Editor,
|
||||
FrontMatterCache,
|
||||
MarkdownView,
|
||||
normalizePath, OpenViewState, parseFrontMatterEntry, TFile, View, Workspace, WorkspaceLeaf, WorkspaceSplit
|
||||
} from "obsidian";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { checkAndCreateFolder, splitFolderAndFilename } from "./FileUtils";
|
||||
import { linkClickModifierType, ModifierKeys } from "./ModifierkeyHelper";
|
||||
import { REG_BLOCK_REF_CLEAN, REG_SECTION_REF_CLEAN } from "src/constants/constants";
|
||||
import yaml from "js-yaml";
|
||||
import yaml, { Mark } from "js-yaml";
|
||||
|
||||
export const getParentOfClass = (element: Element, cssClass: string):HTMLElement | null => {
|
||||
let parent = element.parentElement;
|
||||
@@ -29,11 +31,11 @@ export const getLeaf = (
|
||||
const newTab = ():WorkspaceLeaf => {
|
||||
if(!plugin.settings.openInMainWorkspace) return app.workspace.getLeaf('tab');
|
||||
const [leafLoc, mainLeavesIds] = getLeafLoc(origo);
|
||||
if(leafLoc === 'main') return app.workspace.getLeaf('tab');
|
||||
if(leafLoc === 'main') return plugin.app.workspace.getLeaf('tab');
|
||||
return getNewOrAdjacentLeaf(plugin,origo);
|
||||
}
|
||||
const newTabGroup = ():WorkspaceLeaf => getNewOrAdjacentLeaf(plugin,origo);
|
||||
const newWindow = ():WorkspaceLeaf => app.workspace.openPopoutLeaf();
|
||||
const newWindow = ():WorkspaceLeaf => plugin.app.workspace.openPopoutLeaf();
|
||||
|
||||
switch(linkClickModifierType(ev)) {
|
||||
case "active-pane": return origo;
|
||||
@@ -342,4 +344,51 @@ export const mergeMarkdownFiles = (template: string, target: string): string =>
|
||||
const mergedMarkdown = `---\n${mergedFrontmatterYaml}---\n${targetContent}\n\n${templateContent.trim()}\n`;
|
||||
|
||||
return mergedMarkdown;
|
||||
};
|
||||
};
|
||||
|
||||
export const editorInsertText = (editor: Editor, text: string)=> {
|
||||
const cursor = editor.getCursor();
|
||||
const line = editor.getLine(cursor.line);
|
||||
const updatedLine = line.slice(0, cursor.ch) + text + line.slice(cursor.ch);
|
||||
editor.setLine(cursor.line, updatedLine);
|
||||
}
|
||||
|
||||
export const foldExcalidrawSection = (view: MarkdownView) => {
|
||||
if (!view || !(view instanceof MarkdownView)) return;
|
||||
|
||||
const foldStart = {
|
||||
ed: -1, // # Excalidraw Data
|
||||
te: -1, // ## Text Elements
|
||||
el: -1, // ## Element Links
|
||||
ef: -1, // ## Embedded Files
|
||||
d: -1, // ## Drawing
|
||||
};
|
||||
|
||||
const existingFolds = view.currentMode.getFoldInfo()?.folds ?? [];
|
||||
const lineCount = view.editor.lineCount();
|
||||
|
||||
for (let i = 0; i < lineCount; i++) {
|
||||
const line = view.editor.getLine(i);
|
||||
switch (line) {
|
||||
case "# Excalidraw Data": foldStart.ed = i; break;
|
||||
case "## Text Elements": foldStart.te = i; break;
|
||||
case "## Element Links": foldStart.el = i; break;
|
||||
case "## Embedded Files": foldStart.ef = i; break;
|
||||
case "## Drawing": foldStart.d = i; break;
|
||||
}
|
||||
if (line === "## Drawing") break;
|
||||
}
|
||||
|
||||
if (foldStart.ed > -1 && foldStart.d > -1) {
|
||||
const foldPositions = [
|
||||
...existingFolds,
|
||||
...(foldStart.te > -1 ? [{ from: foldStart.te, to: (foldStart.el > -1 ? foldStart.el : (foldStart.ef > -1 ? foldStart.ef : foldStart.d)) - 1 }] : []),
|
||||
...(foldStart.el > -1 ? [{ from: foldStart.el, to: (foldStart.ef > -1 ? foldStart.ef : foldStart.d) - 1 }] : []),
|
||||
...(foldStart.ef > -1 ? [{ from: foldStart.ef, to: foldStart.d - 1 }] : []),
|
||||
{ from: foldStart.d, to: lineCount - 1 },
|
||||
{ from: foldStart.ed, to: lineCount - 1 },
|
||||
];
|
||||
|
||||
view.currentMode.applyFoldInfo({ folds: foldPositions, lines: lineCount });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { WorkspaceWindow } from "obsidian";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { getAllWindowDocuments } from "./ObsidianUtils";
|
||||
import { debug } from "./DebugHelper";
|
||||
|
||||
const STYLE_VARIABLES = ["--background-modifier-cover","--background-primary-alt","--background-secondary","--background-secondary-alt","--background-modifier-border","--text-normal","--text-muted","--text-accent","--text-accent-hover","--text-faint","--text-highlight-bg","--text-highlight-bg-active","--text-selection","--interactive-normal","--interactive-hover","--interactive-accent","--interactive-accent-hover","--scrollbar-bg","--scrollbar-thumb-bg","--scrollbar-active-thumb-bg"];
|
||||
const EXCALIDRAW_CONTAINER_CLASS = "excalidraw__embeddable__outer";
|
||||
@@ -16,6 +17,7 @@ export class StylesManager {
|
||||
constructor(plugin: ExcalidrawPlugin) {
|
||||
this.plugin = plugin;
|
||||
plugin.app.workspace.onLayoutReady(async () => {
|
||||
debug("StylesManager.constructor app.workspace.onLayoutReady");
|
||||
await this.harvestStyles();
|
||||
getAllWindowDocuments(plugin.app).forEach(doc => {
|
||||
this.copyPropertiesToTheme(doc);
|
||||
|
||||
@@ -21,20 +21,18 @@ import {
|
||||
EXCALIDRAW_PLUGIN,
|
||||
getCommonBoundingBox,
|
||||
DEVICE,
|
||||
getContainerElement,
|
||||
} from "../constants/constants";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { ExcalidrawElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ExcalidrawElement, ExcalidrawTextElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ExportSettings } from "../ExcalidrawView";
|
||||
import { getDataURLFromURL, getIMGFilename, getMimeType, getURLImageExtension } from "./FileUtils";
|
||||
import { generateEmbeddableLink } from "./CustomEmbeddableUtils";
|
||||
import ExcalidrawScene from "src/svgToExcalidraw/elements/ExcalidrawScene";
|
||||
import { FILENAMEPARTS } from "./UtilTypes";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import { cleanBlockRef, cleanSectionHeading, getFileCSSClasses } from "./ObsidianUtils";
|
||||
import { updateElementLinksToObsidianLinks } from "src/ExcalidrawAutomate";
|
||||
import { CropImage } from "./CropImage";
|
||||
import { ExcalidrawData } from "src/ExcalidrawData";
|
||||
import { ExcalidrawGenericElement } from "lib/svgToExcalidraw/types";
|
||||
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
@@ -296,7 +294,7 @@ export const getSVG = async (
|
||||
appState: {
|
||||
exportBackground: exportSettings.withBackground,
|
||||
exportWithDarkMode: exportSettings.withTheme
|
||||
? scene.appState?.theme != "light"
|
||||
? scene.appState?.theme !== "light"
|
||||
: false,
|
||||
...scene.appState,
|
||||
},
|
||||
@@ -348,7 +346,7 @@ export const getPNG = async (
|
||||
appState: {
|
||||
exportBackground: exportSettings.withBackground,
|
||||
exportWithDarkMode: exportSettings.withTheme
|
||||
? scene.appState?.theme != "light"
|
||||
? scene.appState?.theme !== "light"
|
||||
: false,
|
||||
...scene.appState,
|
||||
},
|
||||
@@ -395,13 +393,13 @@ export const embedFontsInSVG = (
|
||||
): SVGSVGElement => {
|
||||
//replace font references with base64 fonts)
|
||||
const includesVirgil = !localOnly &&
|
||||
svg.querySelector("text[font-family^='Virgil']") != null;
|
||||
svg.querySelector("text[font-family^='Virgil']") !== null;
|
||||
const includesCascadia = !localOnly &&
|
||||
svg.querySelector("text[font-family^='Cascadia']") != null;
|
||||
svg.querySelector("text[font-family^='Cascadia']") !== null;
|
||||
const includesAssistant = !localOnly &&
|
||||
svg.querySelector("text[font-family^='Assistant']") != null;
|
||||
svg.querySelector("text[font-family^='Assistant']") !== null;
|
||||
const includesLocalFont =
|
||||
svg.querySelector("text[font-family^='LocalFont']") != null;
|
||||
svg.querySelector("text[font-family^='LocalFont']") !== null;
|
||||
const defs = svg.querySelector("defs");
|
||||
if (defs && (includesCascadia || includesVirgil || includesLocalFont || includesAssistant)) {
|
||||
let style = defs.querySelector("style");
|
||||
@@ -470,7 +468,7 @@ export const scaleLoadedImage = (
|
||||
}
|
||||
if(f.shouldScale) {
|
||||
const elementAspectRatio = w_old / h_old;
|
||||
if (imageAspectRatio != elementAspectRatio) {
|
||||
if (imageAspectRatio !== elementAspectRatio) {
|
||||
dirty = true;
|
||||
const h_new = Math.sqrt((w_old * h_old * h_image) / w_image);
|
||||
const w_new = Math.sqrt((w_old * h_old * w_image) / h_image);
|
||||
@@ -546,7 +544,7 @@ export const getLinkParts = (fname: string, file?: TFile): LinkParts => {
|
||||
};
|
||||
|
||||
export const compress = (data: string): string => {
|
||||
return LZString.compressToBase64(data).replace(/(.{64})/g, "$1\n\n");
|
||||
return LZString.compressToBase64(data).replace(/(.{256})/g, "$1\n\n");
|
||||
};
|
||||
|
||||
export const decompress = (data: string): string => {
|
||||
@@ -561,7 +559,8 @@ export const isMaskFile = (
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["mask"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["mask"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["mask"].name] !== "undefined")
|
||||
) {
|
||||
return Boolean(fileCache.frontmatter[FRONTMATTER_KEYS["mask"].name]);
|
||||
}
|
||||
@@ -577,7 +576,8 @@ export const hasExportTheme = (
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-dark"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-dark"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["export-dark"].name] !== "undefined")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -594,7 +594,8 @@ export const getExportTheme = (
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-dark"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-dark"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["export-dark"].name] !== "undefined")
|
||||
) {
|
||||
return fileCache.frontmatter[FRONTMATTER_KEYS["export-dark"].name]
|
||||
? "dark"
|
||||
@@ -612,7 +613,8 @@ export const shouldEmbedScene = (
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-embed-scene"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-embed-scene"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["export-embed-scene"].name] !== "undefined")
|
||||
) {
|
||||
return fileCache.frontmatter[FRONTMATTER_KEYS["export-embed-scene"].name];
|
||||
}
|
||||
@@ -628,7 +630,8 @@ export const hasExportBackground = (
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-transparent"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-transparent"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["export-transparent"].name] !== "undefined")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -644,7 +647,8 @@ export const getWithBackground = (
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-transparent"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-transparent"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["export-transparent"].name] !== "undefined")
|
||||
) {
|
||||
return !fileCache.frontmatter[FRONTMATTER_KEYS["export-transparent"].name];
|
||||
}
|
||||
@@ -660,7 +664,10 @@ export const getExportPadding = (
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if(!fileCache?.frontmatter) return plugin.settings.exportPaddingSVG;
|
||||
|
||||
if (fileCache.frontmatter[FRONTMATTER_KEYS["export-padding"].name] != null) {
|
||||
if (
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-padding"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["export-padding"].name] !== "undefined")
|
||||
) {
|
||||
const val = parseInt(
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-padding"].name],
|
||||
);
|
||||
@@ -670,7 +677,10 @@ export const getExportPadding = (
|
||||
}
|
||||
|
||||
//deprecated. Retained for backward compatibility
|
||||
if (fileCache.frontmatter[FRONTMATTER_KEYS["export-svgpadding"].name] != null) {
|
||||
if (
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-svgpadding"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["export-svgpadding"].name] !== "undefined")
|
||||
) {
|
||||
const val = parseInt(
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-svgpadding"].name],
|
||||
);
|
||||
@@ -688,7 +698,8 @@ export const getPNGScale = (plugin: ExcalidrawPlugin, file: TFile): number => {
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-pngscale"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-pngscale"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["export-pngscale"].name] !== "undefined")
|
||||
) {
|
||||
const val = parseFloat(
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-pngscale"].name],
|
||||
@@ -764,26 +775,41 @@ export const sleep = async (ms: number) => new Promise((resolve) => setTimeout(r
|
||||
export const awaitNextAnimationFrame = async () => new Promise(requestAnimationFrame);
|
||||
*/
|
||||
|
||||
export const log = console.log.bind(window.console);
|
||||
export const debug = console.log.bind(window.console);
|
||||
//export const debug = function(){};
|
||||
|
||||
|
||||
export const getContainerElement = (
|
||||
export const _getContainerElement = (
|
||||
element:
|
||||
| (ExcalidrawElement & { containerId: ExcalidrawElement["id"] | null })
|
||||
| null,
|
||||
scene: any,
|
||||
) => {
|
||||
if (!element) {
|
||||
if (!element || !scene?.elements || element.type !== "text") {
|
||||
return null;
|
||||
}
|
||||
if (element.containerId) {
|
||||
return scene.elements.find((el:ExcalidrawElement)=>el.id === element.containerId) ?? null;
|
||||
return getContainerElement(element as ExcalidrawTextElement, arrayToMap(scene.elements))
|
||||
//return scene.elements.find((el:ExcalidrawElement)=>el.id === element.containerId) ?? null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Transforms array of objects containing `id` attribute,
|
||||
* or array of ids (strings), into a Map, keyd by `id`.
|
||||
*/
|
||||
export const arrayToMap = <T extends { id: string } | string>(
|
||||
items: readonly T[] | Map<string, T>,
|
||||
) => {
|
||||
if (items instanceof Map) {
|
||||
return items;
|
||||
}
|
||||
return items.reduce((acc: Map<string, T>, element) => {
|
||||
acc.set(typeof element === "string" ? element : element.id, element);
|
||||
return acc;
|
||||
}, new Map());
|
||||
};
|
||||
|
||||
export const updateFrontmatterInString = (data:string, keyValuePairs?: [string,string][]):string => {
|
||||
if(!data || !keyValuePairs) return data;
|
||||
for(const kvp of keyValuePairs) {
|
||||
@@ -868,20 +894,4 @@ export const addIframe = (containerEl: HTMLElement, link:string, startAt?: numbe
|
||||
sandbox: "allow-forms allow-presentation allow-same-origin allow-scripts allow-modals",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms array of objects containing `id` attribute,
|
||||
* or array of ids (strings), into a Map, keyd by `id`.
|
||||
*/
|
||||
export const arrayToMap = <T extends { id: string } | string>(
|
||||
items: readonly T[] | Map<string, T>,
|
||||
) => {
|
||||
if (items instanceof Map) {
|
||||
return items;
|
||||
}
|
||||
return items.reduce((acc: Map<string, T>, element) => {
|
||||
acc.set(typeof element === "string" ? element : element.id, element);
|
||||
return acc;
|
||||
}, new Map());
|
||||
};
|
||||
}
|
||||
15
styles.css
15
styles.css
@@ -579,4 +579,19 @@ img.excalidraw-cropped-pdfpage,
|
||||
|
||||
.ex-opacity-0 {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.popover .excalidraw-svg {
|
||||
width: 100%;
|
||||
max-width: inherit;
|
||||
height: 100%;
|
||||
max-height: inherit;
|
||||
}
|
||||
|
||||
root {
|
||||
--excalidraw-caret-color: initial;
|
||||
}
|
||||
|
||||
textarea.excalidraw-wysiwyg, .excalidraw input {
|
||||
caret-color: var(--excalidraw-caret-color);
|
||||
}
|
||||
Reference in New Issue
Block a user