Compare commits

..

15 Commits

Author SHA1 Message Date
zsviczian
0c5ceaa3f7 fixed unescape, decodeURIComponent issue with non-latin characters.
exitFullscreen when closing the view.
2024-12-18 10:22:27 +00:00
zsviczian
2e602d49a2 Merge pull request #2163 from hackerESQ/master
Allow poorly formated .excalidraw files to render
2024-12-18 10:10:08 +01:00
hackerESQ
84bcdf8bee Merge pull request #1 from hackerESQ/enable-poorly-formatted-json-legacy-mode
Allow poorly formated .excalidraw files to render
2024-12-17 22:07:26 -06:00
hackerESQ
6d60bcf6eb Allow poorly formated .excalidraw files to render
There are several excalidraw implementations which fail to follow the defined excalidraw JSON schema. This allows those poorly formatted files to render in legacy mode in Obsidian.
2024-12-17 22:06:54 -06:00
zsviczian
b832a51a5b Merge pull request #2160 from zsviczian/2.7.0-bugs
Fix ROOTELEMENTSIZE calc, view switch timestamp logic, update global app refs, minify packages, fix FileManager init, add 2.7.0 notes
2024-12-17 18:01:02 +01:00
zsviczian
dd4c07cbf9 ROOTELEMENTSIZE calculation based on overrideObsidianFontSize 2024-12-17 16:51:12 +00:00
zsviczian
6a86de3e1e splitViewLeafSwitchTimestamp should only be set if switching from markdown to Excalidraw view 2024-12-17 16:31:39 +00:00
zsviczian
ff8c649c6a isRecentSplitViewSwitch 2024-12-17 16:03:55 +00:00
zsviczian
ae34e124a7 updated remaining instances of reference to global app 2024-12-17 12:40:10 +00:00
zsviczian
5d084ffc30 minify react and excalidraw, fix filemanager instantiation, added 2.7.0 release notes 2024-12-17 10:34:59 +00:00
zsviczian
b0a9cf848e 2.7.0-beta-7 (0.17.6-22) Modified rollup to load packages in new Function instead of window.eval 2024-12-16 20:56:47 +01:00
zsviczian
37e06efa43 fixed packageLoader for popout windows, getSharedMermaidInstance (load mermaid) 2024-12-16 18:53:39 +01:00
zsviczian
3a6ad7d762 2.7.0-beta-6 (language compress) 2024-12-15 19:26:10 +01:00
zsviczian
2846b358f4 EventManager and improved type safety (removed //@ts-ignore 2024-12-15 15:28:10 +01:00
zsviczian
8b3c22cc7f Carved out CommandManager from main.ts 2024-12-15 07:48:38 +01:00
36 changed files with 2653 additions and 2318 deletions

View File

@@ -15,9 +15,15 @@ let html: any;
let preamble: string;
function svgToBase64(svg: string): string {
return `data:image/svg+xml;base64,${btoa(
decodeURIComponent(encodeURIComponent(svg.replaceAll(" ", " "))),
)}`;
const cleanSvg = svg.replaceAll(" ", " ");
// Convert the string to UTF-8 and handle non-Latin1 characters
const encodedData = encodeURIComponent(cleanSvg)
.replace(/%([0-9A-F]{2})/g,
(match, p1) => String.fromCharCode(parseInt(p1, 16))
);
return `data:image/svg+xml;base64,${btoa(encodedData)}`;
}
async function getImageSize(src: string): Promise<{ height: number; width: number }> {

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-excalidraw-plugin",
"name": "Excalidraw",
"version": "2.7.0-beta-5",
"version": "2.7.0",
"minAppVersion": "1.1.6",
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
"author": "Zsolt Viczian",

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-excalidraw-plugin",
"name": "Excalidraw",
"version": "2.6.8",
"version": "2.7.0",
"minAppVersion": "1.1.6",
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
"author": "Zsolt Viczian",

View File

@@ -16,14 +16,15 @@
"build:mathjax": "cd MathjaxToSVG && npm run build",
"build:all": "npm run build:mathjax && npm run build",
"dev:mathjax": "cd MathjaxToSVG && npm run dev",
"dev:all": "npm run dev:mathjax && npm run dev"
"dev:all": "npm run dev:mathjax && npm run dev",
"build:lang": "node ./scripts/compressLanguages.js"
},
"keywords": [],
"author": "",
"license": "MIT",
"dependencies": {
"@popperjs/core": "^2.11.8",
"@zsviczian/excalidraw": "0.17.6-21",
"@zsviczian/excalidraw": "0.17.6-22",
"chroma-js": "^2.4.2",
"clsx": "^2.0.0",
"@zsviczian/colormaster": "^1.2.2",
@@ -72,7 +73,7 @@
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"lz-string": "^1.5.0",
"obsidian": "1.5.7-1",
"obsidian": "^1.7.2",
"prettier": "^3.0.1",
"rollup": "^2.70.1",
"rollup-plugin-copy": "^3.5.0",
@@ -81,7 +82,9 @@
"rollup-plugin-typescript2": "^0.34.1",
"tslib": "^2.6.1",
"ttypescript": "^1.5.15",
"typescript": "^5.2.2"
"typescript": "^5.2.2",
"fs-extra": "^11.2.0",
"uglify-js": "^3.19.3"
},
"resolutions": {
"@typescript-eslint/typescript-estree": "5.3.0"

View File

@@ -9,6 +9,7 @@ import LZString from 'lz-string';
import postprocess from '@zsviczian/rollup-plugin-postprocess';
import cssnano from 'cssnano';
import jsesc from 'jsesc';
import { minify } from 'uglify-js';
// Load environment variables
import dotenv from 'dotenv';
@@ -21,15 +22,48 @@ console.log(`Running: ${process.env.NODE_ENV}; isProd: ${isProd}; isLib: ${isLib
const mathjaxtosvg_pkg = isLib ? "" : fs.readFileSync("./MathjaxToSVG/dist/index.js", "utf8");
const excalidraw_pkg = isLib ? "" : isProd
const LANGUAGES = ['ru', 'zh-cn']; //english is not compressed as it is always loaded by default
function trimLastSemicolon(input) {
if (input.endsWith(";")) {
return input.slice(0, -1);
}
return input;
}
function minifyCode(code) {
const minified = minify(code,{
compress: true,
mangle: true,
output: {
comments: false,
beautify: false,
},
});
if (minified.error) {
throw new Error(minified.error);
}
return minified.code;
}
function compressLanguageFile(lang) {
const inputDir = "./src/lang/locale";
const filePath = `${inputDir}/${lang}.ts`;
let content = fs.readFileSync(filePath, "utf-8");
content = trimLastSemicolon(content.split("export default")[1].trim());
return LZString.compressToBase64(minifyCode(`x = ${content};`));
}
const excalidraw_pkg = isLib ? "" : minifyCode( isProd
? fs.readFileSync("./node_modules/@zsviczian/excalidraw/dist/excalidraw.production.min.js", "utf8")
: fs.readFileSync("./node_modules/@zsviczian/excalidraw/dist/excalidraw.development.js", "utf8");
const react_pkg = isLib ? "" : isProd
: fs.readFileSync("./node_modules/@zsviczian/excalidraw/dist/excalidraw.development.js", "utf8"));
const react_pkg = isLib ? "" : minifyCode(isProd
? fs.readFileSync("./node_modules/react/umd/react.production.min.js", "utf8")
: fs.readFileSync("./node_modules/react/umd/react.development.js", "utf8");
const reactdom_pkg = isLib ? "" : isProd
: fs.readFileSync("./node_modules/react/umd/react.development.js", "utf8"));
const reactdom_pkg = isLib ? "" : minifyCode(isProd
? fs.readFileSync("./node_modules/react-dom/umd/react-dom.production.min.js", "utf8")
: fs.readFileSync("./node_modules/react-dom/umd/react-dom.development.js", "utf8");
: fs.readFileSync("./node_modules/react-dom/umd/react-dom.development.js", "utf8"));
const lzstring_pkg = isLib ? "" : fs.readFileSync("./node_modules/lz-string/libs/lz-string.min.js", "utf8");
if (!isLib) {
@@ -60,15 +94,11 @@ const packageString = isLib
'\nlet REACT_PACKAGES = `' +
jsesc(react_pkg + reactdom_pkg, { quotes: 'backtick' }) +
'`;\n' +
/* 'let EXCALIDRAW_PACKAGE = `' +
jsesc(excalidraw_pkg, { quotes: 'backtick' }) +
'`;\n' +*/
'const unpackExcalidraw = () => LZString.decompressFromBase64("' + LZString.compressToBase64(excalidraw_pkg) + '");\n' +
'let {react, reactDOM } = window.eval.call(window, `(function() {' + '${REACT_PACKAGES};' + 'return {react: React, reactDOM: ReactDOM};})();`);\n' +
'let {react, reactDOM } = new Function(`${REACT_PACKAGES}; return {react: React, reactDOM: ReactDOM};`)();\n' +
'let excalidrawLib = {};\n' +
'const loadMathjaxToSVG = () => window.eval.call(window, `(function() {' +
'${LZString.decompressFromBase64("' + LZString.compressToBase64(mathjaxtosvg_pkg) + '")}' +
'return MathjaxToSVG;})();`);\n' +
'const loadMathjaxToSVG = () => new Function(`${LZString.decompressFromBase64("' + LZString.compressToBase64(mathjaxtosvg_pkg) + '")}; return MathjaxToSVG;`)();\n' +
`const PLUGIN_LANGUAGES = {${LANGUAGES.map(lang => `"${lang}": "${compressLanguageFile(lang)}"`).join(",")}};\n` +
'const PLUGIN_VERSION="' + manifest.version + '";';
const BASE_CONFIG = {

View File

@@ -266,7 +266,7 @@ export class EmbeddedFile {
public isLoaded(isDark: boolean): boolean {
if(!this.isHyperLink && !this.isLocalLink) {
if (!this.file) {
this.file = app.metadataCache.getFirstLinkpathDest(
this.file = this.plugin.app.metadataCache.getFirstLinkpathDest(
this.linkParts.path,
this.hostPath,
); // maybe the file has synchronized in the mean time

View File

@@ -38,6 +38,7 @@ import {
mermaidToExcalidraw,
refreshTextDimensions,
getFontFamilyString,
EXCALIDRAW_PLUGIN,
} from "src/constants/constants";
import { blobToBase64, checkAndCreateFolder, getDrawingFilename, getExcalidrawEmbeddedFilesFiletree, getListOfTemplateFiles, getNewUniqueFilepath, hasExcalidrawEmbeddedImagesTreeChanged, } from "src/utils/FileUtils";
import {
@@ -246,7 +247,7 @@ export class ExcalidrawAutomate {
* @returns
*/
public getNewUniqueFilepath(filename: string, folderpath: string): string {
return getNewUniqueFilepath(app.vault, filename, folderpath);
return getNewUniqueFilepath(this.plugin.app.vault, filename, folderpath);
}
/**
@@ -278,8 +279,8 @@ export class ExcalidrawAutomate {
errorMessage("targetView not set", "getAttachmentFolderAndFilePath()");
return null;
}
const folderAndPath = await getAttachmentsFolderAndFilePath(app,this.targetView.file.path, filename);
return getNewUniqueFilepath(app.vault, filename, folderAndPath.folder);
const folderAndPath = await getAttachmentsFolderAndFilePath(this.plugin.app,this.targetView.file.path, filename);
return getNewUniqueFilepath(this.plugin.app.vault, filename, folderAndPath.folder);
}
public compressToBase64(str:string): string {
@@ -1049,7 +1050,7 @@ export class ExcalidrawAutomate {
width,
height,
url ? url : file ? `[[${
app.metadataCache.fileToLinktext(
this.plugin.app.metadataCache.fileToLinktext(
file,
this.targetView.file.path,
false, //file.extension === "md", //changed this to false because embedable link navigation in ExcaliBrain
@@ -3164,14 +3165,14 @@ export const updateElementLinksToObsidianLinks = ({elements, hostFile}:{
if (linkText.match(REG_LINKINDEX_INVALIDCHARS)) {
return el;
}
const file = app.metadataCache.getFirstLinkpathDest(
const file = EXCALIDRAW_PLUGIN.app.metadataCache.getFirstLinkpathDest(
linkText,
hostFile.path,
);
if(!file) {
return el;
}
let link = app.getObsidianUrl(file);
let link = EXCALIDRAW_PLUGIN.app.getObsidianUrl(file);
if(window.ExcalidrawAutomate?.onUpdateElementLinkForExportHook) {
link = window.ExcalidrawAutomate.onUpdateElementLinkForExportHook({
originalLink: el.link,

View File

@@ -518,7 +518,7 @@ export class ExcalidrawData {
return;
}
const saveVersion = this.scene.source.split("https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/")[1]??"1.8.16";
const saveVersion = this.scene.source?.split("https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/")[1]??"1.8.16";
const elements = this.scene.elements;
for (const el of elements) {

View File

@@ -2,7 +2,7 @@ import { RestoredDataState } from "@zsviczian/excalidraw/types/excalidraw/data/r
import { ImportedDataState } from "@zsviczian/excalidraw/types/excalidraw/data/types";
import { BoundingBox } from "@zsviczian/excalidraw/types/excalidraw/element/bounds";
import { ElementsMap, ExcalidrawBindableElement, ExcalidrawElement, ExcalidrawFrameElement, ExcalidrawFrameLikeElement, ExcalidrawTextContainer, ExcalidrawTextElement, FontFamilyValues, FontString, NonDeleted, NonDeletedExcalidrawElement, Theme } from "@zsviczian/excalidraw/types/excalidraw/element/types";
import { FontMetadata } from "@zsviczian/excalidraw/types/excalidraw/fonts/metadata";
import { FontMetadata } from "@zsviczian/excalidraw/types/excalidraw/fonts/FontMetadata";
import { AppState, BinaryFiles, DataURL, GenerateDiagramToCode, Zoom } from "@zsviczian/excalidraw/types/excalidraw/types";
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
import { GlobalPoint } from "@zsviczian/excalidraw/types/math/types";
@@ -220,5 +220,6 @@ declare namespace ExcalidrawLib {
): string;
function safelyParseJSON (json: string): Record<string, any> | null;
function loadSceneFonts(elements: NonDeletedExcalidrawElement[]): Promise<void>;
function loadMermaid(): Promise<any>;
}

View File

@@ -817,7 +817,6 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
}
//saving to backup with a delay in case application closes in the meantime, I want to avoid both save and backup corrupted.
const path = this.file.path;
//@ts-ignore
const data = this.lastSavedData;
window.setTimeout(()=>imageCache.addBAKToCache(path,data),50);
triggerReload = (this.lastSaveTimestamp === this.file.stat.mtime) &&
@@ -953,7 +952,6 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.restoreMobileLeaves, "ExcalidrawView.restoreMobileLeaves");
if(this.hiddenMobileLeaves.length>0) {
this.hiddenMobileLeaves.forEach((x:[WorkspaceLeaf,string])=>{
//@ts-ignore
x[0].containerEl.style.display = x[1];
})
this.hiddenMobileLeaves = [];
@@ -1073,7 +1071,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.gotoFullscreen, "ExcalidrawView.gotoFullscreen");
if(this.plugin.leafChangeTimeout) {
window.clearTimeout(this.plugin.leafChangeTimeout); //leafChangeTimeout is created on window in main.ts!!!
this.plugin.leafChangeTimeout = null;
this.plugin.clearLeafChangeTimeout();
}
if (!this.excalidrawWrapperRef) {
return;
@@ -1414,7 +1412,6 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
}
try {
//@ts-ignore
const drawIO = this.app.plugins.plugins["drawio-obsidian"];
if(drawIO && drawIO._loaded) {
if(file.extension === "svg") {
@@ -1524,7 +1521,6 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
if(!silent) new Notice("Save successful", 1000);
}
onload() {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onload, "ExcalidrawView.onload");
if(this.plugin.settings.overrideObsidianFontSize) {
@@ -1536,7 +1532,6 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
if(DEVICE.isDesktop && !apiMissing) {
this.destroyers.push(
//@ts-ignore
//this.containerEl.onWindowMigrated(this.leaf.rebuildView.bind(this))
this.containerEl.onWindowMigrated(async() => {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onload, "ExcalidrawView.onWindowMigrated");
@@ -1930,7 +1925,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
//onClose happens after onunload
protected async onClose(): Promise<void> {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onClose,`ExcalidrawView.onClose, file:${this.file?.name}`);
this.exitFullscreen();
await this.forceSaveIfRequired();
if (this.excalidrawRoot) {
this.excalidrawRoot.unmount();
@@ -1975,7 +1970,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
let leafcount = 0;
this.app.workspace.iterateAllLeaves(l=>{
if(l === this.leaf) return;
//@ts-ignore
if(l.containerEl?.ownerDocument.defaultView === this.ownerWindow) {
leafcount++;
}
@@ -1986,7 +1981,6 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
this.lastMouseEvent = null;
this.requestSave = null;
//@ts-ignore
this.leaf.tabHeaderInnerTitleEl.style.color = "";
//super.onClose will unmount Excalidraw, need to save before that
@@ -2125,7 +2119,6 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
}
if (state.rename === "all") {
//@ts-ignore
this.app.fileManager.promptForFileRename(this.file);
return;
}
@@ -2381,7 +2374,6 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
confirmationPrompt.waitForClose.then(async (confirmed) => {
if (confirmed) {
await this.app.vault.modify(file, drawingBAK);
//@ts-ignore
plugin.excalidrawFileModes[leaf.id || file.path] = VIEW_TYPE_EXCALIDRAW;
setExcalidrawView(leaf);
}
@@ -2744,7 +2736,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
isEditedAsMarkdownInOtherView(): boolean {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.isEditedAsMarkdownInOtherView, "ExcalidrawView.isEditedAsMarkdownInOtherView");
//if the user is editing the same file in markdown mode, do not compress it
const leaves = app.workspace.getLeavesOfType("markdown");
const leaves = this.app.workspace.getLeavesOfType("markdown");
return (
leaves.filter((leaf) => (leaf.view as MarkdownView).file === this.file)
.length > 0
@@ -2772,9 +2764,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
}
if(!DEVICE.isMobile) {
if(requireApiVersion("0.16.0")) {
//@ts-ignore
this.leaf.tabHeaderInnerIconEl.style.color="var(--color-accent)"
//@ts-ignore
this.leaf.tabHeaderInnerTitleEl.style.color="var(--color-accent)"
}
}
@@ -2802,9 +2792,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
this.actionButtons['save'].querySelector("svg").removeClass("excalidraw-dirty");
if(!DEVICE.isMobile) {
if(requireApiVersion("0.16.0")) {
//@ts-ignore
this.leaf.tabHeaderInnerIconEl.style.color=""
//@ts-ignore
this.leaf.tabHeaderInnerTitleEl.style.color=""
}
}
@@ -2938,9 +2926,12 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
: [textElement.x, textElement.y, MAX_IMAGE_SIZE,MAX_IMAGE_SIZE];
const id = ea.addEmbeddable(x,y,w,h, undefined,f);
if(containerElement) {
["backgroundColor", "fillStyle","roughness","roundness","strokeColor","strokeStyle","strokeWidth"].forEach((prop)=>{
//@ts-ignore
ea.getElement(id)[prop] = containerElement[prop];
const props:(keyof ExcalidrawElement)[] = ["backgroundColor", "fillStyle","roughness","roundness","strokeColor","strokeStyle","strokeWidth"];
props.forEach((prop)=>{
const element = ea.getElement(id);
if (prop in element) {
(element as any)[prop] = containerElement[prop];
}
});
}
ea.getElement(id)
@@ -2955,7 +2946,6 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
const thumbnailLink = await getYouTubeThumbnailLink(link);
const ea = getEA(this) as ExcalidrawAutomate;
const id = await ea.addImage(0,0,thumbnailLink);
//@ts-ignore
ea.getElement(id).link = link;
await ea.addElementsToView(true,true,true)
ea.destroy();
@@ -2999,12 +2989,12 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
const ea = getEA(this) as ExcalidrawAutomate;
const el = ea
.getViewElements()
.filter((el) => el.id === id);
.filter((el) => el.type==="text" && el.id === id);
if (el.length === 1) {
//@ts-ignore
el[0].text = el[0].originalText = el[0].rawText =
`[${data.meta.title}](${text})`;
ea.copyViewElementsToEAforEditing(el);
const textElement = ea.getElement(el[0].id) as Mutable<ExcalidrawTextElement>;
textElement.text = textElement.originalText = textElement.rawText =
`[${data.meta.title}](${text})`;
await ea.addElementsToView(false, false, false);
ea.destroy();
}
@@ -3365,13 +3355,10 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
const {parseResult, link} =
await this.excalidrawData.addTextElement(
textElement.id,
//@ts-ignore
textElement.text,
//@ts-ignore
textElement.rawText, //TODO: implement originalText support in ExcalidrawAutomate
);
if (link) {
//@ts-ignore
textElement.link = link;
}
if (this.textMode === TextMode.parsed && !textElement?.isDeleted) {
@@ -3557,7 +3544,6 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
}
private clearHoverPreview() {
//@ts-ignore
const hoverContainerEl = this.hoverPopover?.containerEl;
//don't auto hide hover-editor
if (this.hoverPopover && !hoverContainerEl?.parentElement?.hasClass("hover-editor")) {
@@ -3566,7 +3552,6 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
if(this.hoverPopover.embed?.editor) {
return;
}
//@ts-ignore
this.hoverPopover?.hide();
} else if (this.hoverPreviewTarget) {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.clearHoverPreview, "ExcalidrawView.clearHoverPreview", this);
@@ -3583,7 +3568,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
private dropAction(transfer: DataTransfer) {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.dropAction, "ExcalidrawView.dropAction");
// Return a 'copy' or 'link' action according to the content types, or undefined if no recognized type
const files = (app as any).dragManager.draggable?.files;
const files = (this.app as any).dragManager.draggable?.files;
if (files) {
if (files[0] == this.file) {
files.shift();
@@ -3733,7 +3718,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
return;
}
const f = app.metadataCache.getFirstLinkpathDest(
const f = this.app.metadataCache.getFirstLinkpathDest(
linktext.split("#")[0],
this.file.path,
);
@@ -4248,7 +4233,6 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
if (this.getHookServer().onDropHook) {
try {
return this.getHookServer().onDropHook({
//@ts-ignore
ea: this.getHookServer(), //the ExcalidrawAutomate object
event, //React.DragEvent<HTMLDivElement>
draggable, //Obsidian draggable object
@@ -4326,7 +4310,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
//internalDragAction === "link"
this.addText(
`[[${app.metadataCache.fileToLinktext(
`[[${this.app.metadataCache.fileToLinktext(
draggable.file,
this.file.path,
true,
@@ -4389,7 +4373,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
//internalDragAction === "link"
for (const f of draggable.files) {
await this.addText(
`[[${app.metadataCache.fileToLinktext(
`[[${this.app.metadataCache.fileToLinktext(
f,
this.file.path,
true,
@@ -4473,7 +4457,6 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
if (event.dataTransfer.types.length >= 1 && ["image-url","image-import","embeddable"].contains(localFileDragAction)) {
for(let i=0;i<event.dataTransfer.files.length;i++) {
//@ts-ignore
const path = event.dataTransfer.files[i].path;
if(!path) return true; //excalidarw to continue processing
const link = getInternalLinkOrFileURLLink(path, this.plugin, event.dataTransfer.files[i].name, this.file);
@@ -4567,7 +4550,6 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
if(event.dataTransfer.types.length >= 1 && localFileDragAction === "link") {
const ea = getEA(this) as ExcalidrawAutomate;
for(let i=0;i<event.dataTransfer.files.length;i++) {
//@ts-ignore
const path = event.dataTransfer.files[i].path;
const name = event.dataTransfer.files[i].name;
if(!path || !name) return true; //excalidarw to continue processing
@@ -5764,14 +5746,12 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
excalidrawWrapper.style.top = `${-(st.height - height)}px`;
excalidrawWrapper.style.height = `${st.height}px`;
this.excalidrawContainer?.querySelector(".App-bottom-bar")?.scrollIntoView();
//@ts-ignore
this.headerEl?.scrollIntoView();
}
}
if(isKeyboardBackEvent) {
const excalidrawWrapper = this.excalidrawWrapperRef.current;
const appButtonBar = this.excalidrawContainer?.querySelector(".App-bottom-bar");
//@ts-ignore
const headerEl = this.headerEl;
if(excalidrawWrapper) {
excalidrawWrapper.style.top = "";

View File

@@ -33,7 +33,7 @@ export const updateEquation = async (
addFiles: Function,
) => {
await loadMathJax();
const data = await tex2dataURLExternal(equation, 4, app);
const data = await tex2dataURLExternal(equation, 4, view.app);
if (data) {
const files: FileData[] = [];
files.push({

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,340 @@
import { WorkspaceLeaf, TFile, Editor, MarkdownView, MarkdownFileInfo, MetadataCache, App, EventRef, Menu, FileView } from "obsidian";
import { ExcalidrawElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
import { getLink } from "../utils/FileUtils";
import { editorInsertText, getParentOfClass, setExcalidrawView } from "../utils/ObsidianUtils";
import ExcalidrawPlugin from "src/main";
import { DEBUGGING, debug } from "src/utils/DebugHelper";
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
import { DEVICE, FRONTMATTER_KEYS, ICON_NAME, VIEW_TYPE_EXCALIDRAW } from "src/constants/constants";
import ExcalidrawView from "src/ExcalidrawView";
import { t } from "src/lang/helpers";
/**
* Registers event listeners for the plugin
* Must be constructed after the workspace is ready (onLayoutReady)
* Intended to be called from onLayoutReady in onload()
*/
export class EventManager {
private plugin: ExcalidrawPlugin;
private app: App;
public leafChangeTimeout: number|null = null;
private removeEventLisnters:(()=>void)[] = []; //only used if I register an event directly, not via Obsidian's registerEvent
private previouslyActiveLeaf: WorkspaceLeaf;
private splitViewLeafSwitchTimestamp: number = 0;
get settings() {
return this.plugin.settings;
}
get ea():ExcalidrawAutomate {
return this.plugin.ea;
}
get activeExcalidrawView() {
return this.plugin.activeExcalidrawView;
}
set activeExcalidrawView(view: ExcalidrawView) {
this.plugin.activeExcalidrawView = view;
}
private registerEvent(eventRef: EventRef): void {
this.plugin.registerEvent(eventRef);
}
constructor(plugin: ExcalidrawPlugin) {
this.plugin = plugin;
this.app = plugin.app;
}
destroy() {
if(this.leafChangeTimeout) {
window.clearTimeout(this.leafChangeTimeout);
this.leafChangeTimeout = null;
}
this.removeEventLisnters.forEach((removeEventListener) =>
removeEventListener(),
);
this.removeEventLisnters = [];
}
public async initialize() {
try {
await this.registerEvents();
} catch (e) {
console.error("Error registering event listeners", e);
}
this.plugin.logStartupEvent("Event listeners registered");
}
public isRecentSplitViewSwitch():boolean {
return (Date.now() - this.splitViewLeafSwitchTimestamp) < 3000;
}
public async registerEvents() {
await this.plugin.awaitInit();
this.registerEvent(this.app.workspace.on("editor-paste", this.onPasteHandler.bind(this)));
this.registerEvent(this.app.vault.on("rename", this.onRenameHandler.bind(this)));
this.registerEvent(this.app.vault.on("modify", this.onModifyHandler.bind(this)));
this.registerEvent(this.app.vault.on("delete", this.onDeleteHandler.bind(this)));
//save Excalidraw leaf and update embeds when switching to another leaf
this.registerEvent(this.plugin.app.workspace.on("active-leaf-change", this.onActiveLeafChangeHandler.bind(this)));
//File Save Trigger Handlers
//Save the drawing if the user clicks outside the Excalidraw Canvas
const onClickEventSaveActiveDrawing = this.onClickSaveActiveDrawing.bind(this);
this.app.workspace.containerEl.addEventListener("click", onClickEventSaveActiveDrawing);
this.removeEventLisnters.push(() => {
this.app.workspace.containerEl.removeEventListener("click", onClickEventSaveActiveDrawing)
});
this.registerEvent(this.app.workspace.on("file-menu", this.onFileMenuSaveActiveDrawing.bind(this)));
const metaCache: MetadataCache = this.app.metadataCache;
this.registerEvent(
metaCache.on("changed", (file, _, cache) =>
this.plugin.updateFileCache(file, cache?.frontmatter),
),
);
this.registerEvent(this.app.workspace.on("file-menu", this.onFileMenuHandler.bind(this)));
this.plugin.registerEvent(this.plugin.app.workspace.on("editor-menu", this.onEditorMenuHandler.bind(this)));
}
private onPasteHandler (evt: ClipboardEvent, editor: Editor, info: MarkdownView | MarkdownFileInfo ) {
if(evt.defaultPrevented) return
const data = evt.clipboardData.getData("text/plain");
if (!data) return;
if (data.startsWith(`{"type":"excalidraw/clipboard"`)) {
evt.preventDefault();
try {
const drawing = JSON.parse(data);
const hasOneTextElement = drawing.elements.filter((el:ExcalidrawElement)=>el.type==="text").length === 1;
if (!(hasOneTextElement || drawing.elements?.length === 1)) {
return;
}
const element = hasOneTextElement
? drawing.elements.filter((el:ExcalidrawElement)=>el.type==="text")[0]
: drawing.elements[0];
if (element.type === "image") {
const fileinfo = this.plugin.filesMaster.get(element.fileId);
if(fileinfo && fileinfo.path) {
let path = fileinfo.path;
const sourceFile = info.file;
const imageFile = this.app.vault.getAbstractFileByPath(path);
if(sourceFile && imageFile && imageFile instanceof TFile) {
path = this.app.metadataCache.fileToLinktext(imageFile,sourceFile.path);
}
editorInsertText(editor, getLink(this.plugin, {path}));
}
return;
}
if (element.type === "text") {
editorInsertText(editor, element.rawText);
return;
}
if (element.link) {
editorInsertText(editor, `${element.link}`);
return;
}
} catch (e) {
}
}
};
private onRenameHandler(file: TFile, oldPath: string) {
this.plugin.renameEventHandler(file, oldPath);
}
private onModifyHandler(file: TFile) {
this.plugin.modifyEventHandler(file);
}
private onDeleteHandler(file: TFile) {
this.plugin.deleteEventHandler(file);
}
public async onActiveLeafChangeHandler (leaf: WorkspaceLeaf) {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onActiveLeafChangeHandler,`onActiveLeafChangeEventHandler`, leaf);
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/723
if (leaf.view && leaf.view.getViewType() === "pdf") {
this.plugin.lastPDFLeafID = leaf.id;
}
if(this.leafChangeTimeout) {
window.clearTimeout(this.leafChangeTimeout);
}
this.leafChangeTimeout = window.setTimeout(()=>{this.leafChangeTimeout = null;},1000);
if(this.settings.overrideObsidianFontSize) {
if(leaf.view && (leaf.view.getViewType() === VIEW_TYPE_EXCALIDRAW)) {
document.documentElement.style.fontSize = "";
}
}
const previouslyActiveEV = this.activeExcalidrawView;
const newActiveviewEV: ExcalidrawView =
leaf.view instanceof ExcalidrawView ? leaf.view : null;
this.activeExcalidrawView = newActiveviewEV;
const previousFile = (this.previouslyActiveLeaf?.view as FileView)?.file;
const currentFile = (leaf?.view as FileView).file;
//editing the same file in a different leaf
if(currentFile && (previousFile === currentFile)) {
if((this.previouslyActiveLeaf.view instanceof MarkdownView && leaf.view instanceof ExcalidrawView)) {
this.splitViewLeafSwitchTimestamp = Date.now();
}
}
this.previouslyActiveLeaf = leaf;
if (newActiveviewEV) {
this.plugin.addModalContainerObserver();
this.plugin.lastActiveExcalidrawFilePath = newActiveviewEV.file?.path;
} else {
this.plugin.removeModalContainerObserver();
}
//!Temporary hack
//https://discord.com/channels/686053708261228577/817515900349448202/1031101635784613968
if (DEVICE.isMobile && newActiveviewEV && !previouslyActiveEV) {
const navbar = document.querySelector("body>.app-container>.mobile-navbar");
if(navbar && navbar instanceof HTMLDivElement) {
navbar.style.position="relative";
}
}
if (DEVICE.isMobile && !newActiveviewEV && previouslyActiveEV) {
const navbar = document.querySelector("body>.app-container>.mobile-navbar");
if(navbar && navbar instanceof HTMLDivElement) {
navbar.style.position="";
}
}
//----------------------
//----------------------
if (previouslyActiveEV && previouslyActiveEV !== newActiveviewEV) {
if (previouslyActiveEV.leaf !== leaf) {
//if loading new view to same leaf then don't save. Excalidarw view will take care of saving anyway.
//avoid double saving
if(previouslyActiveEV?.isDirty() && !previouslyActiveEV.semaphores?.viewunload) {
await previouslyActiveEV.save(true); //this will update transclusions in the drawing
}
}
if (previouslyActiveEV.file) {
this.plugin.triggerEmbedUpdates(previouslyActiveEV.file.path);
}
}
if (
newActiveviewEV &&
(!previouslyActiveEV || previouslyActiveEV.leaf !== leaf)
) {
//the user switched to a new leaf
//timeout gives time to the view being exited to finish saving
const f = newActiveviewEV.file;
if (newActiveviewEV.file) {
setTimeout(() => {
if (!newActiveviewEV || !newActiveviewEV._loaded) {
return;
}
if (newActiveviewEV.file?.path !== f?.path) {
return;
}
if (newActiveviewEV.activeLoader) {
return;
}
newActiveviewEV.loadSceneFiles();
}, 2000);
} //refresh embedded files
}
if (
newActiveviewEV && newActiveviewEV._loaded &&
newActiveviewEV.isLoaded && newActiveviewEV.excalidrawAPI &&
this.ea.onCanvasColorChangeHook
) {
this.ea.onCanvasColorChangeHook(
this.ea,
newActiveviewEV,
newActiveviewEV.excalidrawAPI.getAppState().viewBackgroundColor
);
}
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/300
if (this.plugin.popScope) {
this.plugin.popScope();
this.plugin.popScope = null;
}
if (newActiveviewEV) {
this.plugin.registerHotkeyOverrides();
}
}
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/551
private onClickSaveActiveDrawing(e: PointerEvent) {
if (
!this.activeExcalidrawView ||
!this.activeExcalidrawView?.isDirty() ||
e.target && ((e.target as Element).className === "excalidraw__canvas" ||
getParentOfClass((e.target as Element),"excalidraw-wrapper"))
) {
return;
}
this.activeExcalidrawView.save();
}
private onFileMenuSaveActiveDrawing () {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onFileMenuSaveActiveDrawing,`onFileMenuSaveActiveDrawing`);
if (
!this.activeExcalidrawView ||
!this.activeExcalidrawView?.isDirty()
) {
return;
}
this.activeExcalidrawView.save();
};
private onFileMenuHandler(menu: Menu, file: TFile, source: string, leaf: WorkspaceLeaf) {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onFileMenuHandler, `EventManager.onFileMenuHandler`, file, source, leaf);
if (!leaf) return;
const view = leaf.view;
if(!view || !(view instanceof MarkdownView)) return;
if (!(file instanceof TFile)) return;
const cache = this.app.metadataCache.getFileCache(file);
if (!cache?.frontmatter || !cache.frontmatter[FRONTMATTER_KEYS["plugin"].name]) return;
menu.addItem(item => {
item
.setTitle(t("OPEN_AS_EXCALIDRAW"))
.setIcon(ICON_NAME)
.setSection("pane")
.onClick(async () => {
await view.save();
this.plugin.excalidrawFileModes[leaf.id || file.path] = VIEW_TYPE_EXCALIDRAW;
setExcalidrawView(leaf);
})});
menu.items.unshift(menu.items.pop());
}
private onEditorMenuHandler(menu: Menu, editor: Editor, view: MarkdownView) {
if(!view || !(view instanceof MarkdownView)) return;
const file = view.file;
const leaf = view.leaf;
if (!view.file) return;
const cache = this.app.metadataCache.getFileCache(file);
if (!cache?.frontmatter || !cache.frontmatter[FRONTMATTER_KEYS["plugin"].name]) return;
menu.addItem(item => item
.setTitle(t("OPEN_AS_EXCALIDRAW"))
.setIcon(ICON_NAME)
.setSection("excalidraw")
.onClick(async () => {
await view.save();
this.plugin.excalidrawFileModes[leaf.id || file.path] = VIEW_TYPE_EXCALIDRAW;
setExcalidrawView(leaf);
})
);
}
}

View File

@@ -1,5 +1,5 @@
import { debug } from "src/utils/DebugHelper";
import { App, FrontMatterCache, MarkdownView, normalizePath, Notice, TAbstractFile, TFile, WorkspaceLeaf } from "obsidian";
import { App, FrontMatterCache, MarkdownView, MetadataCache, normalizePath, Notice, TAbstractFile, TFile, WorkspaceLeaf } from "obsidian";
import { BLANK_DRAWING, DARK_BLANK_DRAWING, DEVICE, EXPORT_TYPES, FRONTMATTER, FRONTMATTER_KEYS, JSON_parse, nanoid, VIEW_TYPE_EXCALIDRAW } from "src/constants/constants";
import { Prompt, templatePromt } from "src/dialogs/Prompt";
import { changeThemeOfExcalidrawMD, ExcalidrawData, getMarkdownDrawingSection } from "src/ExcalidrawData";
@@ -25,6 +25,23 @@ export class PluginFileManager {
this.app = plugin.app;
}
public async initialize() {
await this.plugin.awaitInit();
const metaCache: MetadataCache = this.app.metadataCache;
metaCache.getCachedFiles().forEach((filename: string) => {
const fm = metaCache.getCache(filename)?.frontmatter;
if (
(fm && typeof fm[FRONTMATTER_KEYS["plugin"].name] !== "undefined") ||
filename.match(/\.excalidraw$/)
) {
this.updateFileCache(
this.app.vault.getAbstractFileByPath(filename) as TFile,
fm,
);
}
});
}
public isExcalidrawFile(f: TFile): boolean {
if(!f) return false;
if (f.extension === "excalidraw") {
@@ -255,7 +272,6 @@ export class PluginFileManager {
}
let leaf: WorkspaceLeaf;
if(location === "popout-window") {
//@ts-ignore (the api does not include x,y)
leaf = this.app.workspace.openPopoutLeaf(popoutLocation);
}
if(location === "new-tab") {
@@ -356,7 +372,7 @@ export class PluginFileManager {
}
[EXPORT_TYPES, "excalidraw"].flat().forEach(async (ext: string) => {
const oldIMGpath = getIMGFilename(oldPath, ext);
const imgFile = app.vault.getAbstractFileByPath(
const imgFile = this.app.vault.getAbstractFileByPath(
normalizePath(oldIMGpath),
);
if (imgFile && imgFile instanceof TFile) {
@@ -367,7 +383,7 @@ export class PluginFileManager {
}
public async modifyEventHandler (file: TFile) {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.modifyEventHandler,`ExcalidrawPlugin.modifyEventHandler`, file);
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.modifyEventHandler,`FileManager.modifyEventHandler`, file);
const excalidrawViews = getExcalidrawViews(this.app);
excalidrawViews.forEach(async (excalidrawView) => {
if(excalidrawView.semaphores?.viewunload) {
@@ -386,11 +402,30 @@ export class PluginFileManager {
excalidrawView.semaphores.preventReload = false;
return;
}
//if the user hasn't touched the file for 5 minutes, don't synchronize, reload.
//this is to avoid complex sync scenarios of multiple remote changes outside an active collaboration session
// Avoid synchronizing or reloading if the user hasn't interacted with the file for 5 minutes.
// This prevents complex sync issues when multiple remote changes occur outside an active collaboration session.
// The following logic handles a rare edge case where:
// 1. The user opens an Excalidraw file.
// 2. Immediately splits the view without saving Excalidraw (since no changes were made).
// 3. Switches the new split view to Markdown, edits the file, and quickly returns to Excalidraw.
// 4. The "modify" event may fire while Excalidraw is active, triggering an unwanted reload and zoom reset.
// To address this:
// - We check if the user is currently editing the Markdown version of the Excalidraw file in a split view.
// - As a heuristic, we also check for recent leaf switches.
// This is not perfectly accurate (e.g., rapid switching between views within a few seconds),
// but it is sufficient to avoid most edge cases without introducing complexity.
// Edge case impact:
// - In extremely rare situations, an update arriving within the "recent switch" timeframe (e.g., from Obsidian Sync)
// might not trigger a reload. This is unlikely and an acceptable trade-off for better user experience.
const activeView = this.app.workspace.activeLeaf.view;
const isEditingMarkdownSideInSplitView = (activeView !== excalidrawView) &&
activeView instanceof MarkdownView && activeView.file === excalidrawView.file;
const isEditingMarkdownSideInSplitView = ((activeView !== excalidrawView) &&
activeView instanceof MarkdownView && activeView.file === excalidrawView.file) ||
(activeView === excalidrawView && this.plugin.isRecentSplitViewSwitch());
if(!isEditingMarkdownSideInSplitView && (excalidrawView.lastSaveTimestamp + 300000 < Date.now())) {
excalidrawView.reload(true, excalidrawView.file);

View File

@@ -2,7 +2,7 @@ import { debug, DEBUGGING } from "src/utils/DebugHelper";
import ExcalidrawPlugin from "src/main";
import { CustomMutationObserver } from "src/utils/DebugHelper";
import { getExcalidrawViews, isObsidianThemeDark } from "src/utils/ObsidianUtils";
import { App, TFile } from "obsidian";
import { App, Notice, TFile } from "obsidian";
export class ObserverManager {
private plugin: ExcalidrawPlugin;
@@ -14,16 +14,45 @@ export class ObserverManager {
private workspaceDrawerRightObserver: MutationObserver | CustomMutationObserver;
private activeViewDoc: Document;
get settings() {
return this.plugin.settings;
}
constructor(plugin: ExcalidrawPlugin) {
this.plugin = plugin;
this.app = plugin.app;
if(this.settings.matchThemeTrigger) this.addThemeObserver();
this.experimentalFileTypeDisplayToggle(this.settings.experimentalFileType);
this.addModalContainerObserver();
}
get settings() {
return this.plugin.settings;
public initialize() {
try {
if(this.settings.matchThemeTrigger) this.addThemeObserver();
this.experimentalFileTypeDisplayToggle(this.settings.experimentalFileType);
this.addModalContainerObserver();
} catch (e) {
new Notice("Error adding ObserverManager", 6000);
console.error("Error adding ObserverManager", e);
}
this.plugin.logStartupEvent("ObserverManager added");
}
public destroy() {
this.removeThemeObserver();
this.removeModalContainerObserver();
if (this.workspaceDrawerLeftObserver) {
this.workspaceDrawerLeftObserver.disconnect();
}
if (this.workspaceDrawerRightObserver) {
this.workspaceDrawerRightObserver.disconnect();
}
if (this.fileExplorerObserver) {
this.fileExplorerObserver.disconnect();
}
if (this.workspaceDrawerRightObserver) {
this.workspaceDrawerRightObserver.disconnect();
}
if (this.workspaceDrawerLeftObserver) {
this.workspaceDrawerLeftObserver.disconnect();
}
}
public addThemeObserver() {
@@ -183,17 +212,46 @@ export class ObserverManager {
this.modalContainerObserver = null;
}
public destroy() {
this.removeThemeObserver();
this.removeModalContainerObserver();
if (this.workspaceDrawerLeftObserver) {
this.workspaceDrawerLeftObserver.disconnect();
}
if (this.workspaceDrawerRightObserver) {
this.workspaceDrawerRightObserver.disconnect();
}
if (this.fileExplorerObserver) {
this.fileExplorerObserver.disconnect();
private addWorkspaceDrawerObserver() {
//when the user activates the sliding drawers on Obsidian Mobile
const leftWorkspaceDrawer = document.querySelector(
".workspace-drawer.mod-left",
);
const rightWorkspaceDrawer = document.querySelector(
".workspace-drawer.mod-right",
);
if (leftWorkspaceDrawer || rightWorkspaceDrawer) {
const action = async (m: MutationRecord[]) => {
if (
m[0].oldValue !== "display: none;" ||
!this.plugin.activeExcalidrawView ||
!this.plugin.activeExcalidrawView?.isDirty()
) {
return;
}
this.plugin.activeExcalidrawView.save();
};
const options = {
attributeOldValue: true,
attributeFilter: ["style"],
};
if (leftWorkspaceDrawer) {
this.workspaceDrawerLeftObserver = DEBUGGING
? new CustomMutationObserver(action, "slidingDrawerLeftObserver")
: new MutationObserver(action);
this.workspaceDrawerLeftObserver.observe(leftWorkspaceDrawer, options);
}
if (rightWorkspaceDrawer) {
this.workspaceDrawerRightObserver = DEBUGGING
? new CustomMutationObserver(action, "slidingDrawerRightObserver")
: new MutationObserver(action);
this.workspaceDrawerRightObserver.observe(
rightWorkspaceDrawer,
options,
);
}
}
}
}

View File

@@ -1,18 +1,31 @@
import { updateExcalidrawLib } from "src/constants/constants";
import { ExcalidrawLib } from "../ExcalidrawLib";
import { Packages } from "../types/types";
import { debug, DEBUGGING } from "../utils/DebugHelper";
import { Notice } from "obsidian";
import ExcalidrawPlugin from "src/main";
declare let REACT_PACKAGES:string;
declare let react:any;
declare let reactDOM:any;
declare let excalidrawLib: typeof ExcalidrawLib;
declare const unpackExcalidraw: Function;
export class PackageManager {
private packageMap: Map<Window, Packages> = new Map<Window, Packages>();
private EXCALIDRAW_PACKAGE: string;
constructor() {
this.packageMap.set(window,{react, reactDOM, excalidrawLib});
constructor(plugin: ExcalidrawPlugin) {
try {
this.EXCALIDRAW_PACKAGE = unpackExcalidraw();
excalidrawLib = window.eval.call(window,`(function() {${this.EXCALIDRAW_PACKAGE};return ExcalidrawLib;})()`);
updateExcalidrawLib();
this.setPackage(window,{react, reactDOM, excalidrawLib});
} catch (e) {
new Notice("Error loading the Excalidraw package", 6000);
console.error("Error loading the Excalidraw package", e);
}
plugin.logStartupEvent("Excalidraw package unpacked");
}
public setPackage(window: Window, pkg: Packages) {
@@ -29,7 +42,7 @@ export class PackageManager {
if(this.packageMap.has(win)) {
return this.packageMap.get(win);
}
const {react:r, reactDOM:rd, excalidrawLib:e} = win.eval.call(win,
`(function() {
${REACT_PACKAGES + this.EXCALIDRAW_PACKAGE};
@@ -76,5 +89,9 @@ export class PackageManager {
delete p.react;
});
this.packageMap.clear();
this.EXCALIDRAW_PACKAGE = "";
react = null;
reactDOM = null;
excalidrawLib = null;
}
}

View File

@@ -27,6 +27,7 @@ export const ERROR_IFRAME_CONVERSION_CANCELED = "iframe conversion canceled";
declare const excalidrawLib: typeof ExcalidrawLib;
export const LOCALE = moment.locale();
export const CJK_FONTS = "CJK Fonts";
export const obsidianToExcalidrawMap: { [key: string]: string } = {
'en': 'en-US',
@@ -104,6 +105,7 @@ export let {
refreshTextDimensions,
getCSSFontDefinition,
loadSceneFonts,
loadMermaid,
} = excalidrawLib;
export function updateExcalidrawLib() {
@@ -129,6 +131,7 @@ export function updateExcalidrawLib() {
refreshTextDimensions,
getCSSFontDefinition,
loadSceneFonts,
loadMermaid,
} = excalidrawLib);
}
@@ -152,7 +155,12 @@ export const DEVICE: DeviceType = {
isAndroid: document.body.hasClass("is-android"),
};
export const ROOTELEMENTSIZE = (() => {
export let ROOTELEMENTSIZE: number = 16;
export function setRootElementSize(size?:number) {
if(size) {
ROOTELEMENTSIZE = size;
return;
}
const tempElement = document.createElement('div');
tempElement.style.fontSize = '1rem';
tempElement.style.display = 'none'; // Hide the element
@@ -161,7 +169,7 @@ export const ROOTELEMENTSIZE = (() => {
const pixelSize = parseFloat(computedStyle.fontSize);
document.body.removeChild(tempElement);
return pixelSize;
})();
};
export const nanoid = customAlphabet(
"1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",

View File

@@ -170,8 +170,8 @@ function RenderObsidianView(
containerRef.current.parentElement.style.padding = "";
const doc = view.ownerDocument;
const rootSplit:WorkspaceSplit = new (WorkspaceSplit as ConstructableWorkspaceSplit)(app.workspace, "vertical");
rootSplit.getRoot = () => app.workspace[doc === document ? 'rootSplit' : 'floatingSplit'];
const rootSplit:WorkspaceSplit = new (WorkspaceSplit as ConstructableWorkspaceSplit)(view.app.workspace, "vertical");
rootSplit.getRoot = () => view.app.workspace[doc === document ? 'rootSplit' : 'floatingSplit'];
rootSplit.getContainer = () => getContainerForDocument(doc);
rootSplit.containerEl.style.width = '100%';
rootSplit.containerEl.style.height = '100%';
@@ -183,7 +183,7 @@ function RenderObsidianView(
};
const setKeepOnTop = () => {
const keepontop = (app.workspace.activeLeaf === view.leaf) && DEVICE.isDesktop;
const keepontop = (view.app.workspace.activeLeaf === view.leaf) && DEVICE.isDesktop;
if (keepontop) {
//@ts-ignore
if(!view.ownerWindow.electronWindow.isAlwaysOnTop()) {
@@ -315,7 +315,6 @@ function RenderObsidianView(
const canvasNode = containerRef.current;
if(!canvasNode.hasClass("canvas-node")) return;
setColors(canvasNode, element, mdProps, canvasColor);
console.log("Setting colors");
}, [
mdProps?.useObsidianDefaults,
mdProps?.backgroundMatchCanvas,

View File

@@ -5,7 +5,7 @@ export const showFrameSettings = (ea: ExcalidrawAutomate) => {
const {enabled, clip, name, outline} = ea.getExcalidrawAPI().getAppState().frameRendering;
// Create modal dialog
const frameSettingsModal = new ea.obsidian.Modal(app);
const frameSettingsModal = new ea.obsidian.Modal(ea.plugin.app);
frameSettingsModal.onOpen = () => {
const {contentEl} = frameSettingsModal;

View File

@@ -17,6 +17,24 @@ 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://storage.ko-fi.com/cdn/kofi6.png?v=6" border="0" alt="Buy Me a Coffee at ko-fi.com" height=45></a></div>
`,
"2.7.0":`
## Fixed
- Various Markdown embeddable "fuzziness":
- Fixed issues with appearance settings and edit mode toggling when single-click editing is enabled.
- Ensured embeddable file editing no longer gets interrupted unexpectedly.
- **Hover Preview**: Disabled hover preview for back-of-the-note cards to reduce distractions.
- **Settings Save**: Fixed an issue where plugin settings unnecessarily saved on every startup.
## New Features
- **Image Cropping Snaps to Objects**: When snapping is enabled in the scene, image cropping now aligns to nearby objects.
- **Session Persistence for Pen Mode**: Excalidraw remembers the last pen mode when switching between drawings within the same session.
## Refactoring
- **Mermaid Diagrams**: Excalidraw now uses its own Mermaid package, breaking future dependencies on Obsidian's Mermaid updates. This ensures stability and includes all fixes and improvements made to Excalidraw Mermaid since February 2024. The plugin file size has increased slightly, but this change significantly improves maintainability while remaining invisible to users.
- **MathJax Optimization**: MathJax (LaTeX equation SVG image generation) now loads only on demand, with the package compressed to minimize the startup and file size impact caused by the inclusion of Mermaid.
- **On-Demand Language Loading**: Non-English language files are now compressed and load only when needed, counterbalancing the increase in file size due to Mermaid and improving load speeds.
- **Codebase Restructuring**: Improved type safety by removing many ${String.fromCharCode(96)}//@ts-ignore${String.fromCharCode(96)} commands and enhancing modularity. Introduced new management classes: **CommandManager**, **EventManager**, **PluginFileManager**, **ObserverManager**, and **PackageManager**. Further restructuring is planned for upcoming releases to improve maintainability and stability.
`,
"2.6.8":`
## New
- **QoL improvements**:

View File

@@ -53,7 +53,7 @@ export class PenSettingsModal extends Modal {
private view: ExcalidrawView,
private pen: number,
) {
super(app);
super(plugin.app);
this.api = view.excalidrawAPI;
}

View File

@@ -176,7 +176,7 @@ export class UniversalInsertFileModal extends Modal {
button
.setButtonText("as Embeddable")
.onClick(async () => {
const path = app.metadataCache.fileToLinktext(
const path = this.app.metadataCache.fileToLinktext(
file,
this.view.file.path,
file.extension === "md",

View File

@@ -1,7 +1,32 @@
//Solution copied from obsidian-kanban: https://github.com/mgmeyers/obsidian-kanban/blob/44118e25661bff9ebfe54f71ae33805dc88ffa53/src/lang/helpers.ts
import { moment } from "obsidian";
import { errorlog } from "src/utils/Utils";
import { LOCALE } from "src/constants/constants";
import en from "./locale/en";
declare const PLUGIN_LANGUAGES: Record<string, string>;
declare var LZString: any;
let locale: Partial<typeof en> | null = null;
function loadLocale(lang: string): Partial<typeof en> {
if (Object.keys(PLUGIN_LANGUAGES).includes(lang)) {
const decompressed = LZString.decompressFromBase64(PLUGIN_LANGUAGES[lang]);
let x = {};
eval(decompressed);
return x;
} else {
return en;
}
}
export function t(str: keyof typeof en): string {
if (!locale) {
locale = loadLocale(LOCALE);
}
return (locale && locale[str]) || en[str];
}
/*
import ar from "./locale/ar";
import cz from "./locale/cz";
import da from "./locale/da";
@@ -51,11 +76,4 @@ const localeMap: { [k: string]: Partial<typeof en> } = {
tr,
"zh-cn": zhCN,
"zh-tw": zhTW,
};
const locale = localeMap[LOCALE];
export function t(str: keyof typeof en): string {
return (locale && locale[str]) || en[str];
}
};*/

View File

@@ -1,12 +1,11 @@
import { FILE } from "dns";
import {
DEVICE,
FRONTMATTER_KEYS,
CJK_FONTS,
} from "src/constants/constants";
import { TAG_AUTOEXPORT, TAG_MDREADINGMODE, TAG_PDFEXPORT } from "src/constants/constSettingsTags";
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/ModifierkeyHelper";
const CJK_FONTS = "CJK Fonts";
declare const PLUGIN_VERSION:string;
// English

View File

@@ -1,7 +1,4 @@
import {
DEVICE,
FRONTMATTER_KEYS,
} from "src/constants/constants";
import { DEVICE, FRONTMATTER_KEYS, CJK_FONTS } from "src/constants/constants";
import { TAG_AUTOEXPORT, TAG_MDREADINGMODE, TAG_PDFEXPORT } from "src/constants/constSettingsTags";
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/ModifierkeyHelper";

View File

@@ -1,12 +1,6 @@
import { FILE } from "dns";
import {
DEVICE,
FRONTMATTER_KEYS,
} from "src/constants/constants";
import { DEVICE, FRONTMATTER_KEYS, CJK_FONTS } from "src/constants/constants";
import { TAG_AUTOEXPORT, TAG_MDREADINGMODE, TAG_PDFEXPORT } from "src/constants/constSettingsTags";
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/ModifierkeyHelper";
const CJK_FONTS = "CJK Fonts";
declare const PLUGIN_VERSION:string;
// 简体中文

File diff suppressed because it is too large Load Diff

View File

@@ -71,7 +71,7 @@ export class EmbeddableMenu {
private async actionMarkdownSelection (file: TFile, isExcalidrawFile: boolean, subpath: string, element: ExcalidrawEmbeddableElement) {
this.view.updateScene({appState: {activeEmbeddable: null}, storeAction: "update"});
const sections = (await app.metadataCache.blockCache
const sections = (await this.view.app.metadataCache.blockCache
.getForFile({ isCancelled: () => false },file))
.blocks.filter((b: any) => b.display && b.node?.type === "heading")
.filter((b: any) => !isExcalidrawFile || !MD_EX_SECTIONS.includes(b.display));
@@ -88,7 +88,7 @@ export class EmbeddableMenu {
);
}
const newSubpath = await ScriptEngine.suggester(
app, display, values, "Select section from document"
this.view.app, display, values, "Select section from document"
);
if(!newSubpath && newSubpath!=="") return;
if (newSubpath !== subpath) {
@@ -99,7 +99,7 @@ export class EmbeddableMenu {
private async actionMarkdownBlock (file: TFile, subpath: string, element: ExcalidrawEmbeddableElement) {
if(!file) return;
this.view.updateScene({appState: {activeEmbeddable: null}, storeAction: "update"});
const paragraphs = (await app.metadataCache.blockCache
const paragraphs = (await this.view.app.metadataCache.blockCache
.getForFile({ isCancelled: () => false },file))
.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")
@@ -109,7 +109,7 @@ export class EmbeddableMenu {
paragraphs.map((b: any) => `${b.node?.id ? `#^${b.node.id}: ` : ``}${b.display.trim()}`));
const selectedBlock = await ScriptEngine.suggester(
app, display, values, "Select section from document"
this.view.app, display, values, "Select section from document"
);
if(!selectedBlock) return;
@@ -125,7 +125,7 @@ export class EmbeddableMenu {
const offset = selectedBlock.node?.position?.end?.offset;
if(!offset) return;
blockID = nanoid();
const fileContents = await app.vault.cachedRead(file);
const fileContents = await this.view.app.vault.cachedRead(file);
if(!fileContents) return;
await this.view.app.vault.modify(file, fileContents.slice(0, offset) + ` ^${blockID}` + fileContents.slice(offset));
await sleep(200); //wait for cache to update

View File

@@ -10,7 +10,7 @@ import {
TextComponent,
TFile,
} from "obsidian";
import { GITHUB_RELEASES } from "./constants/constants";
import { GITHUB_RELEASES, setRootElementSize } from "./constants/constants";
import { t } from "./lang/helpers";
import type ExcalidrawPlugin from "./main";
import { PenStyle } from "./types/PenTypes";
@@ -521,8 +521,10 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
async hide() {
if(this.plugin.settings.overrideObsidianFontSize) {
document.documentElement.style.fontSize = "";
setRootElementSize(16);
} else if(!document.documentElement.style.fontSize) {
document.documentElement.style.fontSize = getComputedStyle(document.body).getPropertyValue("--font-text-size");
setRootElementSize();
}
this.plugin.settings.scriptFolderPath = normalizePath(

41
src/types/types.d.ts vendored
View File

@@ -49,6 +49,9 @@ declare global {
ReactDOM?: any;
ExcalidrawLib?: any;
}
interface File {
path?: string;
}
}
declare module "obsidian" {
@@ -59,6 +62,24 @@ declare module "obsidian" {
metadataTypeManager: {
setType(name:string, type:string): void;
};
plugins: {
plugins: {
[key: string]: Plugin | undefined;
};
};
}
interface FileManager {
promptForFileRename(file: TFile): Promise<void>;
}
interface FileView {
_loaded: boolean;
headerEl: HTMLElement;
}
interface TextFileView {
lastSavedData: string;
}
interface Menu {
items: MenuItem[];
}
interface Keymap {
getRootScope(): Scope;
@@ -66,6 +87,16 @@ declare module "obsidian" {
interface Scope {
keys: any[];
}
interface WorkspaceLeaf {
id: string;
containerEl: HTMLDivElement;
tabHeaderInnerTitleEl: HTMLDivElement;
tabHeaderInnerIconEl: HTMLDivElement;
}
interface WorkspaceWindowInitData {
x?: number;
y?: number;
}
interface Workspace {
on(
name: "hover-link",
@@ -99,5 +130,15 @@ declare module "obsidian" {
interface MetadataCache {
getBacklinksForFile(file: TFile): any;
getLinks(): { [id: string]: Array<{ link: string; displayText: string; original: string; position: any }> };
getCachedFiles(): string[];
}
interface HoverPopover {
containerEl: HTMLElement;
hide(): void;
}
interface Plugin {
_loaded: boolean;
}
}

View File

@@ -57,10 +57,10 @@ export class CanvasNodeFactory {
await canvasPlugin.load();
}
const doc = this.view.ownerDocument;
const rootSplit:WorkspaceSplit = new (WorkspaceSplit as ConstructableWorkspaceSplit)(app.workspace, "vertical");
rootSplit.getRoot = () => app.workspace[doc === document ? 'rootSplit' : 'floatingSplit'];
const rootSplit:WorkspaceSplit = new (WorkspaceSplit as ConstructableWorkspaceSplit)(this.view.app.workspace, "vertical");
rootSplit.getRoot = () => this.view.app.workspace[doc === document ? 'rootSplit' : 'floatingSplit'];
rootSplit.getContainer = () => getContainerForDocument(doc);
this.leaf = app.workspace.createLeafInParent(rootSplit, 0);
this.leaf = this.view.app.workspace.createLeafInParent(rootSplit, 0);
this.canvas = canvasPlugin.views.canvas(this.leaf).canvas;
this.initialized = true;
}

View File

@@ -7,6 +7,7 @@ import { getEA } from "src";
import { ExcalidrawAutomate, cloneElement } from "src/ExcalidrawAutomate";
import { ExportSettings } from "src/ExcalidrawView";
import { nanoid } from "src/constants/constants";
import { svgToBase64 } from "./Utils";
export class CropImage {
private imageEA: ExcalidrawAutomate;
@@ -170,7 +171,7 @@ export class CropImage {
1 // image quality (0 - 1)
);
};
image.src = `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svgData)))}`;
image.src = svgToBase64(svgData);
});
}

View File

@@ -37,7 +37,7 @@ export const processLinkText = (linkText: string, view:ExcalidrawView): { subpat
return {subpath, file: null};
}
const file = app.metadataCache.getFirstLinkpathDest(
const file = view.app.metadataCache.getFirstLinkpathDest(
linkText,
view.file.path,
);

View File

@@ -1,6 +1,6 @@
import { DataURL } from "@zsviczian/excalidraw/types/excalidraw/types";
import { App, loadPdfJs, normalizePath, Notice, requestUrl, RequestUrlResponse, TAbstractFile, TFile, TFolder, Vault } from "obsidian";
import { DEVICE, FRONTMATTER_KEYS, URLFETCHTIMEOUT } from "src/constants/constants";
import { DEVICE, EXCALIDRAW_PLUGIN, FRONTMATTER_KEYS, URLFETCHTIMEOUT } from "src/constants/constants";
import { IMAGE_MIME_TYPES, MimeType } from "src/EmbeddedFileLoader";
import { ExcalidrawSettings } from "src/settings";
import { errorlog, getDataURL } from "./Utils";
@@ -142,7 +142,7 @@ export function getEmbedFilename(
* @param folderpath
*/
export async function checkAndCreateFolder(folderpath: string):Promise<TFolder> {
const vault = app.vault;
const vault = EXCALIDRAW_PLUGIN.app.vault;
folderpath = normalizePath(folderpath);
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/658
//@ts-ignore
@@ -292,7 +292,7 @@ export const blobToBase64 = async (blob: Blob): Promise<string> => {
export const getPDFDoc = async (f: TFile): Promise<any> => {
if(typeof window.pdfjsLib === "undefined") await loadPdfJs();
return await window.pdfjsLib.getDocument(app.vault.getResourcePath(f)).promise;
return await window.pdfjsLib.getDocument(EXCALIDRAW_PLUGIN.app.vault.getResourcePath(f)).promise;
}
export const readLocalFile = async (filePath:string): Promise<string> => {
@@ -330,9 +330,14 @@ export const getPathWithoutExtension = (f:TFile): string => {
return f.path.substring(0, f.path.lastIndexOf("."));
}
const VAULT_BASE_URL = DEVICE.isDesktop
? app.vault.adapter.url.pathToFileURL(app.vault.adapter.basePath).toString()
let _VAULT_BASE_URL:string = null;
const VAULT_BASE_URL = () => {
if(_VAULT_BASE_URL) return _VAULT_BASE_URL;
_VAULT_BASE_URL = DEVICE.isDesktop
? EXCALIDRAW_PLUGIN.app.vault.adapter.url.pathToFileURL(EXCALIDRAW_PLUGIN.app.vault.adapter.basePath).toString()
: "";
return _VAULT_BASE_URL;
}
export const getInternalLinkOrFileURLLink = (
path: string, plugin:ExcalidrawPlugin, alias?: string, sourceFile?: TFile
@@ -344,8 +349,10 @@ export const getInternalLinkOrFileURLLink = (
}
const vault = plugin.app.vault;
const fileURLString = vault.adapter.url.pathToFileURL(path).toString();
if (fileURLString.startsWith(VAULT_BASE_URL)) {
const internalPath = normalizePath(unescape(fileURLString.substring(VAULT_BASE_URL.length)));
if (fileURLString.startsWith(VAULT_BASE_URL())) {
const internalPath = normalizePath(
decodeURIComponent(fileURLString.substring(VAULT_BASE_URL().length))
);
const file = vault.getAbstractFileByPath(internalPath);
if(file && file instanceof TFile) {
const link = plugin.app.metadataCache.fileToLinktext(

View File

@@ -8,7 +8,7 @@ import {
import ExcalidrawPlugin from "../main";
import { checkAndCreateFolder, splitFolderAndFilename } from "./FileUtils";
import { linkClickModifierType, ModifierKeys } from "./ModifierkeyHelper";
import { REG_BLOCK_REF_CLEAN, REG_SECTION_REF_CLEAN, VIEW_TYPE_EXCALIDRAW } from "src/constants/constants";
import { EXCALIDRAW_PLUGIN, REG_BLOCK_REF_CLEAN, REG_SECTION_REF_CLEAN, VIEW_TYPE_EXCALIDRAW } from "src/constants/constants";
import yaml from "js-yaml";
import { debug, DEBUGGING } from "./DebugHelper";
import ExcalidrawView from "src/ExcalidrawView";
@@ -36,7 +36,7 @@ export const getLeaf = (
ev: ModifierKeys
) => {
const newTab = ():WorkspaceLeaf => {
if(!plugin.settings.openInMainWorkspace) return app.workspace.getLeaf('tab');
if(!plugin.settings.openInMainWorkspace) return plugin.app.workspace.getLeaf('tab');
const [leafLoc, mainLeavesIds] = getLeafLoc(origo);
if(leafLoc === 'main') return plugin.app.workspace.getLeaf('tab');
return getNewOrAdjacentLeaf(plugin,origo);
@@ -56,7 +56,7 @@ export const getLeaf = (
const getLeafLoc = (leaf: WorkspaceLeaf): ["main" | "popout" | "left" | "right" | "hover",any] => {
//@ts-ignore
const leafId = leaf.id;
const layout = app.workspace.getLayout();
const layout = EXCALIDRAW_PLUGIN.app.workspace.getLayout();
const getLeaves = (l:any)=> l.children
.filter((c:any)=>c.type!=="leaf")
.map((c:any)=>getLeaves(c))
@@ -95,7 +95,7 @@ export const getNewOrAdjacentLeaf = (
const [leafLoc, mainLeavesIds] = getLeafLoc(leaf);
const getMostRecentOrAvailableLeafInMainWorkspace = (inDifferentTabGroup?: boolean):WorkspaceLeaf => {
let mainLeaf = app.workspace.getMostRecentLeaf();
let mainLeaf = plugin.app.workspace.getMostRecentLeaf();
if(mainLeaf && mainLeaf !== leaf && mainLeaf.view?.containerEl.ownerDocument === document) {
//Found a leaf in the main workspace that is not the originating leaf
return mainLeaf;
@@ -104,7 +104,7 @@ export const getNewOrAdjacentLeaf = (
mainLeaf = null;
mainLeavesIds
.forEach((id:any)=> {
const l = app.workspace.getLeafById(id);
const l = plugin.app.workspace.getLeafById(id);
if(mainLeaf ||
!l.view?.navigation ||
leaf === l ||
@@ -121,28 +121,28 @@ export const getNewOrAdjacentLeaf = (
//1.1 - Create new leaf in main workspace
if(!plugin.settings.openInAdjacentPane) {
if(leafLoc === "main") {
return app.workspace.createLeafBySplit(leaf);
return plugin.app.workspace.createLeafBySplit(leaf);
}
const ml = getMostRecentOrAvailableLeafInMainWorkspace();
return ml
? (ml.view.getViewType() === "empty" ? ml : app.workspace.createLeafBySplit(ml))
: app.workspace.getLeaf(true);
? (ml.view.getViewType() === "empty" ? ml : plugin.app.workspace.createLeafBySplit(ml))
: plugin.app.workspace.getLeaf(true);
}
//1.2 - Reuse leaf if it is adjacent
const ml = getMostRecentOrAvailableLeafInMainWorkspace(true);
return ml ?? app.workspace.createLeafBySplit(leaf); //app.workspace.getLeaf(true);
return ml ?? plugin.app.workspace.createLeafBySplit(leaf); //app.workspace.getLeaf(true);
}
//2
if(!plugin.settings.openInAdjacentPane) {
return app.workspace.createLeafBySplit(leaf);
return plugin.app.workspace.createLeafBySplit(leaf);
}
//3
if(leafLoc === "hover") {
const leaves = new Set<WorkspaceLeaf>();
app.workspace.iterateAllLeaves(l=>{
plugin.app.workspace.iterateAllLeaves(l=>{
//@ts-ignore
if(l!==leaf && leaf.containerEl.parentElement === l.containerEl.parentElement) leaves.add(l);
})
@@ -155,13 +155,13 @@ export const getNewOrAdjacentLeaf = (
//4
if(leafLoc === "popout") {
const popoutLeaves = new Set<WorkspaceLeaf>();
app.workspace.iterateAllLeaves(l=>{
plugin.app.workspace.iterateAllLeaves(l=>{
if(l !== leaf && l.view.navigation && l.view.containerEl.ownerDocument === leaf.view.containerEl.ownerDocument) {
popoutLeaves.add(l);
}
});
if(popoutLeaves.size === 0) {
return app.workspace.createLeafBySplit(leaf);
return plugin.app.workspace.createLeafBySplit(leaf);
}
return Array.from(popoutLeaves)[0];
}
@@ -201,12 +201,12 @@ export const isObsidianThemeDark = () => document.body.classList.contains("theme
export type ConstructableWorkspaceSplit = new (ws: Workspace, dir: "horizontal"|"vertical") => WorkspaceSplit;
export const getContainerForDocument = (doc:Document) => {
if (doc !== document && app.workspace.floatingSplit) {
for (const container of app.workspace.floatingSplit.children) {
if (doc !== document && EXCALIDRAW_PLUGIN.app.workspace.floatingSplit) {
for (const container of EXCALIDRAW_PLUGIN.app.workspace.floatingSplit.children) {
if (container.doc === doc) return container;
}
}
return app.workspace.rootSplit;
return EXCALIDRAW_PLUGIN.app.workspace.rootSplit;
};
export const cleanSectionHeading = (heading:string) => {

View File

@@ -230,11 +230,17 @@ export function base64StringToBlob (base64String: string, mimeType: string): Blo
return new Blob([buffer], { type: mimeType });
};
export function svgToBase64 (svg: string): string {
return `data:image/svg+xml;base64,${btoa(
unescape(encodeURIComponent(svg.replaceAll("&nbsp;", " "))),
)}`;
};
export function svgToBase64(svg: string): string {
const cleanSvg = svg.replaceAll("&nbsp;", " ");
// Convert the string to UTF-8 and handle non-Latin1 characters
const encodedData = encodeURIComponent(cleanSvg)
.replace(/%([0-9A-F]{2})/g,
(match, p1) => String.fromCharCode(parseInt(p1, 16))
);
return `data:image/svg+xml;base64,${btoa(encodedData)}`;
}
export async function getBinaryFileFromDataURL (dataURL: string): Promise<ArrayBuffer> {
if (!dataURL) {
@@ -533,7 +539,7 @@ export function setDocLeftHandedMode(isLeftHanded: boolean, ownerDocument:Docume
export function setLeftHandedMode (isLeftHanded: boolean) {
const visitedDocs = new Set<Document>();
app.workspace.iterateAllLeaves((leaf) => {
EXCALIDRAW_PLUGIN.app.workspace.iterateAllLeaves((leaf) => {
const ownerDocument = DEVICE.isMobile?document:leaf.view.containerEl.ownerDocument;
if(!ownerDocument) return;
if(visitedDocs.has(ownerDocument)) return;