mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
11 Commits
2.1.4
...
2.1.8.1-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26812dd297 | ||
|
|
ae4f4b4f08 | ||
|
|
4ac0a4c565 | ||
|
|
69c9f824a0 | ||
|
|
6a2220c960 | ||
|
|
aa501c2843 | ||
|
|
efce44f0a7 | ||
|
|
491eb83d35 | ||
|
|
35cf0802d1 | ||
|
|
f768548f60 | ||
|
|
37789f9907 |
12
.github/ISSUE_TEMPLATE/bug_report.md
vendored
12
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,12 +1,22 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help me improve Excalidraw
|
||||
about: When something is clearly broken. Everything else is a feature request.
|
||||
title: 'BUG: '
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Help me help you. I am a one man show doing this plugin as a part time hobby. There is no point in flooding me with issues, if there are too many, and they are poorly documented, I will just ignore them. Sorry...
|
||||
|
||||
Before creating a bug report, please
|
||||
1. review recent release notes - maybe there is already an answer,
|
||||
2. search issues (including closed ones) to see if there is anything similar.
|
||||
|
||||
⚠️ I will have to close all recorded bugs that do not provide this background information. Sorry, I need to control my workload/time. ⚠️
|
||||
|
||||
--------
|
||||
|
||||
**Your environment**
|
||||
Please run `Command Palette/Show Debug info` in Obsidian and paste the result here.
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.0.1-beta-2",
|
||||
"version": "2.1.8.1-beta-2",
|
||||
"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.4",
|
||||
"version": "2.1.8",
|
||||
"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-21",
|
||||
"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) {
|
||||
@@ -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;
|
||||
|
||||
@@ -35,10 +35,8 @@ 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,
|
||||
@@ -51,7 +49,6 @@ import {
|
||||
getSVG,
|
||||
isMaskFile,
|
||||
isVersionNewerThanOther,
|
||||
log,
|
||||
scaleLoadedImage,
|
||||
wrapTextAtCharLength,
|
||||
} from "src/utils/Utils";
|
||||
@@ -59,7 +56,7 @@ import { getAttachmentsFolderAndFilePath, getLeaf, getNewOrAdjacentLeaf, isObsid
|
||||
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()");
|
||||
@@ -601,6 +613,7 @@ export class ExcalidrawAutomate {
|
||||
"excalidraw-export-dark"?: boolean;
|
||||
"excalidraw-export-padding"?: number;
|
||||
"excalidraw-export-pngscale"?: number;
|
||||
"excalidraw-export-embed-scene"?: boolean;
|
||||
"excalidraw-default-mode"?: "view" | "zen";
|
||||
"excalidraw-onload-script"?: string;
|
||||
"excalidraw-linkbutton-opacity"?: number;
|
||||
@@ -705,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`;
|
||||
});
|
||||
@@ -716,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)=> {
|
||||
@@ -2628,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);
|
||||
@@ -2761,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,6 @@ import {
|
||||
ERROR_IFRAME_CONVERSION_CANCELED,
|
||||
JSON_parse,
|
||||
FRONTMATTER_KEYS,
|
||||
MD_TEXTELEMENTS,
|
||||
MD_DRAWING,
|
||||
MD_ELEMENTLINKS,
|
||||
} from "./constants/constants";
|
||||
import { _measureText } from "./ExcalidrawAutomate";
|
||||
import ExcalidrawPlugin from "./main";
|
||||
@@ -28,7 +25,6 @@ import { TextMode } from "./ExcalidrawView";
|
||||
import {
|
||||
addAppendUpdateCustomData,
|
||||
compress,
|
||||
debug,
|
||||
decompress,
|
||||
//getBakPath,
|
||||
getBinaryFileFromDataURL,
|
||||
@@ -51,6 +47,7 @@ import { BinaryFiles, DataURL, SceneData } from "@zsviczian/excalidraw/types/exc
|
||||
import { EmbeddedFile, MimeType } from "./EmbeddedFileLoader";
|
||||
import { ConfirmationPrompt } from "./dialogs/Prompt";
|
||||
import { getMermaidImageElements, getMermaidText, shouldRenderMermaid } from "./utils/MermaidUtils";
|
||||
import { debug } from "./utils/DebugHelper";
|
||||
|
||||
type SceneDataWithFiles = SceneData & { files: BinaryFiles };
|
||||
|
||||
@@ -117,12 +114,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 => {
|
||||
@@ -204,10 +201,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%%`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -240,30 +237,149 @@ const estimateMaxLineLen = (text: string, originalText: string): number => {
|
||||
const wrap = (text: string, lineLen: number) =>
|
||||
lineLen ? wrapTextAtCharLength(text, lineLen, false, 0) : text;
|
||||
|
||||
const RE_TEXTELEMENTS = new RegExp(`^(%%\n)?${MD_TEXTELEMENTS}(?:\n|$)`, "m");
|
||||
//WITHSECTION refers to back of the card note (see this.inputEl.onkeyup in SelectCard.ts)
|
||||
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 => {
|
||||
let trimLocation = data.search(RE_TEXTELEMENTS);
|
||||
//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
|
||||
*/
|
||||
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#
|
||||
%%
|
||||
# Text Elements
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
trimLocation = data.search(RE_TEXTELEMENTS_WITHSECTION_NOTOK);
|
||||
if(trimLocation > 0) {
|
||||
shouldFixTrailingHashtag = true;
|
||||
}
|
||||
}
|
||||
/* Expected markdown structure
|
||||
a)
|
||||
bla bla bla
|
||||
%%
|
||||
# Text Elements
|
||||
b)
|
||||
bla bla bla
|
||||
# Text Elements
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
trimLocation = data.search(RE_TEXTELEMENTS_NOSECTION_OK);
|
||||
}
|
||||
/* Expected markdown structure:
|
||||
bla bla bla%%
|
||||
# Text Elements
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
const res = data.match(RE_TEXTELEMENTS_FALLBACK_1);
|
||||
if(res && Boolean(res[1])) {
|
||||
trimLocation = res.index + res[1].length;
|
||||
}
|
||||
}
|
||||
/* Expected markdown structure:
|
||||
bla bla bla# Text Elements
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
const res = data.match(RE_TEXTELEMENTS_FALLBACK_2);
|
||||
if(res && Boolean(res[1])) {
|
||||
trimLocation = res.index + res[1].length;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Expected markdown structure:
|
||||
a)
|
||||
bla bla bla
|
||||
# Drawing
|
||||
b)
|
||||
bla bla bla
|
||||
%%
|
||||
# Drawing
|
||||
*/
|
||||
if (trimLocation === -1) {
|
||||
trimLocation = data.search(RE_DRAWING);
|
||||
}
|
||||
@@ -273,12 +389,14 @@ export const getExcalidrawMarkdownHeaderSection = (data:string, keys?:[string,st
|
||||
|
||||
let header = updateFrontmatterInString(data.substring(0, trimLocation),keys);
|
||||
//this should be removed at a later time. Left it here to remediate 1.4.9 mistake
|
||||
const REG_IMG = /(^---[\w\W]*?---\n)(!\[\[.*?]]\n(%%\n)?)/m; //(%%\n)? because of 1.4.8-beta... to be backward compatible with anyone who installed that version
|
||||
/*const REG_IMG = /(^---[\w\W]*?---\n)(!\[\[.*?]]\n(%%\n)?)/m; //(%%\n)? because of 1.4.8-beta... to be backward compatible with anyone who installed that version
|
||||
if (header.match(REG_IMG)) {
|
||||
header = header.replace(REG_IMG, "$1");
|
||||
}
|
||||
}*/
|
||||
//end of remove
|
||||
return header.endsWith("\n") ? header : (header + "\n");
|
||||
return shouldFixTrailingHashtag
|
||||
? header + "\n#\n"
|
||||
: header.endsWith("\n") ? header : (header + "\n");
|
||||
}
|
||||
|
||||
|
||||
@@ -580,28 +698,72 @@ export class ExcalidrawData {
|
||||
|
||||
data = data.substring(0, sceneJSONandPOS.pos);
|
||||
|
||||
//The Markdown # Text Elements take priority over the JSON text elements. Assuming the scenario in which the link was updated due to filename changes
|
||||
//The Markdown # Text Elements take priority over the JSON text elements. Assuming the scenario in which the
|
||||
//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);
|
||||
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
|
||||
// ....# Text Elements
|
||||
// ....
|
||||
// --------------
|
||||
// instead of
|
||||
// --------------
|
||||
// # back of note section
|
||||
// ....
|
||||
// # Text Elements
|
||||
position = data.search(RE_TEXTELEMENTS_FALLBACK_2);
|
||||
}
|
||||
if (position === -1) {
|
||||
await this.setTextMode(textMode, false);
|
||||
this.loaded = true;
|
||||
return true; //Text Elements header does not exist
|
||||
}
|
||||
const textElementsMatch = data.match(new RegExp(`^((%%\n)?${MD_TEXTELEMENTS}(?:\n|$))`, "m"))[0]
|
||||
position += textElementsMatch.length;
|
||||
|
||||
data = data.slice(position);
|
||||
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(/(.*##? Text Elements(?:\n|$))/m)[0];
|
||||
|
||||
data = data.slice(textElementsMatch.length);
|
||||
this.textElementCommentedOut = textElementsMatch.startsWith("%%\n");
|
||||
position = 0;
|
||||
let parts;
|
||||
|
||||
//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);
|
||||
@@ -672,11 +834,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);
|
||||
@@ -829,7 +995,7 @@ export class ExcalidrawData {
|
||||
container?.type,
|
||||
); //(await this.getText(te.id))??te.text serves the case when the whole #Text Elements section is deleted by accident
|
||||
} catch(e) {
|
||||
debug({where: "ExcalidrawData.updateSceneTextElements", fn: this.updateSceneTextElements, textElement: te});
|
||||
debug(`ExcalidrawData.updateSceneTextElements, textElement: ${te?.id}`, te, this.updateSceneTextElements);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1191,7 +1357,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
|
||||
@@ -1207,7 +1373,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`;
|
||||
}
|
||||
@@ -1220,7 +1386,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()) {
|
||||
@@ -1360,14 +1526,18 @@ export class ExcalidrawData {
|
||||
const processedIds = new Set<string>();
|
||||
fileIds.forEach((fileId,idx)=>{
|
||||
if(processedIds.has(fileId)) {
|
||||
const file = this.getFile(fileId);
|
||||
const embeddedFile = this.getFile(fileId);
|
||||
const equation = this.getEquation(fileId);
|
||||
const mermaid = this.getMermaid(fileId);
|
||||
|
||||
|
||||
|
||||
//images should have a single reference, but equations, and markdown embeds should have as many as instances of the file in the scene
|
||||
if(file && (file.isHyperLink || file.isLocalLink || (file.file && (file.file.extension !== "md" || this.plugin.isExcalidrawFile(file.file))))) {
|
||||
if (embeddedFile &&
|
||||
(embeddedFile.isHyperLink || embeddedFile.isLocalLink ||
|
||||
(embeddedFile.file &&
|
||||
(embeddedFile.file.extension !== "md" || this.plugin.isExcalidrawFile(embeddedFile.file))
|
||||
)
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if(mermaid) {
|
||||
@@ -1379,6 +1549,11 @@ export class ExcalidrawData {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!embeddedFile && !equation && !mermaid) {
|
||||
//processing freshly pasted images from likely anotehr instance of excalidraw (e.g. Excalidraw.com, or another Obsidian instance)
|
||||
return;
|
||||
}
|
||||
|
||||
const newId = fileid();
|
||||
(scene
|
||||
.elements
|
||||
@@ -1387,8 +1562,8 @@ export class ExcalidrawData {
|
||||
.fileId = newId;
|
||||
dirty = true;
|
||||
processedIds.add(newId);
|
||||
if(file) {
|
||||
this.setFile(newId as FileId,new EmbeddedFile(this.plugin,this.file.path,file.linkParts.original));
|
||||
if(embeddedFile) {
|
||||
this.setFile(newId as FileId,new EmbeddedFile(this.plugin,this.file.path,embeddedFile.linkParts.original));
|
||||
}
|
||||
if(equation) {
|
||||
this.setEquation(newId as FileId, {latex:equation.latex, isLoaded:false});
|
||||
|
||||
@@ -9,14 +9,12 @@ import {
|
||||
MarkdownView,
|
||||
request,
|
||||
requireApiVersion,
|
||||
requestUrl,
|
||||
} from "obsidian";
|
||||
//import * as React from "react";
|
||||
//import * as ReactDOM from "react-dom";
|
||||
//import Excalidraw from "@zsviczian/excalidraw";
|
||||
import {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawGenericElement,
|
||||
ExcalidrawImageElement,
|
||||
ExcalidrawTextElement,
|
||||
FileId,
|
||||
@@ -82,12 +80,10 @@ import {
|
||||
} from "./utils/FileUtils";
|
||||
import {
|
||||
checkExcalidrawVersion,
|
||||
debug,
|
||||
embedFontsInSVG,
|
||||
errorlog,
|
||||
getEmbeddedFilenameParts,
|
||||
getExportTheme,
|
||||
getLinkParts,
|
||||
getPNG,
|
||||
getPNGScale,
|
||||
getSVG,
|
||||
@@ -105,7 +101,7 @@ import {
|
||||
shouldEmbedScene,
|
||||
getContainerElement,
|
||||
} from "./utils/Utils";
|
||||
import { cleanSectionHeading, getLeaf, getParentOfClass, obsidianPDFQuoteWithRef, openLeaf } from "./utils/ObsidianUtils";
|
||||
import { cleanBlockRef, cleanSectionHeading, 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";
|
||||
@@ -136,11 +132,13 @@ 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";
|
||||
import { link } from "fs";
|
||||
|
||||
const EMBEDDABLE_SEMAPHORE_TIMEOUT = 2000;
|
||||
const PREVENT_RELOAD_TIMEOUT = 2000;
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
|
||||
@@ -345,6 +343,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;
|
||||
|
||||
@@ -394,7 +393,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
if (!this.excalidrawAPI || !this.file) {
|
||||
return;
|
||||
}
|
||||
if (this.app.isMobile) {
|
||||
if (DEVICE.isMobile) {
|
||||
const prompt = new Prompt(
|
||||
this.app,
|
||||
"Please provide filename",
|
||||
@@ -620,7 +619,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
public setPreventReload() {
|
||||
this.semaphores.preventReload = true;
|
||||
const self = this;
|
||||
this.preventReloadResetTimer = setTimeout(()=>self.semaphores.preventReload = false,2000);
|
||||
this.preventReloadResetTimer = setTimeout(()=>self.semaphores.preventReload = false,PREVENT_RELOAD_TIMEOUT);
|
||||
}
|
||||
|
||||
public clearPreventReloadTimer() {
|
||||
@@ -647,7 +646,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
public clearEmbeddableIsEditingSelf() {
|
||||
const self = this;
|
||||
this.clearEmbeddableIsEditingSelfTimer();
|
||||
this.editingSelfResetTimer = setTimeout(()=>self.semaphores.embeddableIsEditingSelf = false,2000);
|
||||
this.editingSelfResetTimer = setTimeout(()=>self.semaphores.embeddableIsEditingSelf = false,EMBEDDABLE_SEMAPHORE_TIMEOUT);
|
||||
}
|
||||
|
||||
async save(preventReload: boolean = true, forcesave: boolean = false) {
|
||||
@@ -681,30 +680,28 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
|
||||
try {
|
||||
const allowSave = Boolean (
|
||||
(this.semaphores.dirty !== null && this.semaphores.dirty) ||
|
||||
this.semaphores.autosaving ||
|
||||
forcesave
|
||||
); //dirty == false when view.file == null;
|
||||
const scene = this.getScene();
|
||||
|
||||
if (this.compatibilityMode) {
|
||||
await this.excalidrawData.syncElements(scene);
|
||||
} else if (
|
||||
await this.excalidrawData.syncElements(scene, this.excalidrawAPI.getAppState().selectedElementIds)
|
||||
&& !this.semaphores.popoutUnload //Obsidian going black after REACT 18 migration when closing last leaf on popout
|
||||
) {
|
||||
await this.loadDrawing(
|
||||
false,
|
||||
this.excalidrawAPI.getSceneElementsIncludingDeleted().filter((el:ExcalidrawElement)=>el.isDeleted)
|
||||
);
|
||||
}
|
||||
const allowSave = this.isDirty() || forcesave; //removed this.semaphores.autosaving
|
||||
debug({where: "ExcalidrawView.save", allowSave, isDirty: this.isDirty(), autosaving: this.semaphores.autosaving, forcesave});
|
||||
|
||||
if (allowSave) {
|
||||
const scene = this.getScene();
|
||||
|
||||
if (this.compatibilityMode) {
|
||||
await this.excalidrawData.syncElements(scene);
|
||||
} else if (
|
||||
await this.excalidrawData.syncElements(scene, this.excalidrawAPI.getAppState().selectedElementIds)
|
||||
&& !this.semaphores.popoutUnload //Obsidian going black after REACT 18 migration when closing last leaf on popout
|
||||
) {
|
||||
await this.loadDrawing(
|
||||
false,
|
||||
this.excalidrawAPI.getSceneElementsIncludingDeleted().filter((el:ExcalidrawElement)=>el.isDeleted)
|
||||
);
|
||||
}
|
||||
|
||||
//reload() is triggered indirectly when saving by the modifyEventHandler in main.ts
|
||||
//prevent reload is set here to override reload when not wanted: typically when the user is editing
|
||||
//and we do not want to interrupt the flow by reloading the drawing into the canvas.
|
||||
|
||||
this.clearDirty();
|
||||
this.clearPreventReloadTimer();
|
||||
|
||||
this.semaphores.preventReload = preventReload;
|
||||
@@ -717,7 +714,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
triggerReload = (this.lastSaveTimestamp === this.file.stat.mtime) &&
|
||||
!preventReload && forcesave;
|
||||
this.lastSaveTimestamp = this.file.stat.mtime;
|
||||
this.clearDirty();
|
||||
//this.clearDirty(); //moved to right after allow save, to avoid autosave collision with load drawing
|
||||
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/629
|
||||
//there were odd cases when preventReload semaphore did not get cleared and consequently a synchronized image
|
||||
@@ -803,7 +800,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(
|
||||
@@ -942,6 +939,46 @@ export default class ExcalidrawView extends TextFileView {
|
||||
return false;
|
||||
}
|
||||
|
||||
private getLinkTextForElement(
|
||||
selectedText:SelectedElementWithLink,
|
||||
selectedElementWithLink?:SelectedElementWithLink
|
||||
): {
|
||||
linkText: string,
|
||||
selectedElement: ExcalidrawElement,
|
||||
} {
|
||||
if (selectedText?.id || selectedElementWithLink?.id) {
|
||||
const selectedTextElement: ExcalidrawTextElement = selectedText.id
|
||||
? this.excalidrawAPI.getSceneElements().find((el:ExcalidrawElement)=>el.id === selectedText.id)
|
||||
: null;
|
||||
|
||||
const selectedElement = selectedElementWithLink.id
|
||||
? this.excalidrawAPI.getSceneElements().find((el:ExcalidrawElement)=>el.id === selectedElementWithLink.id)
|
||||
: null;
|
||||
|
||||
let linkText =
|
||||
selectedElementWithLink?.text ??
|
||||
(this.textMode === TextMode.parsed
|
||||
? this.excalidrawData.getRawText(selectedText.id)
|
||||
: selectedText.text);
|
||||
|
||||
const partsArray = REGEX_LINK.getResList(linkText);
|
||||
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()});
|
||||
if(container) {
|
||||
linkText = container.link;
|
||||
}
|
||||
}
|
||||
if(!linkText || partsArray.length === 0) {
|
||||
linkText = selectedTextElement?.link;
|
||||
}
|
||||
}
|
||||
return {linkText, selectedElement: selectedTextElement ?? selectedElement};
|
||||
}
|
||||
return {linkText: null, selectedElement: null};
|
||||
}
|
||||
|
||||
async linkClick(
|
||||
ev: MouseEvent | null,
|
||||
selectedText: SelectedElementWithLink,
|
||||
@@ -959,41 +996,16 @@ export default class ExcalidrawView extends TextFileView {
|
||||
|
||||
let file = null;
|
||||
let subpath: string = null;
|
||||
let linkText: string = null;
|
||||
|
||||
if (selectedText?.id || selectedElementWithLink?.id) {
|
||||
const selectedTextElement: ExcalidrawTextElement = selectedText.id
|
||||
? this.excalidrawAPI.getSceneElements().find((el:ExcalidrawElement)=>el.id === selectedText.id)
|
||||
: null;
|
||||
|
||||
linkText =
|
||||
selectedElementWithLink?.text ??
|
||||
(this.textMode === TextMode.parsed
|
||||
? this.excalidrawData.getRawText(selectedText.id)
|
||||
: selectedText.text);
|
||||
|
||||
const partsArray = REGEX_LINK.getResList(linkText);
|
||||
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()});
|
||||
if(container) {
|
||||
linkText = container.link;
|
||||
}
|
||||
}
|
||||
if(!linkText) {
|
||||
linkText = selectedTextElement?.link;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let {linkText, selectedElement} = this.getLinkTextForElement(selectedText, selectedElementWithLink);
|
||||
|
||||
//if (selectedText?.id || selectedElementWithLink?.id) {
|
||||
if (selectedElement) {
|
||||
if (!linkText) {
|
||||
return;
|
||||
}
|
||||
linkText = linkText.replaceAll("\n", ""); //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/187
|
||||
|
||||
if(this.handleLinkHookCall(selectedTextElement,linkText,ev)) return;
|
||||
if(this.handleLinkHookCall(selectedElement,linkText,ev)) return;
|
||||
if(openExternalLink(linkText, this.app)) return;
|
||||
|
||||
const result = await linkPrompt(linkText, this.app, this);
|
||||
@@ -1010,8 +1022,6 @@ export default class ExcalidrawView extends TextFileView {
|
||||
selectedImage.fileId,
|
||||
).latex;
|
||||
GenericInputPrompt.Prompt(this,this.plugin,this.app,t("ENTER_LATEX"),undefined,equation, undefined, 3).then(async (formula: string) => {
|
||||
// const prompt = new Prompt(this.app, t("ENTER_LATEX"), equation, "");
|
||||
// prompt.openAndGetValue(async (formula: string) => {
|
||||
if (!formula || formula === equation) {
|
||||
return;
|
||||
}
|
||||
@@ -1182,7 +1192,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
? null
|
||||
: this.getSelectedImageElement();
|
||||
const selectedElementWithLink =
|
||||
selectedImage?.id || selectedText?.id
|
||||
(selectedImage?.id || selectedText?.id)
|
||||
? null
|
||||
: this.getSelectedElementWithLink();
|
||||
this.linkClick(
|
||||
@@ -1247,8 +1257,8 @@ export default class ExcalidrawView extends TextFileView {
|
||||
onload() {
|
||||
const apiMissing = Boolean(typeof this.containerEl.onWindowMigrated === "undefined")
|
||||
//@ts-ignore
|
||||
if(!app.isMobile && !apiMissing) this.containerEl.onWindowMigrated(()=>this.leaf.rebuildView());
|
||||
const doc = app.isMobile?document:this.containerEl.ownerDocument;
|
||||
if(!DEVICE.isMobile && !apiMissing) this.containerEl.onWindowMigrated(()=>this.leaf.rebuildView());
|
||||
const doc = DEVICE.isMobile?document:this.containerEl.ownerDocument;
|
||||
this.ownerDocument = doc;
|
||||
this.ownerWindow = this.ownerDocument.defaultView;
|
||||
this.plugin.getPackage(this.ownerWindow);
|
||||
@@ -1293,8 +1303,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
|
||||
@@ -1310,7 +1325,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
};
|
||||
|
||||
self.onKeyDown = (e: KeyboardEvent) => {
|
||||
this.modifierKeyDown = {
|
||||
self.modifierKeyDown = {
|
||||
shiftKey: e.shiftKey,
|
||||
ctrlKey: e.ctrlKey,
|
||||
altKey: e.altKey,
|
||||
@@ -1336,7 +1351,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
const self = this;
|
||||
this.slidingPanesListner = () => {
|
||||
if (self.excalidrawAPI) {
|
||||
self.refresh();
|
||||
self.refreshCanvasOffset();
|
||||
}
|
||||
};
|
||||
let rootSplit = this.app.workspace.rootSplit as WorkspaceItem as WorkspaceItemExt;
|
||||
@@ -1380,13 +1395,13 @@ export default class ExcalidrawView extends TextFileView {
|
||||
const { offsetLeft, offsetTop } = target;
|
||||
if (offsetLeft !== self.offsetLeft || offsetTop != self.offsetTop) {
|
||||
if (self.excalidrawAPI) {
|
||||
self.refresh();
|
||||
self.refreshCanvasOffset();
|
||||
}
|
||||
self.offsetLeft = offsetLeft;
|
||||
self.offsetTop = offsetTop;
|
||||
}
|
||||
};
|
||||
this.parentMoveObserver = isDebugMode
|
||||
this.parentMoveObserver = this.plugin.settings.isDebugMode
|
||||
? new CustomMutationObserver(observerFn, "parentMoveObserver")
|
||||
: new MutationObserver(observerFn)
|
||||
|
||||
@@ -1474,19 +1489,19 @@ export default class ExcalidrawView extends TextFileView {
|
||||
return;
|
||||
}
|
||||
const st = api.getAppState();
|
||||
const editing = st.editingElement !== null;
|
||||
const isEditing = st.editingElement !== null;
|
||||
const isDragging = st.draggingElement !== null;
|
||||
//this will reset positioning of the cursor in case due to the popup keyboard,
|
||||
//or the command palette, or some other unexpected reason the onResize would not fire...
|
||||
this.refresh();
|
||||
this.refreshCanvasOffset();
|
||||
if (
|
||||
this.semaphores.dirty &&
|
||||
this.semaphores.dirty == this.file?.path &&
|
||||
this.isDirty() &&
|
||||
this.plugin.settings.autosave &&
|
||||
!this.semaphores.forceSaving &&
|
||||
!this.semaphores.autosaving &&
|
||||
!this.semaphores.embeddableIsEditingSelf &&
|
||||
!editing &&
|
||||
st.draggingElement === null //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/630
|
||||
!isEditing &&
|
||||
!isDragging //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/630
|
||||
) {
|
||||
//console.log("autosave");
|
||||
this.autosaveTimer = null;
|
||||
@@ -1513,14 +1528,22 @@ export default class ExcalidrawView extends TextFileView {
|
||||
};
|
||||
|
||||
this.autosaveFunction = timer;
|
||||
this.resetAutosaveTimer();
|
||||
}
|
||||
|
||||
|
||||
private resetAutosaveTimer() {
|
||||
if(!this.autosaveFunction) return;
|
||||
|
||||
if (this.autosaveTimer) {
|
||||
clearTimeout(this.autosaveTimer);
|
||||
this.autosaveTimer = null;
|
||||
} // clear previous timer if one exists
|
||||
this.autosaveTimer = setTimeout(
|
||||
timer,
|
||||
this.autosaveFunction,
|
||||
this.plugin.settings.autosaveInterval,
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
//save current drawing when user closes workspace leaf
|
||||
@@ -1683,10 +1706,26 @@ export default class ExcalidrawView extends TextFileView {
|
||||
) await sleep(50); //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/734
|
||||
}
|
||||
|
||||
const filenameParts = getEmbeddedFilenameParts(state.subpath);
|
||||
const filenameParts = getEmbeddedFilenameParts(
|
||||
(state.subpath && state.subpath.startsWith("#^group") && !state.subpath.startsWith("#^group="))
|
||||
? "#^group=" + state.subpath.substring(7)
|
||||
: (state.subpath && state.subpath.startsWith("#^area") && !state.subpath.startsWith("#^area="))
|
||||
? "#^area=" + state.subpath.substring(6)
|
||||
: state.subpath
|
||||
);
|
||||
if(filenameParts.hasBlockref) {
|
||||
setTimeout(async () => {
|
||||
await waitForExcalidraw();
|
||||
if(filenameParts.blockref && !filenameParts.hasGroupref) {
|
||||
if(!self.getScene()?.elements.find(el=>el.id === filenameParts.blockref)) {
|
||||
const cleanQuery = cleanSectionHeading(filenameParts.blockref).replaceAll(" ","");
|
||||
const blocks = await self.getBackOfTheNoteBlocks();
|
||||
if(blocks.includes(cleanQuery)) {
|
||||
this.setMarkdownView(state);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
setTimeout(()=>self.zoomToElementId(filenameParts.blockref, filenameParts.hasGroupref));
|
||||
});
|
||||
}
|
||||
@@ -1731,13 +1770,20 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
}
|
||||
|
||||
self.selectElementsMatchingQuery(
|
||||
if(!self.selectElementsMatchingQuery(
|
||||
elements,
|
||||
query,
|
||||
!api.getAppState().viewModeEnabled,
|
||||
filenameParts.hasSectionref,
|
||||
filenameParts.hasGroupref
|
||||
);
|
||||
)) {
|
||||
const cleanQuery = cleanSectionHeading(query[0]).replaceAll(" ","");
|
||||
const sections = await self.getBackOfTheNoteSections();
|
||||
if(sections.includes(cleanQuery)) {
|
||||
self.setMarkdownView(state);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1792,7 +1838,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) {
|
||||
@@ -1920,7 +1970,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;
|
||||
}
|
||||
@@ -1956,7 +2006,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
return false;
|
||||
})
|
||||
}
|
||||
},0
|
||||
},0,isThemeChange,
|
||||
);
|
||||
};
|
||||
if (!this.activeLoader) {
|
||||
@@ -2121,7 +2171,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
this.semaphores.preventReload = false;
|
||||
const penEnabled =
|
||||
this.plugin.settings.defaultPenMode === "always" ||
|
||||
(this.plugin.settings.defaultPenMode === "mobile" && app.isMobile);
|
||||
(this.plugin.settings.defaultPenMode === "mobile" && DEVICE.isMobile);
|
||||
const api = this.excalidrawAPI;
|
||||
if (api) {
|
||||
//isLoaded flags that a new file is being loaded, isLoaded will be true after loadDrawing completes
|
||||
@@ -2222,14 +2272,15 @@ export default class ExcalidrawView extends TextFileView {
|
||||
this.initializeToolsIconPanelAfterLoading();
|
||||
}
|
||||
|
||||
public setDirty(debug?:number) {
|
||||
if(isDebugMode) console.log(debug);
|
||||
public setDirty(location?:number) {
|
||||
if(this.semaphores.saving) return; //do not set dirty if saving
|
||||
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) {
|
||||
this.toolsPanelRef.current.setDirty(true);
|
||||
}
|
||||
if(!this.app.isMobile) {
|
||||
if(!DEVICE.isMobile) {
|
||||
if(requireApiVersion("0.16.0")) {
|
||||
//@ts-ignore
|
||||
this.leaf.tabHeaderInnerTitleEl.style.color="var(--color-accent)"
|
||||
@@ -2237,6 +2288,10 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
}
|
||||
|
||||
public isDirty() {
|
||||
return Boolean(this.semaphores.dirty) && (this.semaphores.dirty === this.file?.path);
|
||||
}
|
||||
|
||||
public clearDirty() {
|
||||
if(this.semaphores.viewunload) return;
|
||||
const api = this.excalidrawAPI;
|
||||
@@ -2252,7 +2307,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
this.previousSceneVersion = this.getSceneVersion(el);
|
||||
}
|
||||
this.diskIcon.querySelector("svg").removeClass("excalidraw-dirty");
|
||||
if(!app.isMobile) {
|
||||
if(!DEVICE.isMobile) {
|
||||
if(requireApiVersion("0.16.0")) {
|
||||
//@ts-ignore
|
||||
this.leaf.tabHeaderInnerTitleEl.style.color=""
|
||||
@@ -2302,17 +2357,17 @@ export default class ExcalidrawView extends TextFileView {
|
||||
return ICON_NAME;
|
||||
}
|
||||
|
||||
setMarkdownView() {
|
||||
setMarkdownView(eState?: any) {
|
||||
this.plugin.excalidrawFileModes[this.id || this.file.path] = "markdown";
|
||||
this.plugin.setMarkdownView(this.leaf);
|
||||
this.plugin.setMarkdownView(this.leaf, eState);
|
||||
}
|
||||
|
||||
public async openAsMarkdown() {
|
||||
if (this.plugin.settings.compress === true) {
|
||||
public async openAsMarkdown(eState?: any) {
|
||||
if (this.plugin.settings.compress && this.plugin.settings.decompressForMDView) {
|
||||
this.excalidrawData.disableCompression = true;
|
||||
await this.save(true, true);
|
||||
}
|
||||
this.setMarkdownView();
|
||||
this.setMarkdownView(eState);
|
||||
}
|
||||
|
||||
public async convertExcalidrawToMD() {
|
||||
@@ -2891,6 +2946,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
currentStrokeOptions: st.currentStrokeOptions,
|
||||
previousGridSize: st.previousGridSize,
|
||||
frameRendering: st.frameRendering,
|
||||
objectsSnapModeEnabled: st.objectsSnapModeEnabled,
|
||||
},
|
||||
prevTextMode: this.prevTextMode,
|
||||
files,
|
||||
@@ -2901,7 +2957,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
* ExcalidrawAPI refreshes canvas offsets
|
||||
* @returns
|
||||
*/
|
||||
private refresh() {
|
||||
private refreshCanvasOffset() {
|
||||
if(this.contentEl.clientWidth === 0 || this.contentEl.clientHeight === 0) return;
|
||||
const api = this.excalidrawAPI;
|
||||
if (!api) {
|
||||
@@ -2910,6 +2966,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", {
|
||||
@@ -3002,41 +3064,52 @@ export default class ExcalidrawView extends TextFileView {
|
||||
if (!linktext) {
|
||||
if(!this.currentPosition) return;
|
||||
linktext = "";
|
||||
const selectedElement = getTextElementAtPointer(this.currentPosition, this);
|
||||
if (!selectedElement || !selectedElement.text) {
|
||||
const selectedEl = getTextElementAtPointer(this.currentPosition, this);
|
||||
if (!selectedEl || !selectedEl.text) {
|
||||
const selectedImgElement =
|
||||
getImageElementAtPointer(this.currentPosition, this);
|
||||
const selectedElementWithLink = (selectedImgElement?.id || selectedImgElement?.id)
|
||||
? null
|
||||
: getElementWithLinkAtPointer(this.currentPosition, this);
|
||||
element = this.excalidrawAPI.getSceneElements().find((el:ExcalidrawElement)=>el.id === selectedImgElement.id);
|
||||
if (!selectedImgElement || !selectedImgElement.fileId) {
|
||||
if ((!selectedImgElement || !selectedImgElement.fileId) && !selectedElementWithLink?.id) {
|
||||
return;
|
||||
}
|
||||
if (!this.excalidrawData.hasFile(selectedImgElement.fileId)) {
|
||||
return;
|
||||
if (selectedImgElement?.id) {
|
||||
if (!this.excalidrawData.hasFile(selectedImgElement.fileId)) {
|
||||
return;
|
||||
}
|
||||
const ef = this.excalidrawData.getFile(selectedImgElement.fileId);
|
||||
if (
|
||||
(ef.isHyperLink || ef.isLocalLink) || //web images don't have a preview
|
||||
(IMAGE_TYPES.contains(ef.file.extension)) || //images don't have a preview
|
||||
(ef.file.extension.toLowerCase() === "pdf") || //pdfs don't have a preview
|
||||
(this.plugin.ea.isExcalidrawFile(ef.file))
|
||||
) {//excalidraw files don't have a preview
|
||||
linktext = getLinkTextFromLink(element.link);
|
||||
if(!linktext) return;
|
||||
} else {
|
||||
const ref = ef.linkParts.ref
|
||||
? `#${ef.linkParts.isBlockRef ? "^" : ""}${ef.linkParts.ref}`
|
||||
: "";
|
||||
linktext =
|
||||
ef.file.path + ref;
|
||||
}
|
||||
}
|
||||
const ef = this.excalidrawData.getFile(selectedImgElement.fileId);
|
||||
if (
|
||||
(ef.isHyperLink || ef.isLocalLink) || //web images don't have a preview
|
||||
(IMAGE_TYPES.contains(ef.file.extension)) || //images don't have a preview
|
||||
(ef.file.extension.toLowerCase() === "pdf") || //pdfs don't have a preview
|
||||
(this.plugin.ea.isExcalidrawFile(ef.file))
|
||||
) {//excalidraw files don't have a preview
|
||||
linktext = getLinkTextFromLink(element.link);
|
||||
if (selectedElementWithLink?.id) {
|
||||
linktext = getLinkTextFromLink(selectedElementWithLink.text);
|
||||
if(!linktext) return;
|
||||
} else {
|
||||
const ref = ef.linkParts.ref
|
||||
? `#${ef.linkParts.isBlockRef ? "^" : ""}${ef.linkParts.ref}`
|
||||
: "";
|
||||
linktext =
|
||||
ef.file.path + ref;
|
||||
}
|
||||
} else {
|
||||
element = this.excalidrawAPI.getSceneElements().filter((el:ExcalidrawElement)=>el.id === selectedElement.id)[0];
|
||||
const text: string =
|
||||
this.textMode === TextMode.parsed
|
||||
? this.excalidrawData.getRawText(selectedElement.id)
|
||||
: selectedElement.text;
|
||||
const {linkText, selectedElement} = this.getLinkTextForElement(selectedEl, selectedEl);
|
||||
element = selectedElement;
|
||||
/*this.excalidrawAPI.getSceneElements().filter((el:ExcalidrawElement)=>el.id === selectedElement.id)[0];
|
||||
const text: string =
|
||||
this.textMode === TextMode.parsed
|
||||
? this.excalidrawData.getRawText(selectedElement.id)
|
||||
: selectedElement.text;*/
|
||||
|
||||
linktext = getLinkTextFromLink(text);
|
||||
linktext = getLinkTextFromLink(linkText);
|
||||
if(!linktext) return;
|
||||
}
|
||||
}
|
||||
@@ -3381,7 +3454,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
if (data.elements) {
|
||||
const self = this;
|
||||
setTimeout(() => self.save(false), 300);
|
||||
setTimeout(() => self.save(), 300); //removed prevent reload = false, as reload was triggered when pasted containers were processed and there was a conflict with the new elements
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -3389,7 +3462,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));
|
||||
@@ -3810,7 +3883,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
|
||||
// 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);
|
||||
@@ -4052,6 +4125,19 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
}
|
||||
|
||||
private async getBackOfTheNoteSections() {
|
||||
return (await this.app.metadataCache.blockCache.getForFile({ isCancelled: () => false },this.file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "heading")
|
||||
.filter((b: any) => !MD_EX_SECTIONS.includes(b.display))
|
||||
.map((b: any) => cleanSectionHeading(b.display));
|
||||
}
|
||||
|
||||
private async getBackOfTheNoteBlocks() {
|
||||
return (await this.app.metadataCache.blockCache.getForFile({ isCancelled: () => false },this.file))
|
||||
.blocks.filter((b:any) => b.display && b.node && b.node.hasOwnProperty("type") && b.node.hasOwnProperty("id"))
|
||||
.map((b:any) => cleanBlockRef(b.node.id));
|
||||
}
|
||||
|
||||
public getSingleSelectedImage(): {imageEl: ExcalidrawImageElement, embeddedFile: EmbeddedFile} {
|
||||
if(!this.excalidrawAPI) return null;
|
||||
const els = this.getViewSelectedElements().filter(el=>el.type==="image");
|
||||
@@ -4064,13 +4150,9 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
|
||||
public async insertBackOfTheNoteCard() {
|
||||
const sections = (await this.app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },this.file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "heading")
|
||||
.filter((b: any) => !MD_EX_SECTIONS.includes(b.display))
|
||||
.map((b: any) => cleanSectionHeading(b.display));
|
||||
const sections = await this.getBackOfTheNoteSections();
|
||||
const selectCardDialog = new SelectCard(this.app,this,sections);
|
||||
selectCardDialog.start();
|
||||
selectCardDialog.start();
|
||||
}
|
||||
|
||||
public async convertImageElWithURLToLocalFile(data: {imageEl: ExcalidrawImageElement, embeddedFile: EmbeddedFile}) {
|
||||
@@ -4176,6 +4258,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
const onContextMenu = (elements: readonly ExcalidrawElement[], appState: AppState, onClose: (callback?: () => void) => void) => {
|
||||
const contextMenuActions = [];
|
||||
const api = this.excalidrawAPI as ExcalidrawImperativeAPI;
|
||||
const areElementsSelected = Object.keys(api.getAppState().selectedElementIds).length>0
|
||||
|
||||
if(this.isLinkSelected()) {
|
||||
contextMenuActions.push([
|
||||
@@ -4290,6 +4373,17 @@ export default class ExcalidrawView extends TextFileView {
|
||||
]);
|
||||
}
|
||||
|
||||
if(areElementsSelected) {
|
||||
contextMenuActions.push([
|
||||
renderContextMenuAction(
|
||||
t("COPY_ELEMENT_LINK"),
|
||||
() => {
|
||||
this.copyLinkToSelectedElementToClipboard("");
|
||||
},
|
||||
onClose
|
||||
),
|
||||
]);
|
||||
}
|
||||
contextMenuActions.push([
|
||||
renderContextMenuAction(
|
||||
t("INSERT_CARD"),
|
||||
@@ -4462,17 +4556,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"),
|
||||
};
|
||||
@@ -4481,7 +4575,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"),
|
||||
};
|
||||
@@ -4619,7 +4713,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
|
||||
@@ -4630,8 +4724,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;
|
||||
@@ -4670,7 +4768,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
this.toolsPanelRef.current.updatePosition();
|
||||
}
|
||||
if(this.ownerDocument !== document) {
|
||||
this.refresh(); //because resizeobserver in Excalidraw does not seem to work when in Obsidian Window
|
||||
this.refreshCanvasOffset(); //because resizeobserver in Excalidraw does not seem to work when in Obsidian Window
|
||||
}
|
||||
} catch (err) {
|
||||
errorlog({
|
||||
@@ -4836,7 +4934,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
const maxZoom = this.plugin.settings.zoomToFitMaxLevel;
|
||||
const elements = api.getSceneElements().filter((el:ExcalidrawElement)=>el.width<10000 && el.height<10000);
|
||||
if((app.isMobile && elements.length>1000) || elements.length>2500) {
|
||||
if((DEVICE.isMobile && elements.length>1000) || elements.length>2500) {
|
||||
if(justLoaded) api.scrollToContent();
|
||||
return;
|
||||
}
|
||||
@@ -4909,13 +5007,23 @@ export default class ExcalidrawView extends TextFileView {
|
||||
this.plugin.saveSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param elements
|
||||
* @param query
|
||||
* @param selectResult
|
||||
* @param exactMatch
|
||||
* @param selectGroup
|
||||
* @returns true if element found, false if no element is found.
|
||||
*/
|
||||
|
||||
public selectElementsMatchingQuery(
|
||||
elements: ExcalidrawElement[],
|
||||
query: string[],
|
||||
selectResult: boolean = true,
|
||||
exactMatch: boolean = false, //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/530
|
||||
selectGroup: boolean = false,
|
||||
) {
|
||||
):boolean {
|
||||
let match = getTextElementsMatchingQuery(
|
||||
elements.filter((el: ExcalidrawElement) => el.type === "text"),
|
||||
query,
|
||||
@@ -4928,7 +5036,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
|
||||
if (match.length === 0) {
|
||||
new Notice("I could not find a matching text element");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if(selectGroup) {
|
||||
@@ -4939,6 +5047,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
|
||||
this.zoomToElements(selectResult,match);
|
||||
return true;
|
||||
}
|
||||
|
||||
public zoomToElements(
|
||||
|
||||
@@ -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, {
|
||||
@@ -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,12 @@ 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_DRAWING = "## Drawing";
|
||||
const MD_ELEMENTLINKS = "## Element Links";
|
||||
const MD_EMBEDFILES = "## Embedded Files";
|
||||
export const MD_EX_SECTIONS = [MD_EXCALIDRAW, MD_TEXTELEMENTS, MD_DRAWING, MD_ELEMENTLINKS, MD_EMBEDFILES];
|
||||
|
||||
export const ERROR_IFRAME_CONVERSION_CANCELED = "iframe conversion canceled";
|
||||
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
|
||||
@@ -17,6 +17,90 @@ I develop this plugin as a hobby, spending my free time doing this. If you find
|
||||
|
||||
<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.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
|
||||
- Scaling of LaTeX formulas when the formula is changed
|
||||
- If the back of the note card only contains a block embed ${String.fromCharCode(96)}![[embed]]${String.fromCharCode(96)} this got removed when saving the Excalidraw file. This issue has been present since November, 2021 (v1.4.9).
|
||||
`,
|
||||
"2.1.5":`
|
||||
## New
|
||||
- Save "Snap to objects" with the scene state. If this is the only change you make to the scene, force save it using CTRL+S (note, use CTRL on Mac as well).
|
||||
- Added "Copy markdown link" to the context menu.
|
||||
|
||||
## Fixed
|
||||
- Paste operation occasionally duplicated text elements.
|
||||
- Pasting multiple instances of the same image from excalidraw.com or another instance of Obsidian, or pasting an image from anywhere and making copies with ALT/OPT + drag immediately after pasting (before autosave triggered) led to broken images when reopening the drawing.
|
||||
- CTRL/CMD+Click on a Text Element with an element link did not work (previously, you had to click the top right link indicator). Now, you can click anywhere on the element.
|
||||
- Hover preview for elements with a link only worked when hovering over the element link. Now, you can hover anywhere. If there are multiple elements with links, the top-level element will take precedence.
|
||||
- Link navigation within drawing when the "Focus on Existing Tab" feature is enabled under "Links, transclusion and TODOs" in settings works again.
|
||||
- If a link points to a back-of-the-card section or block the drawing will automatically switch to markdown view mode and navigate to the block or section.
|
||||
- DynamicSytle, dark mode when canvas background is set to transparent.
|
||||
- Scale to maintain the aspect ratio of a markdown notes embedded as images.
|
||||
- You can now borrow interactive markdown embeds to tables, blockquotes, list elements and callouts - not just paragraphs.
|
||||
- Back of the drawing cards:
|
||||
- Leaving the Section Name empty when creating the first back of the card note resulted in an error.
|
||||
- If you add the markdown comment (${String.fromCharCode(96)}%%${String.fromCharCode(96)}) directly before ${String.fromCharCode(96)}# Text Elements${String.fromCharCode(96)}, a trailing ${String.fromCharCode(96)}#${String.fromCharCode(96)} will be added to your document, when adding a back of the card note. This is to hide the markdown comment from the card. The trailing (empty) ${String.fromCharCode(96)}#${String.fromCharCode(96)} will not be visible in reading mode, pdf exports, and when publishing with Obsidian Publish.
|
||||
Here's a sample markdown structure of your document:
|
||||
|
||||
${String.fromCharCode(96,96,96)}markdown
|
||||
---
|
||||
excalidraw-plugin: parsed
|
||||
---
|
||||
# Your back of the card section
|
||||
bla bla bla
|
||||
|
||||
#
|
||||
%%
|
||||
# Text Elements
|
||||
... the rest of the Excalidraw file
|
||||
${String.fromCharCode(96,96,96)}
|
||||
`,
|
||||
"2.1.4":`
|
||||
## Fixed
|
||||
- Fixed the **aspect ratio** of an Excalidraw embedded within another Excalidraw **not updating**. [#1707](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1707)
|
||||
@@ -33,7 +117,7 @@ I develop this plugin as a hobby, spending my free time doing this. If you find
|
||||
- **Enhanced annotation and cropping** of images in Markdown documents:
|
||||
- Newly embedded **links will now follow the style of the original link**. If the original format was a ${String.fromCharCode(96)}${String.fromCharCode(96)}, the annotated file will follow this format. For ${String.fromCharCode(96)}[[wiki links]]${String.fromCharCode(96)}, it will follow that style. Additionally, if an alias was specified like ${String.fromCharCode(96)}[[link|alias]]${String.fromCharCode(96)}, the annotated or cropped image will retain the alias.
|
||||
- Introduced a new setting under "Saving" titled **"Preserve image size when annotating"**. This setting is disabled by default. When enabled, the embed link replacing the annotated image will maintain the size of the original image.
|
||||
- Option to **automaticaly embed the scene in exported PNG and SVG image files**. Including the scene will allow users to open the picture on Excalidraw.com or in another Obsidian Vault as an editable Excalidraw file.New setting is under the Export category. The new frontmatter tag is: ${String.fromCharCode(96)}excalidraw-export-embed-scene: true/false${String.fromCharCode(96)}.
|
||||
- Option to **automatically embed the scene in exported PNG and SVG image files**. Including the scene will allow users to open the picture on Excalidraw.com or in another Obsidian Vault as an editable Excalidraw file.New setting is under the Export category. The new frontmatter tag is: ${String.fromCharCode(96)}excalidraw-export-embed-scene: true/false${String.fromCharCode(96)}.
|
||||
`,
|
||||
"2.1.3":`
|
||||
This is a republish of 2.1.2 with a minor change. Sorry about the frequent releases. I will hold back for a few weeks now.
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -5,7 +5,6 @@ import { getEA } from "src";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { getExcalidrawMarkdownHeaderSection } from "src/ExcalidrawData";
|
||||
import { MD_EX_SECTIONS } from "src/constants/constants";
|
||||
import { ExcalidrawImageElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { cleanSectionHeading } from "src/utils/ObsidianUtils";
|
||||
|
||||
@@ -29,7 +28,7 @@ export class SelectCard extends FuzzySuggestModal<string> {
|
||||
if (e.key == "Enter") {
|
||||
if (this.containerEl.innerText.includes(t("EMPTY_SECTION_MESSAGE"))) {
|
||||
const item = this.inputEl.value;
|
||||
if(MD_EX_SECTIONS.includes(item)) {
|
||||
if(item === "" || MD_EX_SECTIONS.includes(item)) {
|
||||
new Notice(t("INVALID_SECTION_NAME"));
|
||||
this.close();
|
||||
return;
|
||||
@@ -37,7 +36,13 @@ export class SelectCard extends FuzzySuggestModal<string> {
|
||||
(async () => {
|
||||
const data = view.data;
|
||||
const header = getExcalidrawMarkdownHeaderSection(data);
|
||||
view.data = data.replace(header, header + `\n# ${item}\n\n`);
|
||||
const body = data.split(header)[1];
|
||||
const shouldAddHashtag = body && body.startsWith("%%");
|
||||
const shouldRemoveTrailingHashtag = header.endsWith("#\n");
|
||||
view.data = data.replace(
|
||||
header,
|
||||
(shouldRemoveTrailingHashtag ? header.substring(0,header.length-2) : header) +
|
||||
`\n# ${item}\n\n${shouldAddHashtag ? "#\n" : ""}`);
|
||||
await view.forceSave(true);
|
||||
let watchdog = 0;
|
||||
await sleep(200);
|
||||
|
||||
@@ -197,6 +197,7 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
' "excalidraw-export-dark"?: boolean;\n' +
|
||||
' "excalidraw-export-padding"?: number;\n' +
|
||||
' "excalidraw-export-pngscale"?: number;\n' +
|
||||
' "excalidraw-export-embed-scene"?: boolean;\n' +
|
||||
' "excalidraw-default-mode"?: "view" | "zen";\n' +
|
||||
' "excalidraw-onload-script"?: string;\n' +
|
||||
' "excalidraw-linkbutton-opacity"?: number;\n' +
|
||||
@@ -570,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>",
|
||||
@@ -821,6 +829,12 @@ export const FRONTMATTER_KEYS_INFO: SuggesterInfo[] = [
|
||||
desc: "If this key is present it will override the default excalidraw embed and export setting. This only affects export to PNG. Specify the export scale for the image. The typical range is between 0.5 and 5, but you can experiment with other values as well.",
|
||||
after: ": 1",
|
||||
},
|
||||
{
|
||||
field: "excalidraw-export-embed-scene",
|
||||
code: null,
|
||||
desc: "If this key is present it will override the default excalidraw embed and export setting.",
|
||||
after: ": false",
|
||||
},
|
||||
{
|
||||
field: "open-md",
|
||||
code: null,
|
||||
|
||||
@@ -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",
|
||||
@@ -48,6 +50,7 @@ export default {
|
||||
NEW_IN_POPOUT_WINDOW_EMBED: "Create new drawing - IN A POPOUT WINDOW - and embed into active document",
|
||||
TOGGLE_LOCK: "Toggle Text Element between edit RAW and PREVIEW",
|
||||
DELETE_FILE: "Delete selected image or Markdown file from Obsidian Vault",
|
||||
COPY_ELEMENT_LINK: "Copy markdown link for selected element(s)",
|
||||
INSERT_LINK_TO_ELEMENT:
|
||||
`Copy markdown link for selected element to clipboard. ${labelCTRL()}+CLICK to copy 'group=' link. ${labelSHIFT()}+CLICK to copy an 'area=' link. ${labelALT()}+CLICK to watch a help video.`,
|
||||
INSERT_LINK_TO_ELEMENT_GROUP:
|
||||
@@ -73,7 +76,9 @@ 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_IMAGE: "Crop and mask image",
|
||||
@@ -214,6 +219,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. " +
|
||||
@@ -562,6 +575,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>" +
|
||||
@@ -684,7 +700,7 @@ FILENAME_HEAD: "Filename",
|
||||
SELECT_SECTION_OR_TYPE_NEW:
|
||||
"Select existing section or type name of a new section then press Enter.",
|
||||
INVALID_SECTION_NAME: "Invalid section name.",
|
||||
EMPTY_SECTION_MESSAGE: "Hit enter to create a new Section",
|
||||
EMPTY_SECTION_MESSAGE: "Type the Section Name and hit enter to create a new Section",
|
||||
|
||||
//EmbeddedFileLoader.ts
|
||||
INFINITE_LOOP_WARNING:
|
||||
|
||||
212
src/main.ts
212
src/main.ts
@@ -41,8 +41,8 @@ import {
|
||||
EXPORT_IMG_ICON,
|
||||
LOCALE,
|
||||
IMAGE_TYPES,
|
||||
MD_TEXTELEMENTS,
|
||||
setExcalidrawPlugin
|
||||
setExcalidrawPlugin,
|
||||
DEVICE
|
||||
} from "./constants/constants";
|
||||
import {
|
||||
VIRGIL_FONT,
|
||||
@@ -95,7 +95,6 @@ import {
|
||||
import {
|
||||
getFontDataURL,
|
||||
errorlog,
|
||||
log,
|
||||
setLeftHandedMode,
|
||||
sleep,
|
||||
isVersionNewerThanOther,
|
||||
@@ -104,7 +103,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 {
|
||||
@@ -132,10 +131,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;
|
||||
@@ -185,6 +185,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);
|
||||
@@ -228,7 +231,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
|
||||
public registerEvent(event: any) {
|
||||
if(!isDebugMode) {
|
||||
if(!this.settings.isDebugMode) {
|
||||
super.registerEvent(event);
|
||||
return;
|
||||
}
|
||||
@@ -319,6 +322,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);
|
||||
});
|
||||
@@ -328,6 +332,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];
|
||||
@@ -338,6 +343,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,
|
||||
@@ -381,7 +387,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
private getOpenObsidianDocuments(): Document[] {
|
||||
const visitedDocs = new Set<Document>();
|
||||
this.app.workspace.iterateAllLeaves((leaf)=>{
|
||||
const ownerDocument = this.app.isMobile?document:leaf.view.containerEl.ownerDocument;
|
||||
const ownerDocument = DEVICE.isMobile?document:leaf.view.containerEl.ownerDocument;
|
||||
if(!ownerDocument) return;
|
||||
if(visitedDocs.has(ownerDocument)) return;
|
||||
visitedDocs.add(ownerDocument);
|
||||
@@ -392,16 +398,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -619,16 +626,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();
|
||||
}
|
||||
});
|
||||
@@ -671,7 +681,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
});
|
||||
};
|
||||
|
||||
this.themeObserver = isDebugMode
|
||||
this.themeObserver = this.settings.isDebugMode
|
||||
? new CustomMutationObserver(themeObserverFn, "themeObserver")
|
||||
: new MutationObserver(themeObserverFn);
|
||||
|
||||
@@ -737,12 +747,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) {
|
||||
@@ -867,9 +878,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]);
|
||||
@@ -1087,7 +1098,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
name: t("NEW_IN_POPOUT_WINDOW"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
return !app.isMobile
|
||||
return !DEVICE.isMobile;
|
||||
}
|
||||
this.createAndOpenDrawing(getDrawingFilename(this.settings), "popout-window");
|
||||
},
|
||||
@@ -1159,7 +1170,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
name: t("NEW_IN_POPOUT_WINDOW_EMBED"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
return !app.isMobile && Boolean(this.app.workspace.getActiveViewOfType(MarkdownView));
|
||||
return !DEVICE.isMobile && Boolean(this.app.workspace.getActiveViewOfType(MarkdownView));
|
||||
}
|
||||
insertDrawingToDoc("popout-window");
|
||||
return true;
|
||||
@@ -1181,7 +1192,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;
|
||||
@@ -1530,6 +1585,31 @@ 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;
|
||||
this.openDrawing(ef.file, DEVICE.isMobile ? "new-tab":"popout-window", true);
|
||||
}
|
||||
})
|
||||
|
||||
this.addCommand({
|
||||
id: "reset-image-to-100",
|
||||
name: t("RESET_IMG_TO_100"),
|
||||
@@ -2226,12 +2306,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;
|
||||
@@ -2331,28 +2412,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]);
|
||||
@@ -2368,6 +2459,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`;
|
||||
@@ -2390,6 +2482,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,
|
||||
@@ -2418,16 +2511,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) {
|
||||
@@ -2580,14 +2673,14 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
//!Temporary hack
|
||||
//https://discord.com/channels/686053708261228577/817515900349448202/1031101635784613968
|
||||
if (this.app.isMobile && newActiveviewEV && !previouslyActiveEV) {
|
||||
if (DEVICE.isMobile && newActiveviewEV && !previouslyActiveEV) {
|
||||
const navbar = document.querySelector("body>.app-container>.mobile-navbar");
|
||||
if(navbar && navbar instanceof HTMLDivElement) {
|
||||
navbar.style.position="relative";
|
||||
}
|
||||
}
|
||||
|
||||
if (this.app.isMobile && !newActiveviewEV && previouslyActiveEV) {
|
||||
if (DEVICE.isMobile && !newActiveviewEV && previouslyActiveEV) {
|
||||
const navbar = document.querySelector("body>.app-container>.mobile-navbar");
|
||||
if(navbar && navbar instanceof HTMLDivElement) {
|
||||
navbar.style.position="";
|
||||
@@ -2656,7 +2749,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);
|
||||
@@ -2686,8 +2779,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
}
|
||||
};
|
||||
this.activeLeafChangeEventHandler = activeLeafChangeEventHandler;
|
||||
self.registerEvent(
|
||||
app.workspace.on(
|
||||
this.app.workspace.on(
|
||||
"active-leaf-change",
|
||||
activeLeafChangeEventHandler,
|
||||
),
|
||||
@@ -2695,7 +2789,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;
|
||||
@@ -2704,7 +2798,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,
|
||||
);
|
||||
}
|
||||
@@ -2777,14 +2871,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(
|
||||
@@ -2818,7 +2912,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;
|
||||
@@ -2991,7 +3085,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
if(opts.applyLefthandedMode) setLeftHandedMode(this.settings.isLeftHanded);
|
||||
if(opts.reEnableAutosave) this.settings.autosave = true;
|
||||
this.settings.autosaveInterval = app.isMobile
|
||||
this.settings.autosaveInterval = DEVICE.isMobile
|
||||
? this.settings.autosaveIntervalMobile
|
||||
: this.settings.autosaveIntervalDesktop;
|
||||
}
|
||||
@@ -3018,7 +3112,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
public triggerEmbedUpdates(filepath?: string) {
|
||||
const visitedDocs = new Set<Document>();
|
||||
app.workspace.iterateAllLeaves((leaf)=>{
|
||||
const ownerDocument = app.isMobile?document:leaf.view.containerEl.ownerDocument;
|
||||
const ownerDocument = DEVICE.isMobile?document:leaf.view.containerEl.ownerDocument;
|
||||
if(!ownerDocument) return;
|
||||
if(visitedDocs.has(ownerDocument)) return;
|
||||
visitedDocs.add(ownerDocument);
|
||||
@@ -3048,10 +3142,10 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
let leaf: WorkspaceLeaf;
|
||||
if(location === "popout-window") {
|
||||
leaf = app.workspace.openPopoutLeaf();
|
||||
leaf = this.app.workspace.openPopoutLeaf();
|
||||
}
|
||||
if(location === "new-tab") {
|
||||
leaf = app.workspace.getLeaf('tab');
|
||||
leaf = this.app.workspace.getLeaf('tab');
|
||||
}
|
||||
if(!leaf) {
|
||||
leaf = this.app.workspace.getLeaf(false);
|
||||
@@ -3132,7 +3226,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;
|
||||
@@ -3193,7 +3287,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
return file.path;
|
||||
}
|
||||
|
||||
public async setMarkdownView(leaf: WorkspaceLeaf) {
|
||||
public async setMarkdownView(leaf: WorkspaceLeaf, eState?: any) {
|
||||
const state = leaf.view.getState();
|
||||
|
||||
//Note v2.0.19: I have absolutely no idea why I thought this is necessary. Removing this.
|
||||
@@ -3209,8 +3303,14 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
state,
|
||||
popstate: true,
|
||||
} as ViewState,
|
||||
{ focus: true },
|
||||
eState ? eState : { focus: true },
|
||||
);
|
||||
|
||||
const mdView = leaf.view;
|
||||
if(mdView instanceof MarkdownView) {
|
||||
foldExcalidrawSection(mdView);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async setExcalidrawView(leaf: WorkspaceLeaf) {
|
||||
@@ -3231,7 +3331,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
|
||||
public async exportLibrary() {
|
||||
if (this.app.isMobile) {
|
||||
if (DEVICE.isMobile) {
|
||||
const prompt = new Prompt(
|
||||
this.app,
|
||||
"Please provide a filename",
|
||||
|
||||
@@ -172,7 +172,9 @@ export class EmbeddableMenu {
|
||||
view.updateScene({appState: {activeEmbeddable: null}});
|
||||
const paragraphs = (await app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "paragraph");
|
||||
.blocks.filter((b: any) => b.display && b.node &&
|
||||
(b.node.type === "paragraph" || b.node.type === "blockquote" || b.node.type === "listItem" || b.node.type === "table" || b.node.type === "callout")
|
||||
);
|
||||
const values = ["entire-file"].concat(paragraphs);
|
||||
const display = [t("SHOW_ENTIRE_FILE")].concat(
|
||||
paragraphs.map((b: any) => `${b.node?.id ? `#^${b.node.id}: ` : ``}${b.display.trim()}`));
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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":""}.`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
TextComponent,
|
||||
TFile,
|
||||
} from "obsidian";
|
||||
import { GITHUB_RELEASES, VIEW_TYPE_EXCALIDRAW } from "./constants/constants";
|
||||
import { DEVICE, GITHUB_RELEASES, VIEW_TYPE_EXCALIDRAW } from "./constants/constants";
|
||||
import ExcalidrawView from "./ExcalidrawView";
|
||||
import { t } from "./lang/helpers";
|
||||
import type ExcalidrawPlugin from "./main";
|
||||
@@ -44,6 +44,7 @@ export interface ExcalidrawSettings {
|
||||
templateFilePath: string;
|
||||
scriptFolderPath: string;
|
||||
compress: boolean;
|
||||
decompressForMDView: boolean;
|
||||
autosave: boolean;
|
||||
autosaveInterval: number;
|
||||
autosaveIntervalDesktop: number;
|
||||
@@ -188,6 +189,7 @@ export interface ExcalidrawSettings {
|
||||
areaZoomLimit: number;
|
||||
longPressDesktop: number;
|
||||
longPressMobile: number;
|
||||
isDebugMode: boolean;
|
||||
}
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
@@ -200,6 +202,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
templateFilePath: "Excalidraw/Template.excalidraw",
|
||||
scriptFolderPath: "Excalidraw/Scripts",
|
||||
compress: false,
|
||||
decompressForMDView: true,
|
||||
autosave: true,
|
||||
autosaveInterval: 15000,
|
||||
autosaveIntervalDesktop: 15000,
|
||||
@@ -436,6 +439,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
areaZoomLimit: 1,
|
||||
longPressDesktop: 500,
|
||||
longPressMobile: 500,
|
||||
isDebugMode: false,
|
||||
};
|
||||
|
||||
export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
@@ -662,6 +666,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")))
|
||||
@@ -674,7 +690,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
.setValue(this.plugin.settings.autosaveIntervalDesktop.toString())
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.autosaveIntervalDesktop = parseInt(value);
|
||||
this.plugin.settings.autosaveInterval = app.isMobile
|
||||
this.plugin.settings.autosaveInterval = DEVICE.isMobile
|
||||
? this.plugin.settings.autosaveIntervalMobile
|
||||
: this.plugin.settings.autosaveIntervalDesktop;
|
||||
this.applySettingsUpdate();
|
||||
@@ -693,7 +709,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
.setValue(this.plugin.settings.autosaveIntervalMobile.toString())
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.autosaveIntervalMobile = parseInt(value);
|
||||
this.plugin.settings.autosaveInterval = app.isMobile
|
||||
this.plugin.settings.autosaveInterval = DEVICE.isMobile
|
||||
? this.plugin.settings.autosaveIntervalMobile
|
||||
: this.plugin.settings.autosaveIntervalDesktop;
|
||||
this.applySettingsUpdate();
|
||||
@@ -2461,6 +2477,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;
|
||||
|
||||
@@ -97,16 +97,12 @@ export class CropImage {
|
||||
withTheme: false,
|
||||
isMask: false,
|
||||
}
|
||||
const isRotated = this.imageEA.getElements().some(el=>el.type === "image" && el.angle !== 0);
|
||||
const images = Object.values(this.imageEA.imagesDict);
|
||||
if(images.length === 1) {
|
||||
if(!isRotated && (images.length === 1)) {
|
||||
return images[0].dataURL;
|
||||
}
|
||||
return await this.imageEA.createPNGBase64(null,1,exportSettings,null,null,0);
|
||||
const imageSVG = await this.imageEA.createSVG(null,false,exportSettings,null,null,0);
|
||||
const svgData = new XMLSerializer().serializeToString(imageSVG);
|
||||
return `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svgData)))}`;
|
||||
// const blob = new Blob([svgString], { type: 'image/svg+xml' });
|
||||
// return `data:image/svg+xml;base64,${await blobToBase64(blob)}`;
|
||||
}
|
||||
|
||||
private async buildSVG(): Promise<SVGSVGElement> {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -32,6 +32,10 @@ export const setDynamicStyle = (
|
||||
view?.excalidrawAPI?.getAppState?.()?.theme === "light" ||
|
||||
view?.excalidrawData?.scene?.appState?.theme === "light";
|
||||
|
||||
if (color==="transparent") {
|
||||
color = "#ffffff";
|
||||
}
|
||||
|
||||
const darker = "#101010";
|
||||
const lighter = "#f0f0f0";
|
||||
const step = 10;
|
||||
@@ -110,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
|
||||
};
|
||||
|
||||
@@ -440,3 +440,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);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export const getElementsAtPointer = (
|
||||
y <= pointer.y &&
|
||||
y + h >= pointer.y
|
||||
);
|
||||
});
|
||||
}).reverse();
|
||||
};
|
||||
|
||||
export const getTextElementAtPointer = (pointer: any, view: ExcalidrawView) => {
|
||||
|
||||
@@ -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_);
|
||||
|
||||
@@ -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;
|
||||
@@ -285,7 +287,13 @@ export const openLeaf = ({
|
||||
leaf = l;
|
||||
}
|
||||
});
|
||||
if(leaf) return {leaf, promise: Promise.resolve()};
|
||||
if(leaf) {
|
||||
if(openState) {
|
||||
const promise = leaf.openFile(file, openState);
|
||||
return {leaf, promise};
|
||||
}
|
||||
return {leaf, promise: Promise.resolve()};
|
||||
}
|
||||
}
|
||||
leaf = fnGetLeaf();
|
||||
const promise = leaf.openFile(file, openState);
|
||||
@@ -336,4 +344,27 @@ 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 existingFolds = view.currentMode.getFoldInfo();
|
||||
const lineCount = view.editor.lineCount();
|
||||
let i = -1;
|
||||
while(i++<lineCount && view.editor.getLine(i) !== "# Excalidraw Data");
|
||||
|
||||
if(i>-1 && i<lineCount) {
|
||||
const foldPositions = [
|
||||
...(existingFolds?.folds ?? []),
|
||||
...[{from: i, 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);
|
||||
|
||||
@@ -19,20 +19,19 @@ import {
|
||||
IMAGE_TYPES,
|
||||
FRONTMATTER_KEYS,
|
||||
EXCALIDRAW_PLUGIN,
|
||||
getCommonBoundingBox,
|
||||
DEVICE,
|
||||
} from "../constants/constants";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { ExcalidrawElement } 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;
|
||||
@@ -157,6 +156,10 @@ const rotate = (
|
||||
export const rotatedDimensions = (
|
||||
element: ExcalidrawElement,
|
||||
): [number, number, number, number] => {
|
||||
const bb = getCommonBoundingBox([element]);
|
||||
return [bb.minX, bb.minY, bb.maxX - bb.minX, bb.maxY - bb.minY];
|
||||
|
||||
//removed with 2.1.5... will delete later
|
||||
if (element.angle === 0) {
|
||||
return [element.x, element.y, element.width, element.height];
|
||||
}
|
||||
@@ -447,10 +450,10 @@ export const scaleLoadedImage = (
|
||||
for (const f of files.filter((f:any)=>{
|
||||
if(!Boolean(EXCALIDRAW_PLUGIN)) return true; //this should never happen
|
||||
const ef = EXCALIDRAW_PLUGIN.filesMaster.get(f.id);
|
||||
if(!ef) return false;
|
||||
if(!ef) return true; //mermaid SVG or equation
|
||||
const file = EXCALIDRAW_PLUGIN.app.vault.getAbstractFileByPath(ef.path.replace(/#.*$/,"").replace(/\|.*$/,""));
|
||||
if(!file || (file instanceof TFolder)) return false;
|
||||
return EXCALIDRAW_PLUGIN.isExcalidrawFile(file as TFile)
|
||||
return (file as TFile).extension==="md" || EXCALIDRAW_PLUGIN.isExcalidrawFile(file as TFile)
|
||||
})) {
|
||||
const [w_image, h_image] = [f.size.width, f.size.height];
|
||||
const imageAspectRatio = f.size.width / f.size.height;
|
||||
@@ -503,7 +506,7 @@ export const setDocLeftHandedMode = (isLeftHanded: boolean, ownerDocument:Docume
|
||||
export const setLeftHandedMode = (isLeftHanded: boolean) => {
|
||||
const visitedDocs = new Set<Document>();
|
||||
app.workspace.iterateAllLeaves((leaf) => {
|
||||
const ownerDocument = app.isMobile?document:leaf.view.containerEl.ownerDocument;
|
||||
const ownerDocument = DEVICE.isMobile?document:leaf.view.containerEl.ownerDocument;
|
||||
if(!ownerDocument) return;
|
||||
if(visitedDocs.has(ownerDocument)) return;
|
||||
visitedDocs.add(ownerDocument);
|
||||
@@ -540,7 +543,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 => {
|
||||
@@ -758,8 +761,6 @@ 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(){};
|
||||
|
||||
|
||||
|
||||
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