Compare commits

..

2 Commits
2.2.8 ... 2.2.9

Author SHA1 Message Date
zsviczian
53c27f2a59 2.2.9 2024-07-13 17:34:19 +02:00
zsviczian
db80f5c715 allows opacity 0% 2024-07-09 16:45:06 +02:00
12 changed files with 300 additions and 183 deletions

View File

@@ -103,7 +103,7 @@ const selectAttributesToCopy = () => {
attributes.forEach(attr => {
const attrValue = elements[0][attr.key];
if(attrValue || (attr.key === "startArrowhead" && elements[0].type === "arrow") || (attr.key === "endArrowhead" && elements[0].type === "arrow")) {
if((typeof attrValue !== "undefined" && attrValue !== null) || (attr.key === "startArrowhead" && elements[0].type === "arrow") || (attr.key === "endArrowhead" && elements[0].type === "arrow")) {
let description = '';
switch(attr.key) {
@@ -190,7 +190,9 @@ const selectAttributesToCopy = () => {
configModal.onClose = () => {
setTimeout(()=>delete configModal);
setTimeout(()=>{
delete configModal
});
}
configModal.open();

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -127,7 +127,7 @@ import { anyModifierKeysPressed, emulateKeysForLinkClick, webbrowserDragModifier
import { setDynamicStyle } from "./utils/DynamicStyling";
import { InsertPDFModal } from "./dialogs/InsertPDFModal";
import { CustomEmbeddable, renderWebView } from "./customEmbeddable";
import { addBackOfTheNoteCard, getExcalidrawFileForwardLinks, getFrameBasedOnFrameNameOrId, getLinkTextFromLink, insertEmbeddableToView, insertImageToView, openExternalLink, openTagSearch, renderContextMenuAction, tmpBruteForceCleanup } from "./utils/ExcalidrawViewUtils";
import { addBackOfTheNoteCard, getExcalidrawFileForwardLinks, getFrameBasedOnFrameNameOrId, getLinkTextFromLink, insertEmbeddableToView, insertImageToView, openExternalLink, openTagSearch, parseObsidianLink, renderContextMenuAction, tmpBruteForceCleanup } from "./utils/ExcalidrawViewUtils";
import { imageCache } from "./utils/ImageCache";
import { CanvasNodeFactory, ObsidianCanvasNode } from "./utils/CanvasNodeFactory";
import { EmbeddableMenu } from "./menu/EmbeddableActionsMenu";
@@ -640,10 +640,9 @@ export default class ExcalidrawView extends TextFileView {
}
const reader = new FileReader();
reader.readAsDataURL(png);
const self = this;
reader.onloadend = function () {
reader.onloadend = () => {
const base64data = reader.result;
download(null, base64data, `${self.file.basename}.png`);
download(null, base64data, `${this.file.basename}.png`);
};
}
@@ -683,12 +682,17 @@ export default class ExcalidrawView extends TextFileView {
}
async save(preventReload: boolean = true, forcesave: boolean = false, overrideEmbeddableIsEditingSelfDebounce: boolean = false) {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.save, "ExcalidrawView.save, enter", preventReload, forcesave);
if ((process.env.NODE_ENV === 'development')) {
if (DEBUGGING) {
debug(this.save, "ExcalidrawView.save, enter", preventReload, forcesave);
console.trace();
}
}
/*if(this.semaphores.viewunload && (this.ownerWindow !== window)) {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.save, `ExcalidrawView.save, view is unloading, aborting save`);
return;
}*/
if(!this.isLoaded) {
return;
}
@@ -759,6 +763,12 @@ export default class ExcalidrawView extends TextFileView {
}
await super.save();
if (process.env.NODE_ENV === 'development') {
if (DEBUGGING) {
debug(this.save, `ExcalidrawView.save, super.save finished`, this.file);
console.trace();
}
}
//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
@@ -1026,7 +1036,12 @@ export default class ExcalidrawView extends TextFileView {
? this.excalidrawData.getRawText(selectedText.id)
: selectedText.text);
const partsArray = REGEX_LINK.getResList(linkText);
const maybeObsidianLink = parseObsidianLink(linkText, this.app);
if(typeof maybeObsidianLink === "string") {
linkText = maybeObsidianLink;
}
const partsArray = REGEX_LINK.getResList(linkText);
if (!linkText || partsArray.length === 0) {
//the container link takes precedence over the text link
if(selectedTextElement?.containerId) {
@@ -1074,6 +1089,12 @@ export default class ExcalidrawView extends TextFileView {
if(this.handleLinkHookCall(selectedElement,linkText,ev)) return;
if(openExternalLink(linkText, this.app)) return;
const maybeObsidianLink = parseObsidianLink(linkText,this.app);
if (typeof maybeObsidianLink === "boolean" && maybeObsidianLink) return;
if (typeof maybeObsidianLink === "string") {
linkText = maybeObsidianLink;
}
const result = await linkPrompt(linkText, this.app, this);
if(!result) return;
[file, linkText, subpath] = result;
@@ -1233,6 +1254,8 @@ export default class ExcalidrawView extends TextFileView {
console.error(e);
}
//if link will open in the same pane I want to save the drawing before opening the link
await this.forceSaveIfRequired();
const {leaf, promise} = openLeaf({
plugin: this.plugin,
fnGetLeaf: () => getLeaf(this.plugin,this.leaf,keys),
@@ -1470,20 +1493,21 @@ export default class ExcalidrawView extends TextFileView {
this.offsetLeft = parent.offsetLeft;
this.offsetTop = parent.offsetTop;
const self = this;
//triggers when the leaf is moved in the workspace
const observerFn = async (m: MutationRecord[]) => {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(observerFn, `ExcalidrawView.parentMoveObserver, file:${self.file?.name}`);
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(observerFn, `ExcalidrawView.parentMoveObserver, file:${this.file?.name}`);
const target = m[0].target;
if (!(target instanceof HTMLElement)) {
return;
}
const { offsetLeft, offsetTop } = target;
if (offsetLeft !== self.offsetLeft || offsetTop !== self.offsetTop) {
if (self.excalidrawAPI) {
self.refreshCanvasOffset();
if (offsetLeft !== this.offsetLeft || offsetTop !== this.offsetTop) {
if (this.excalidrawAPI) {
this.refreshCanvasOffset();
}
self.offsetLeft = offsetLeft;
self.offsetTop = offsetTop;
this.offsetLeft = offsetLeft;
this.offsetTop = offsetTop;
}
};
this.parentMoveObserver = DEBUGGING
@@ -1602,9 +1626,8 @@ export default class ExcalidrawView extends TextFileView {
this.autosaveTimer = null;
if (this.excalidrawAPI) {
this.semaphores.autosaving = true;
const self = this;
//changed from await to then to avoid lag during saving of large file
this.save().then(()=>self.semaphores.autosaving = false);
this.save().then(()=>this.semaphores.autosaving = false);
}
this.autosaveTimer = window.setTimeout(
timer,
@@ -1652,9 +1675,16 @@ export default class ExcalidrawView extends TextFileView {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onUnloadFile,`ExcalidrawView.onUnloadFile, file:${this.file?.name}`);
}
//onClose happens after onunload
protected async onClose(): Promise<void> {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onClose,`ExcalidrawView.onClose, file:${this.file?.name}`);
private async forceSaveIfRequired():Promise<boolean> {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.forceSaveIfRequired,`ExcalidrawView.forceSaveIfRequired`);
let watchdog = 0;
let dirty = false;
//if saving was already in progress
//the function awaits the save to finish.
while (this.semaphores.saving && watchdog++ < 10) {
dirty = true;
await sleep(20);
}
if(this.excalidrawAPI) {
this.checkSceneVersion(this.excalidrawAPI.getSceneElements());
if(this.isDirty()) {
@@ -1663,11 +1693,18 @@ export default class ExcalidrawView extends TextFileView {
window.setTimeout(() => {
plugin.triggerEmbedUpdates(path)
},400);
dirty = true;
await this.save(true,true,true);
}
}
return dirty;
}
//onClose happens after onunload
protected async onClose(): Promise<void> {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onClose,`ExcalidrawView.onClose, file:${this.file?.name}`);
await this.forceSaveIfRequired();
if (this.excalidrawRoot) {
this.excalidrawRoot.unmount();
this.excalidrawRoot = null;
@@ -1858,7 +1895,7 @@ export default class ExcalidrawView extends TextFileView {
if (!state) {
return;
}
const self = this;
let query: string[] = null;
if (
@@ -1879,10 +1916,10 @@ export default class ExcalidrawView extends TextFileView {
const waitForExcalidraw = async () => {
let counter = 0;
while (
(self.semaphores.justLoaded ||
!self.isLoaded ||
!self.excalidrawAPI ||
self.excalidrawAPI?.getAppState()?.isLoading) &&
(this.semaphores.justLoaded ||
!this.isLoaded ||
!this.excalidrawAPI ||
this.excalidrawAPI?.getAppState()?.isLoading) &&
counter++<100
) await sleep(50); //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/734
}
@@ -1898,16 +1935,16 @@ export default class ExcalidrawView extends TextFileView {
window.setTimeout(async () => {
await waitForExcalidraw();
if(filenameParts.blockref && !filenameParts.hasGroupref) {
if(!self.getScene()?.elements.find((el:ExcalidrawElement)=>el.id === filenameParts.blockref)) {
if(!this.getScene()?.elements.find((el:ExcalidrawElement)=>el.id === filenameParts.blockref)) {
const cleanQuery = cleanSectionHeading(filenameParts.blockref).replaceAll(" ","");
const blocks = await self.getBackOfTheNoteBlocks();
const blocks = await this.getBackOfTheNoteBlocks();
if(blocks.includes(cleanQuery)) {
this.setMarkdownView(state);
return;
}
}
}
window.setTimeout(()=>self.zoomToElementId(filenameParts.blockref, filenameParts.hasGroupref));
window.setTimeout(()=>this.zoomToElementId(filenameParts.blockref, filenameParts.hasGroupref));
});
}
@@ -1921,7 +1958,7 @@ export default class ExcalidrawView extends TextFileView {
window.setTimeout(async () => {
await waitForExcalidraw();
const api = self.excalidrawAPI;
const api = this.excalidrawAPI;
if (!api) return;
if (api.getAppState().isLoading) return;
@@ -1933,17 +1970,17 @@ export default class ExcalidrawView extends TextFileView {
if(parts) {
const linkText = REGEX_LINK.getLink(parts);
if(linkText) {
const file = self.plugin.app.metadataCache.getFirstLinkpathDest(linkText, self.file.path);
const file = this.plugin.app.metadataCache.getFirstLinkpathDest(linkText, this.file.path);
if(file) {
let fileId:FileId[] = [];
self.excalidrawData.files.forEach((ef,fileID) => {
this.excalidrawData.files.forEach((ef,fileID) => {
if(ef.file?.path === file.path) fileId.push(fileID);
});
if(fileId.length>0) {
const images = elements.filter(el=>el.type === "image" && fileId.includes(el.fileId));
if(images.length>0) {
this.preventAutozoom();
window.setTimeout(()=>self.zoomToElements(!api.getAppState().viewModeEnabled, images));
window.setTimeout(()=>this.zoomToElements(!api.getAppState().viewModeEnabled, images));
}
}
}
@@ -1951,24 +1988,24 @@ export default class ExcalidrawView extends TextFileView {
}
}
if(!self.selectElementsMatchingQuery(
if(!this.selectElementsMatchingQuery(
elements,
query,
!api.getAppState().viewModeEnabled,
filenameParts.hasSectionref,
filenameParts.hasGroupref
)) {
const cleanQuery = cleanSectionHeading(query[0]).replaceAll(" ","");
const sections = await self.getBackOfTheNoteSections();
const cleanQuery = cleanSectionHeading(query[0]);
const sections = await this.getBackOfTheNoteSections();
if(sections.includes(cleanQuery)) {
self.setMarkdownView(state);
this.setMarkdownView(state);
return;
}
}
});
}
super.setEphemeralState(state);
//super.setEphemeralState(state);
}
// clear the view content
@@ -2021,41 +2058,40 @@ 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");
const self = this;
this.app.workspace.onLayoutReady(async () => {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(self.setViewData, `ExcalidrawView.setViewData > app.workspace.onLayoutReady, file:${self.file?.name}, isActiveLeaf:${self?.app?.workspace?.activeLeaf === self.leaf}`);
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.setViewData, `ExcalidrawView.setViewData > app.workspace.onLayoutReady, file:${this.file?.name}, isActiveLeaf:${this?.app?.workspace?.activeLeaf === this.leaf}`);
//the leaf moved to a window and ExcalidrawView was destructed
//Happens during Obsidian startup if View opens in new window.
if(!self?.app) {
if(!this?.app) {
return;
}
let counter = 0;
while (!self.file && counter++<50) await sleep(50);
if(!self.file) return;
self.compatibilityMode = self.file.extension === "excalidraw";
await self.plugin.loadSettings();
if (self.compatibilityMode) {
self.plugin.enableLegacyFilePopoverObserver();
self.actionButtons['isRaw'].hide();
self.actionButtons['isParsed'].hide();
self.actionButtons['link'].hide();
self.textMode = TextMode.raw;
await self.excalidrawData.loadLegacyData(data, self.file);
if (!self.plugin.settings.compatibilityMode) {
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) {
this.plugin.enableLegacyFilePopoverObserver();
this.actionButtons['isRaw'].hide();
this.actionButtons['isParsed'].hide();
this.actionButtons['link'].hide();
this.textMode = TextMode.raw;
await this.excalidrawData.loadLegacyData(data, this.file);
if (!this.plugin.settings.compatibilityMode) {
new Notice(t("COMPATIBILITY_MODE"), 4000);
}
self.excalidrawData.disableCompression = true;
this.excalidrawData.disableCompression = true;
} else {
self.actionButtons['link'].show();
self.excalidrawData.disableCompression = false;
this.actionButtons['link'].show();
this.excalidrawData.disableCompression = false;
const textMode = getTextMode(data);
self.changeTextMode(textMode, false);
this.changeTextMode(textMode, false);
try {
if (
!(await self.excalidrawData.loadData(
!(await this.excalidrawData.loadData(
data,
self.file,
self.textMode,
this.file,
this.textMode,
))
) {
return;
@@ -2063,12 +2099,12 @@ export default class ExcalidrawView extends TextFileView {
} catch (e) {
errorlog({ where: "ExcalidrawView.setViewData", error: e });
if(e.message === ERROR_IFRAME_CONVERSION_CANCELED) {
self.setMarkdownView();
this.setMarkdownView();
return;
}
const file = self.file;
const plugin = self.plugin;
const leaf = self.leaf;
const file = this.file;
const plugin = this.plugin;
const leaf = this.leaf;
(async () => {
let confirmation:boolean = true;
let counter = 0;
@@ -2114,18 +2150,18 @@ export default class ExcalidrawView extends TextFileView {
})();
self.setMarkdownView();
this.setMarkdownView();
return;
}
}
await self.loadDrawing(true);
await this.loadDrawing(true);
if(self.plugin.ea.onFileOpenHook) {
if(this.plugin.ea.onFileOpenHook) {
const tempEA = getEA(this);
try {
await self.plugin.ea.onFileOpenHook({
await this.plugin.ea.onFileOpenHook({
ea: tempEA,
excalidrawFile: self.file,
excalidrawFile: this.file,
view: this,
});
} catch(e) {
@@ -2135,20 +2171,19 @@ export default class ExcalidrawView extends TextFileView {
}
}
const script = self.excalidrawData.getOnLoadScript();
const script = this.excalidrawData.getOnLoadScript();
if(script) {
const self = this;
const scriptname = self.file.basename+ "-onlaod-script";
const scriptname = this.file.basename+ "-onlaod-script";
const runScript = () => {
if(!self.excalidrawAPI) { //need to wait for Excalidraw to initialize
window.setTimeout(runScript,200);
if(!this.excalidrawAPI) { //need to wait for Excalidraw to initialize
window.setTimeout(runScript.bind(this),200);
return;
}
self.plugin.scriptEngine.executeScript(self,script,scriptname,self.file);
this.plugin.scriptEngine.executeScript(this,script,scriptname,this.file);
}
runScript();
}
self.isLoaded = true;
this.isLoaded = true;
});
}
@@ -2188,11 +2223,10 @@ export default class ExcalidrawView extends TextFileView {
//in case one or more files have not loaded retry later hoping that sync has delivered the file in the mean time.
this.excalidrawData.getFiles().some(ef=>{
if(ef && !ef.file && ef.attemptCounter<30) {
const self = this;
const currentFile = this.file.path;
window.setTimeout(async ()=>{
if(self && self.excalidrawAPI && currentFile === self.file.path) {
self.loadSceneFiles();
if(this && this.excalidrawAPI && currentFile === this.file.path) {
this.loadSceneFiles();
}
},2000)
return true;
@@ -2562,8 +2596,17 @@ export default class ExcalidrawView extends TextFileView {
return ICON_NAME;
}
setMarkdownView(eState?: any) {
async setMarkdownView(eState?: any) {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.setMarkdownView, "ExcalidrawView.setMarkdownView", eState);
//save before switching to markdown view.
//this would also happen onClose, but it does not hurt to save it here
//this way isDirty() will return false in onClose, thuse
//saving here will not result in double save
//there was a race condition when clicking a link with a section or block reference to the back-of-the-note
//that resulted in a call to save after the view has been destroyed
//The sleep is required for metadata cache to be updated with the location of the block or section
await this.forceSaveIfRequired();
await sleep(200); //dirty hack to wait for Obsidian metadata to be updated, note that save may have been triggered elsewhere already
this.plugin.excalidrawFileModes[this.id || this.file.path] = "markdown";
this.plugin.setMarkdownView(this.leaf, eState);
}
@@ -3393,8 +3436,7 @@ export default class ExcalidrawView extends TextFileView {
}
this.semaphores.hoverSleep = true;
const self = this;
window.setTimeout(() => (self.semaphores.hoverSleep = false), 500);
window.setTimeout(() => (this.semaphores.hoverSleep = false), 500);
this.plugin.hover.linkText = linktext;
this.plugin.hover.sourcePath = this.file.path;
this.hoverPreviewTarget = this.contentEl; //e.target;
@@ -3408,14 +3450,13 @@ export default class ExcalidrawView extends TextFileView {
});
this.hoverPoint = this.currentPosition;
if (this.isFullscreen()) {
const self = this;
window.setTimeout(() => {
const popover =
this.ownerDocument.querySelector(`div.popover-title[data-path="${f.path}"]`)
?.parentElement?.parentElement?.parentElement ??
this.ownerDocument.body.querySelector("div.popover");
if (popover) {
self.contentEl.append(popover);
this.contentEl.append(popover);
}
}, 400);
}
@@ -3719,8 +3760,7 @@ export default class ExcalidrawView extends TextFileView {
}
}
if (data.elements) {
const self = this;
window.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
window.setTimeout(() => this.save(), 30); //removed prevent reload = false, as reload was triggered when pasted containers were processed and there was a conflict with the new elements
}
return true;
}
@@ -4003,7 +4043,7 @@ export default class ExcalidrawView extends TextFileView {
if(link.file.extension === "pdf") {
const insertPDFModal = new InsertPDFModal(this.plugin, this);
insertPDFModal.open(link.file);
return false;
//return false;
}
const ea = getEA(this) as ExcalidrawAutomate;
insertImageToView(ea, pos, link.file).then(()=>ea.destroy()) ;
@@ -4065,7 +4105,7 @@ export default class ExcalidrawView extends TextFileView {
//return false;
}
}
if(localFileDragAction === "embeddable" || !IMAGE_TYPES.contains(extension)) {
else if(localFileDragAction === "embeddable" || !IMAGE_TYPES.contains(extension)) {
const ea = getEA(this) as ExcalidrawAutomate;
insertEmbeddableToView(ea, pos, null, link.url).then(()=>ea.destroy());
if(localFileDragAction !== "embeddable") {
@@ -4216,7 +4256,7 @@ export default class ExcalidrawView extends TextFileView {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onBeforeTextSubmit, "ExcalidrawView.onBeforeTextSubmit", textElement, nextText, nextOriginalText, isDeleted);
const api = this.excalidrawAPI;
if (!api) {
return {updatedNextOriginalText: null, nextLink: null};
return {updatedNextOriginalText: null, nextLink: textElement?.link ?? null};
}
// 1. Set the isEditingText flag to true to prevent autoresize on mobile
@@ -4299,7 +4339,7 @@ export default class ExcalidrawView extends TextFileView {
this.setDirty(9);
}
});
return {updatedNextOriginalText: null, nextLink: null};
return {updatedNextOriginalText: null, nextLink: textElement.link};
} else {
new Notice(t("USE_INSERT_FILE_MODAL"),5000);
}
@@ -4372,12 +4412,12 @@ export default class ExcalidrawView extends TextFileView {
//don't forget the case: link-prefix:"" && link-brackets:true
return {updatedNextOriginalText: parseResultOriginal, nextLink: link};
}
return {updatedNextOriginalText: null, nextLink: null};
return {updatedNextOriginalText: null, nextLink: textElement.link};
} //There were no links to parse, raw text and parsed text are equivalent
api.history.clear();
return {updatedNextOriginalText: parseResultOriginal, nextLink:link};
}
return {updatedNextOriginalText: null, nextLink: null};
return {updatedNextOriginalText: null, nextLink: textElement.link};
}
// even if the text did not change, container sizes might need to be updated
if (containerId) {
@@ -4387,7 +4427,7 @@ export default class ExcalidrawView extends TextFileView {
const parseResultOriginal = this.excalidrawData.getParsedText(textElement.id);
return {updatedNextOriginalText: parseResultOriginal, nextLink: textElement.link};
}
return {updatedNextOriginalText: null, nextLink: null};
return {updatedNextOriginalText: null, nextLink: textElement.link};
}
private async onLinkOpen(element: ExcalidrawElement, e: any): Promise<void> {
@@ -4404,7 +4444,8 @@ export default class ExcalidrawView extends TextFileView {
let event = e?.detail?.nativeEvent;
if(this.handleLinkHookCall(element,element.link,event)) return;
if(openExternalLink(element.link, this.app, !isSHIFT(event) && !isWinCTRLorMacCMD(event) && !isWinMETAorMacCTRL(event) && !isWinALTorMacOPT(event) ? element : undefined)) return;
//if(openExternalLink(element.link, this.app, !isSHIFT(event) && !isWinCTRLorMacCMD(event) && !isWinMETAorMacCTRL(event) && !isWinALTorMacOPT(event) ? element : undefined)) return;
if(openExternalLink(element.link, this.app)) return;
//if element is type text and element has multiple links, then submit the element text to linkClick to trigger link suggester
if(element.type === "text") {
@@ -4418,7 +4459,7 @@ export default class ExcalidrawView extends TextFileView {
if (!event.shiftKey && !event.ctrlKey && !event.metaKey && !event.altKey) {
event = emulateKeysForLinkClick("new-tab");
}
this.linkClick(
event,
null,

View File

@@ -46,7 +46,9 @@ export class InsertCommandDialog extends FuzzySuggestModal<TFile> {
}
onClose(): void {
this.addText = null;
window.setTimeout(()=>{
this.addText = null;
}) //onChooseItem must run first
super.onClose();
}
}

View File

@@ -58,7 +58,9 @@ export class InsertLinkDialog extends FuzzySuggestModal<TFile> {
}
onClose(): void {
this.addText = null;
window.setTimeout(()=>{
this.addText = null
}); //make sure this happens after onChooseItem runs
super.onClose();
}

View File

@@ -17,6 +17,19 @@ 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.2.9": `
## New
- Improved the "Open the back-of-the-note of the selected Excalidraw image" action. It now works with grouped elements and keeps the popout window within the visible screen area when elements are close to the top of the canvas. Note: Due to an Obsidian bug, I do not recommend using this feature with versions 1.6.0 - 1.6.6, if you have Obsidian Sync enabled, because Obsidian may freeze when closing the popout window. It functions properly in Obsidian versions before 1.6.0 and from 1.6.7 onwards.
## Fixed
- Drag and drop from a local folder (outside Obsidian) resulted in duplicate images.
- Insert Link Action did not work [#1873](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1873)
- Insert Obsidian Command Action did not work
- Element link for text element got deleted when editing the text. [#1878](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1878)
- When back-of-the-drawing Section Headings have spaces in them, clicking the link opens the drawing side not the markdown side. [#1877](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1877)
- obsidian:// links did not work as expected. [#1872](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1872)
- copying and moving a rectangle with text, moves the text unexpectedly. The issue should now be resolved (at least much less likely to occur) [#1867](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1867)
`,
"2.2.8": `
While this release may appear modest with no new features, it represents nearly 50 hours of dedicated work. Here's what's been improved:

View File

@@ -22,7 +22,7 @@ import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
import { MAX_IMAGE_SIZE, REG_LINKINDEX_INVALIDCHARS } from "src/constants/constants";
import { REGEX_LINK } from "src/ExcalidrawData";
import { ScriptEngine } from "src/Scripts";
import { openExternalLink, openTagSearch } from "src/utils/ExcalidrawViewUtils";
import { openExternalLink, openTagSearch, parseObsidianLink } from "src/utils/ExcalidrawViewUtils";
export type ButtonDefinition = { caption: string; tooltip?:string; action: Function };
@@ -708,7 +708,12 @@ export class ConfirmationPrompt extends Modal {
}
}
export const linkPrompt = async (linkText:string, app: App, view?: ExcalidrawView, message: string = "Select link to open"):Promise<[file:TFile, linkText:string, subpath: string]> => {
export async function linkPrompt (
linkText:string,
app: App,
view?: ExcalidrawView,
message: string = "Select link to open",
):Promise<[file:TFile, linkText:string, subpath: string]> {
const partsArray = REGEX_LINK.getResList(linkText);
let subpath: string = null;
let file: TFile = null;
@@ -737,6 +742,9 @@ export const linkPrompt = async (linkText:string, app: App, view?: ExcalidrawVie
linkText = REGEX_LINK.getLink(parts);
if(openExternalLink(linkText, app)) return;
const maybeObsidianLink = parseObsidianLink(linkText, app, false);
if (typeof maybeObsidianLink === "boolean" && maybeObsidianLink) return;
if (typeof maybeObsidianLink === "string") linkText = maybeObsidianLink;
if (linkText.search("#") > -1) {
const linkParts = getLinkParts(linkText, view ? view.file : undefined);

View File

@@ -368,11 +368,10 @@ export default class ExcalidrawPlugin extends Plugin {
this.switchToExcalidarwAfterLoad();
const self = this;
this.app.workspace.onLayoutReady(() => {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(self.onload,"ExcalidrawPlugin.onload > app.workspace.onLayoutReady");
this.scriptEngine = new ScriptEngine(self);
imageCache.initializeDB(self);
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onload,"ExcalidrawPlugin.onload > app.workspace.onLayoutReady");
this.scriptEngine = new ScriptEngine(this);
imageCache.initializeDB(this);
});
this.taskbone = new Taskbone(this);
}
@@ -380,9 +379,8 @@ export default class ExcalidrawPlugin extends Plugin {
private setPropertyTypes() {
if(!this.settings.loadPropertySuggestions) return;
const app = this.app;
const self = this;
this.app.workspace.onLayoutReady(() => {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(self.setPropertyTypes, `ExcalidrawPlugin.setPropertyTypes > app.workspace.onLayoutReady`);
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.setPropertyTypes, `ExcalidrawPlugin.setPropertyTypes > app.workspace.onLayoutReady`);
Object.keys(FRONTMATTER_KEYS).forEach((key) => {
if(FRONTMATTER_KEYS[key].depricated === true) return;
const {name, type} = FRONTMATTER_KEYS[key];
@@ -392,9 +390,8 @@ export default class ExcalidrawPlugin extends Plugin {
}
public initializeFonts() {
const self = this;
this.app.workspace.onLayoutReady(async () => {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(self.initializeFonts,`ExcalidrawPlugin.initializeFonts > app.workspace.onLayoutReady`);
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.initializeFonts,`ExcalidrawPlugin.initializeFonts > app.workspace.onLayoutReady`);
const font = await getFontDataURL(
this.app,
this.settings.experimantalFourthFont,
@@ -448,16 +445,15 @@ export default class ExcalidrawPlugin extends Plugin {
}
private switchToExcalidarwAfterLoad() {
const self = this;
this.app.workspace.onLayoutReady(() => {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(self.switchToExcalidarwAfterLoad, `ExcalidrawPlugin.switchToExcalidarwAfterLoad > app.workspace.onLayoutReady`);
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.switchToExcalidarwAfterLoad, `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)) {
if (fileShouldDefaultAsExcalidraw(leaf.view.file?.path, self.app)) {
self.excalidrawFileModes[(leaf as any).id || leaf.view.file.path] =
if ( leaf.view instanceof MarkdownView && this.isExcalidrawFile(leaf.view.file)) {
if (fileShouldDefaultAsExcalidraw(leaf.view.file?.path, this.app)) {
this.excalidrawFileModes[(leaf as any).id || leaf.view.file.path] =
VIEW_TYPE_EXCALIDRAW;
self.setExcalidrawView(leaf);
this.setExcalidrawView(leaf);
} else {
foldExcalidrawSection(leaf.view);
}
@@ -682,16 +678,15 @@ export default class ExcalidrawPlugin extends Plugin {
initializeMarkdownPostProcessor(this);
this.registerMarkdownPostProcessor(markdownPostProcessor);
const self = this;
this.app.workspace.onLayoutReady(() => {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(self.addMarkdownPostProcessor, `ExcalidrawPlugin.addMarkdownPostProcessor > app.workspace.onLayoutReady`);
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.addMarkdownPostProcessor, `ExcalidrawPlugin.addMarkdownPostProcessor > app.workspace.onLayoutReady`);
// internal-link quick preview
self.registerEvent(self.app.workspace.on("hover-link", hoverEvent));
this.registerEvent(this.app.workspace.on("hover-link", hoverEvent));
//only add the legacy file observer if there are legacy files in the vault
if(self.app.vault.getFiles().some(f=>f.extension === "excalidraw")) {
self.enableLegacyFilePopoverObserver();
if(this.app.vault.getFiles().some(f=>f.extension === "excalidraw")) {
this.enableLegacyFilePopoverObserver();
}
});
}
@@ -722,10 +717,9 @@ export default class ExcalidrawPlugin extends Plugin {
const darkClass = bodyClassList.contains('theme-dark');
if (mutation?.oldValue?.includes('theme-dark') === darkClass) return;
const self = this;
setTimeout(()=>{ //run async to avoid blocking the UI
const theme = isObsidianThemeDark() ? "dark" : "light";
const leaves = self.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
leaves.forEach((leaf: WorkspaceLeaf) => {
const excalidrawView = leaf.view as ExcalidrawView;
if (excalidrawView.file && excalidrawView.excalidrawAPI) {
@@ -807,13 +801,12 @@ export default class ExcalidrawPlugin extends Plugin {
? new CustomMutationObserver(fileExplorerObserverFn, "fileExplorerObserver")
: new MutationObserver(fileExplorerObserverFn);
const self = this;
this.app.workspace.onLayoutReady(() => {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(self.experimentalFileTypeDisplay, `ExcalidrawPlugin.experimentalFileTypeDisplay > app.workspace.onLayoutReady`);
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.experimentalFileTypeDisplay, `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) {
self.fileExplorerObserver.observe(container, {
this.fileExplorerObserver.observe(container, {
childList: true,
subtree: true,
});
@@ -1650,26 +1643,31 @@ export default class ExcalidrawPlugin extends Plugin {
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");
const els = view
.getViewSelectedElements()
.filter(el=>{
if(el.type==="image") {
const ef = view.excalidrawData.getFile(el.fileId);
if(!ef) {
return false;
}
return this.isExcalidrawFile(ef.file);
}
return false;
});
if(els.length !== 1) {
return false;
}
if(checking) return true;
const el = els[0] as ExcalidrawImageElement;
let ef = view.excalidrawData.getFile(el.fileId);
if(!ef) {
return false;
}
if(!this.isExcalidrawFile(ef.file)) {
return false;
}
if(checking) return true;
this.forceToOpenInMarkdownFilepath = ef.file?.path;
const appState = view.excalidrawAPI.getAppState();
const {x:centerX,y:centerY} = sceneCoordsToViewportCoords({sceneX:el.x+el.width/2,sceneY:el.y+el.height/2},appState);
const {width, height} = {width:600, height:600};
const {x,y} = {
x:centerX - width/2 + view.ownerWindow.screenX,
y:centerY - height/2 + view.ownerWindow.screenY,
x:Math.max(0,centerX - width/2 + view.ownerWindow.screenX),
y:Math.max(0,centerY - height/2 + view.ownerWindow.screenY),
}
this.openDrawing(ef.file, DEVICE.isMobile ? "new-tab":"popout-window", true, undefined, false, {x,y,width,height});
@@ -2555,21 +2553,20 @@ export default class ExcalidrawPlugin extends Plugin {
if(!this.settings.startupScriptPath || this.settings.startupScriptPath === "") {
return;
}
const self = this;
this.app.workspace.onLayoutReady(async () => {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(self.runStartupScript, `ExcalidrawPlugin.runStartupScript > app.workspace.onLayoutReady, scriptPath:${self.settings?.startupScriptPath}`);
const path = self.settings.startupScriptPath.endsWith(".md")
? self.settings.startupScriptPath
: `${self.settings.startupScriptPath}.md`;
const f = self.app.vault.getAbstractFileByPath(path);
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.runStartupScript, `ExcalidrawPlugin.runStartupScript > app.workspace.onLayoutReady, scriptPath:${this.settings?.startupScriptPath}`);
const path = this.settings.startupScriptPath.endsWith(".md")
? this.settings.startupScriptPath
: `${this.settings.startupScriptPath}.md`;
const f = this.app.vault.getAbstractFileByPath(path);
if (!f || !(f instanceof TFile)) {
new Notice(`Startup script not found: ${path}`);
return;
}
const script = await self.app.vault.read(f);
const script = await this.app.vault.read(f);
const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor;
try {
await new AsyncFunction("ea", script)(self.ea);
await new AsyncFunction("ea", script)(this.ea);
} catch (e) {
new Notice(`Error running startup script: ${e}`);
}
@@ -2619,7 +2616,7 @@ export default class ExcalidrawPlugin extends Plugin {
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.semaphores?.dirty && !previouslyActiveEV.semaphores?.viewunload) {
if(previouslyActiveEV?.isDirty() && !previouslyActiveEV.semaphores?.viewunload) {
await previouslyActiveEV.save(true); //this will update transclusions in the drawing
}
}
@@ -2817,7 +2814,7 @@ export default class ExcalidrawPlugin extends Plugin {
await inData.loadData(data,file,getTextMode(data));
excalidrawView.synchronizeWithData(inData);
inData.destroy();
if(excalidrawView.semaphores?.dirty) {
if(excalidrawView?.isDirty()) {
if(excalidrawView.autosaveTimer && excalidrawView.autosaveFunction) {
clearTimeout(excalidrawView.autosaveTimer);
}
@@ -2874,16 +2871,6 @@ export default class ExcalidrawPlugin extends Plugin {
};
this.registerEvent(this.app.vault.on("delete", (file:TFile) => deleteEventHandler(file)));
//save open drawings when user quits the application
//Removing because it is not guaranteed to run, and frequently gets terminated mid flight, causing file consistency issues
/*const quitEventHandler = async () => {
const leaves = app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
for (let i = 0; i < leaves.length; i++) {
await (leaves[i].view as ExcalidrawView).save(true);
}
};
self.registerEvent(app.workspace.on("quit", quitEventHandler));*/
//save Excalidraw leaf and update embeds when switching to another leaf
this.registerEvent(
this.app.workspace.on(
@@ -2922,7 +2909,7 @@ export default class ExcalidrawPlugin extends Plugin {
const onClickEventSaveActiveDrawing = (e: PointerEvent) => {
if (
!this.activeExcalidrawView ||
!this.activeExcalidrawView.semaphores?.dirty ||
!this.activeExcalidrawView?.isDirty() ||
//@ts-ignore
e.target && (e.target.className === "excalidraw__canvas" ||
//@ts-ignore
@@ -2941,7 +2928,7 @@ export default class ExcalidrawPlugin extends Plugin {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(onFileMenuEventSaveActiveDrawing,`ExcalidrawPlugin.onFileMenuEventSaveActiveDrawing`);
if (
!this.activeExcalidrawView ||
!this.activeExcalidrawView.semaphores?.dirty
!this.activeExcalidrawView?.isDirty()
) {
return;
}
@@ -2965,7 +2952,7 @@ export default class ExcalidrawPlugin extends Plugin {
if (
m[0].oldValue !== "display: none;" ||
!this.activeExcalidrawView ||
!this.activeExcalidrawView.semaphores?.dirty
!this.activeExcalidrawView?.isDirty()
) {
return;
}
@@ -3012,7 +2999,8 @@ export default class ExcalidrawPlugin extends Plugin {
(m[0].type !== "childList") ||
(m[0].addedNodes.length !== 1) ||
(!this.activeExcalidrawView) ||
(!this.activeExcalidrawView.semaphores?.dirty)
this.activeExcalidrawView?.semaphores?.viewunload ||
(!this.activeExcalidrawView?.isDirty())
) {
return;
}

View File

@@ -64,9 +64,8 @@ export class EmbeddableMenu {
};
private handleMouseLeave () {
const self = this;
this.menuFadeTimeout = window.setTimeout(() => {
self.containerRef.current?.style.setProperty("opacity", "0.2");
this.containerRef.current?.style.setProperty("opacity", "0.2");
}, 5000);
};

View File

@@ -12,12 +12,12 @@ import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/
import { EmbeddableMDCustomProps } from "src/dialogs/EmbeddableSettings";
import { nanoid } from "nanoid";
export const insertImageToView = async (
export async function insertImageToView(
ea: ExcalidrawAutomate,
position: { x: number, y: number },
file: TFile | string,
scale?: boolean,
):Promise<string> => {
):Promise<string> {
ea.clear();
ea.style.strokeColor = "transparent";
ea.style.backgroundColor = "transparent";
@@ -33,12 +33,12 @@ export const insertImageToView = async (
return id;
}
export const insertEmbeddableToView = async (
export async function insertEmbeddableToView (
ea: ExcalidrawAutomate,
position: { x: number, y: number },
file?: TFile,
link?: string,
):Promise<string> => {
):Promise<string> {
ea.clear();
ea.style.strokeColor = "transparent";
ea.style.backgroundColor = "transparent";
@@ -58,7 +58,7 @@ export const insertEmbeddableToView = async (
}
}
export const getLinkTextFromLink = (text: string): string => {
export function getLinkTextFromLink (text: string): string {
if (!text) return;
if (text.match(REG_LINKINDEX_HYPERLINK)) return;
@@ -71,7 +71,7 @@ export const getLinkTextFromLink = (text: string): string => {
return linktext;
}
export const openTagSearch = (link:string, app: App, view?: ExcalidrawView) => {
export function openTagSearch (link:string, app: App, view?: ExcalidrawView) {
const tags = link
.matchAll(/#([\p{Letter}\p{Emoji_Presentation}\p{Number}\/_-]+)/gu)
.next();
@@ -92,21 +92,70 @@ export const openTagSearch = (link:string, app: App, view?: ExcalidrawView) => {
return;
}
export const openExternalLink = (link:string, app: App, element?: ExcalidrawElement):boolean => {
function getLinkFromMarkdownLink(link: string): string {
const result = /^\[[^\]]*]\(([^\)]*)\)/.exec(link);
return result ? result[1] : link;
}
export function openExternalLink (link:string, app: App, element?: ExcalidrawElement):boolean {
link = getLinkFromMarkdownLink(link);
if (link.match(/^cmd:\/\/.*/)) {
const cmd = link.replace("cmd://", "");
//@ts-ignore
app.commands.executeCommandById(cmd);
return true;
}
if (link.match(REG_LINKINDEX_HYPERLINK)) {
window.open(link, "_blank");
if (!link.startsWith("obsidian://") && link.match(REG_LINKINDEX_HYPERLINK)) {
window.open(link, "_blank");
return true;
}
return false;
}
export const getExcalidrawFileForwardLinks = (app: App, excalidrawFile: TFile, secondOrderLinksSet: Set<string>):string => {
/**
*
* @param link
* @param app
* @param returnWikiLink
* @returns
* false if the link is not an obsidian link,
* true if the link is an obsidian link and it was opened (i.e. it is a link to another Vault or not a file link e.g. plugin link), or
* the link to the file path. By default as a wiki link, or as a file path if returnWikiLink is false.
*/
export function parseObsidianLink(link: string, app: App, returnWikiLink: boolean = true): boolean | string {
link = getLinkFromMarkdownLink(link);
if (!link.startsWith("obsidian://")) {
return false;
}
const url = new URL(link);
const action = url.pathname.slice(2); // Remove leading '//'
const props: {[key: string]: string} = {};
url.searchParams.forEach((value, key) => {
props[key] = decodeURIComponent(value);
});
if (action === "open" && props.vault === app.vault.getName()) {
const file = props.file;
const f = app.metadataCache.getFirstLinkpathDest(file, "");
if (f && f instanceof TFile) {
if (returnWikiLink) {
return `[[${f.path}]]`;
} else {
return f.path;
}
}
}
window.open(link, "_blank");
return true;
}
export function getExcalidrawFileForwardLinks (
app: App, excalidrawFile: TFile,
secondOrderLinksSet: Set<string>,
):string {
let secondOrderLinks = "";
const forwardLinks = app.metadataCache.getLinks()[excalidrawFile.path];
if(forwardLinks && forwardLinks.length > 0) {
@@ -125,7 +174,10 @@ export const getExcalidrawFileForwardLinks = (app: App, excalidrawFile: TFile, s
return secondOrderLinks;
}
export const getFrameBasedOnFrameNameOrId = (frameName: string, elements: ExcalidrawElement[]): ExcalidrawFrameElement | null => {
export function getFrameBasedOnFrameNameOrId(
frameName: string,
elements: ExcalidrawElement[],
): ExcalidrawFrameElement | null {
const frames = elements
.filter((el: ExcalidrawElement)=>el.type==="frame")
.map((el: ExcalidrawFrameElement, idx: number)=>{
@@ -136,7 +188,13 @@ export const getFrameBasedOnFrameNameOrId = (frameName: string, elements: Excali
return frames.length === 1 ? frames[0] : null;
}
export const addBackOfTheNoteCard = async (view: ExcalidrawView, title: string, activate: boolean = true, cardBody?: string, embeddableCustomData?: EmbeddableMDCustomProps):Promise<string> => {
export async function addBackOfTheNoteCard(
view: ExcalidrawView,
title: string,
activate: boolean = true,
cardBody?: string,
embeddableCustomData?: EmbeddableMDCustomProps,
):Promise<string> {
const data = view.data;
const header = getExcalidrawMarkdownHeaderSection(data);
const body = data.split(header)[1];
@@ -186,7 +244,12 @@ export const addBackOfTheNoteCard = async (view: ExcalidrawView, title: string,
return el.id;
}
export const renderContextMenuAction = (React: any, label: string, action: Function, onClose: (callback?: () => void) => void) => {
export function renderContextMenuAction(
React: any,
label: string,
action: Function,
onClose: (callback?: () => void) => void,
) {
return React.createElement (
"li",
{
@@ -218,7 +281,7 @@ export const renderContextMenuAction = (React: any, label: string, action: Funct
);
}
export const tmpBruteForceCleanup = (view: ExcalidrawView) => {
export function tmpBruteForceCleanup (view: ExcalidrawView) {
window.setTimeout(()=>{
if(!view) return;
// const cleanupHTMLElement = (el: Element) => {

View File

@@ -37,23 +37,22 @@ export class StylesManager {
constructor(plugin: ExcalidrawPlugin) {
this.plugin = plugin;
const self = this;
plugin.app.workspace.onLayoutReady(async () => {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(undefined, "StylesManager.constructor > app.workspace.onLayoutReady", self);
await self.harvestStyles();
getAllWindowDocuments(plugin.app).forEach(doc => self.copyPropertiesToTheme(doc));
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(undefined, "StylesManager.constructor > app.workspace.onLayoutReady", this);
await this.harvestStyles();
getAllWindowDocuments(plugin.app).forEach(doc => this.copyPropertiesToTheme(doc));
//initialize
plugin.registerEvent(
plugin.app.workspace.on("css-change", ()=>self.onCSSChange()),
plugin.app.workspace.on("css-change", ()=>this.onCSSChange()),
)
plugin.registerEvent(
plugin.app.workspace.on("window-open", (win)=>self.onWindowOpen(win)),
plugin.app.workspace.on("window-open", (win)=>this.onWindowOpen(win)),
)
plugin.registerEvent(
plugin.app.workspace.on("window-close", (win)=>self.onWindowClose(win)),
plugin.app.workspace.on("window-close", (win)=>this.onWindowClose(win)),
)
});
}