diff --git a/manifest.json b/manifest.json index 6991c17..1bc61d0 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-excalidraw-plugin", "name": "Excalidraw", - "version": "1.2.5", + "version": "1.2.6", "minAppVersion": "0.11.13", "description": "An Obsidian plugin to edit and view Excalidraw drawings", "author": "Zsolt Viczian", diff --git a/src/ExcalidrawView.ts b/src/ExcalidrawView.ts index 62036df..445433c 100644 --- a/src/ExcalidrawView.ts +++ b/src/ExcalidrawView.ts @@ -61,6 +61,7 @@ export default class ExcalidrawView extends TextFileView { private plugin: ExcalidrawPlugin; private dirty: boolean = false; public autosaveTimer: any = null; + public autosaving:boolean = false; public isTextLocked:boolean = false; private lockedElement:HTMLElement; private unlockedElement:HTMLElement; @@ -82,8 +83,8 @@ export default class ExcalidrawView extends TextFileView { } const filepath = this.file.path.substring(0,this.file.path.lastIndexOf('.md')) + '.excalidraw'; const file = this.app.vault.getAbstractFileByPath(normalizePath(filepath)); - if(file && file instanceof TFile) this.app.vault.modify(file,JSON.stringify(scene));//data.replaceAll("[","[")); - else this.app.vault.create(filepath,JSON.stringify(scene));//.replaceAll("[","[")); + if(file && file instanceof TFile) this.app.vault.modify(file,JSON.stringify(scene)); + else this.app.vault.create(filepath,JSON.stringify(scene)); } public async saveSVG(scene?: any) { @@ -142,9 +143,9 @@ export default class ExcalidrawView extends TextFileView { // if drawing is in Text Element Edit Unlock, then everything is raw and parse and so an async function is not required here getViewData () { //console.log("ExcalidrawView.getViewData()"); - if(this.getScene && !this.compatibilityMode) { - - if(this.excalidrawData.syncElements(this.getScene())) { + if(!this.getScene) return this.data; + if(!this.compatibilityMode) { + if(this.excalidrawData.syncElements(this.getScene()) && !this.autosaving) { this.loadDrawing(false); } let trimLocation = this.data.search("# Text Elements\n"); @@ -152,22 +153,28 @@ export default class ExcalidrawView extends TextFileView { if(trimLocation == -1) return this.data; const scene = this.excalidrawData.scene; - if(this.plugin.settings.autoexportSVG) this.saveSVG(scene); - if(this.plugin.settings.autoexportPNG) this.savePNG(scene); - if(this.plugin.settings.autoexportExcalidraw) this.saveExcalidraw(scene); + if(!this.autosaving) { + this.autosaving = false; + if(this.plugin.settings.autoexportSVG) this.saveSVG(scene); + if(this.plugin.settings.autoexportPNG) this.savePNG(scene); + if(this.plugin.settings.autoexportExcalidraw) this.saveExcalidraw(scene); + } const header = this.data.substring(0,trimLocation) .replace(/excalidraw-plugin:\s.*\n/,FRONTMATTER_KEY+": " + (this.isTextLocked ? "locked\n" : "unlocked\n")); return header + this.excalidrawData.generateMD(); } - if(this.getScene && this.compatibilityMode) { + if(this.compatibilityMode) { this.excalidrawData.syncElements(this.getScene()); const scene = this.excalidrawData.scene; - if(this.plugin.settings.autoexportSVG) this.saveSVG(scene); - if(this.plugin.settings.autoexportPNG) this.savePNG(scene); + if(!this.autosaving) { + this.autosaving = false; + if(this.plugin.settings.autoexportSVG) this.saveSVG(scene); + if(this.plugin.settings.autoexportPNG) this.savePNG(scene); + } return JSON.stringify(scene); } - else return this.data; + return this.data; } handleLinkClick(view: ExcalidrawView, ev:MouseEvent) { @@ -257,15 +264,15 @@ export default class ExcalidrawView extends TextFileView { const timer = async () => { //console.log("ExcalidrawView.autosaveTimer(), dirty", this.dirty); if(this.dirty) { - console.log("autosave",Date.now()); this.dirty = false; + this.autosaving=true; if(this.excalidrawRef) await this.save(); - this.plugin.triggerEmbedUpdates(); + //this.plugin.triggerEmbedUpdates(); } } - if(this.plugin.settings.autosave) { - this.autosaveTimer = setInterval(timer,30000); - } + //if(this.plugin.settings.autosave) { + this.autosaveTimer = setInterval(timer,30000); + //} } //save current drawing when user closes workspace leaf @@ -334,7 +341,6 @@ export default class ExcalidrawView extends TextFileView { this.instantiateExcalidraw({ elements: excalidrawData.elements, appState: excalidrawData.appState, -// scrollToContent: true, libraryItems: await this.getLibrary(), }); })(); @@ -576,61 +582,6 @@ export default class ExcalidrawView extends TextFileView { excalidrawRef.current.refresh(); }; - /* - const dropAction = (transfer: DataTransfer) => { - // Return a 'copy' or 'link' action according to the content types, or undefined if no recognized type - if (transfer.types.includes('text/uri-list')) return 'link'; - if (['file', 'files', 'link'].includes((this.app as any).dragManager.draggable?.type)) return 'link'; - if (transfer.types.includes('text/html') || transfer.types.includes('text/plain')) return 'copy'; - } - - const linkTo = (f: TFile, subpath?: string) => { - this.addText(this.app.metadataCache.fileToLinktext(f,subpath ? subpath : this.file.path,true)); - }; - - const fixBulletsAndLInks = (text: string) => { - // Internal links from e.g. dataview plugin incorrectly begin with `app://obsidian.md/`, and - // we also want to remove bullet points and task markers from text and markdown - return text.replace(/^\s*[-+*]\s+(\[.]\s+)?/, "").trim().replace(/^\[(.*)\]\(app:\/\/obsidian.md\/(.*)\)$/, "[$1]($2)"); - } - - const getMarkdown = (transfer: DataTransfer ) => { - // crude hack to use Obsidian's html-to-markdown converter (replace when Obsidian exposes it in API): - console.log(transfer); - - } - - let importLines = (transfer: DataTransfer, forcePlaintext: boolean = false) => { - const draggable = (this.app as any).dragManager.draggable; - const html = transfer.getData("text/html"); - const plain = transfer.getData("text/plain"); - const uris = transfer.getData("text/uri-list"); - - switch(draggable?.type) { - case "file": - linkTo(draggable.file); - break; - case "files": - for(const f of draggable.files) { - linkTo(f); - } - break; - case "link": - if(draggable.file) { - linkTo(draggable.file, parseLinktext(draggable.linktext).subpath); - break; - } - console.log(`[[${draggable.linktext}]]`); - break; - default: - const text = forcePlaintext ? (plain||html) : getMarkdown(transfer); - // Split lines and strip leading bullets/task indicators - const lines: string[] = (text || html || uris || plain || "").split(/\r\n?|\n/).map(fixBulletsAndLInks); - console.log( lines.filter(line => line)); - break; - } - }*/ - let timestamp = 0; const dblclickEvent = (e: Event):boolean => { @@ -661,6 +612,8 @@ export default class ExcalidrawView extends TextFileView { ref: excalidrawWrapperRef, key: "abc", onTouchEnd: (e: TouchEvent) => { + //@ts-ignore + if (!this.app.isMobile) return; if (dblclickEvent(e)) return; }, onClick: (e:MouseEvent):any => { @@ -677,29 +630,9 @@ export default class ExcalidrawView extends TextFileView { if(ev.keyCode!=13) return; //not an enter if(!(ev.target instanceof HTMLDivElement)) return; if(!this.getSelectedId()) return; -/* const event = new MouseEvent('dblclick', { - 'view': window, - 'bubbles': true, - 'cancelable': true, - }); - ev.target.querySelector("canvas").dispatchEvent(event);*/ this.lock(false); new Notice(t("UNLOCK_TO_EDIT")); }, -/* onDragOver: (e:any) => { - const action = dropAction(e.dataTransfer); - if (action) { - e.dataTransfer.dropEffect = action; - e.preventDefault(); - return false; - } - }, - onDragLeave: () => { }, - onDrop: (e:any) => { - importLines(e.dataTransfer); - e.preventDefault(); - // shift key to force plain text, the same way Obsidian does it - },*/ }, React.createElement(Excalidraw.default, { ref: excalidrawRef, @@ -723,7 +656,11 @@ export default class ExcalidrawView extends TextFileView { onChange: (et:ExcalidrawElement[],st:AppState) => { if(this.justLoaded) { this.justLoaded = false; - excalidrawRef.current.scrollToContent(); + const el = this.containerEl; + setTimeout(()=>{ + const e = new KeyboardEvent("keydown", {bubbles : true, cancelable : true, shiftKey : true, code:"Digit1"}); + el.querySelector("canvas")?.dispatchEvent(e); + },200) previousSceneVersion = getSceneVersion(et); } if (st.editingElement == null && st.resizingElement == null && diff --git a/src/Utils.ts b/src/Utils.ts index 3d87d91..9f37622 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -27,4 +27,16 @@ export function download(encoding:string,data:any,filename:string) { document.body.appendChild(element); element.click(); document.body.removeChild(element); +} + +/** + * Generates the image filename based on the excalidraw filename + * @param excalidrawPath - Full filepath of ExclidrawFile + * @param newExtension - extension of IMG file in ".extension" format + * @returns + */ +export function getIMGPathFromExcalidrawFile (excalidrawPath:string,newExtension:string):string { + const isLegacyFile:boolean = excalidrawPath.endsWith(".excalidraw"); + const replaceExtension:string = isLegacyFile ? ".excalidraw" : ".md"; + return excalidrawPath.substring(0,excalidrawPath.lastIndexOf(replaceExtension)) + newExtension; } \ No newline at end of file diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts index 96e0242..4e53027 100644 --- a/src/lang/locale/en.ts +++ b/src/lang/locale/en.ts @@ -109,17 +109,6 @@ export default { "While the auto-export switch is on, this file will get updated every time you edit the excalidraw drawing with the matching name.", EXPORT_PNG_NAME: "Auto-export PNG", EXPORT_PNG_DESC: "Same as the auto-export SVG, but for *.PNG", -/* STENCIL_HEAD: "Stencil Library", - STENCIL_INVAULT_NAME: "Store as FILE", - STENCIL_INVAULT_DESC: "By enabling this feature, the stencil library will be stored in a file specified in the next setting. " + - "Obsidan Sync now synchronizes all filetypes. By storing your stencil library in a file you can synchronize your library between devices. " + - "When enabling this setting, if the file you specified is empty, your existing stencils in settings will be copied to the file. " + - "When disabling this setting, your current stencil library will not overwirte stencils in your settings. " + - "You need to close all Excalidraw views and reopen them, for this change to take effect. " + - "The default filename is Excalidraw/stencils.excalidrawlib " , - STENCIL_PATH_NAME: "Filepath", - STENCIL_PATH_DESC: "This can only be edited when \"Store as FILE\" is turned off. " + - "The filepath of the stencil library. Enter the filename without the extension. ",*/ COMPATIBILITY_HEAD: "Compatibility features", EXPORT_EXCALIDRAW_NAME: "Auto-export Excalidraw", EXPORT_EXCALIDRAW_DESC: "Same as the auto-export SVG, but for *.Excalidraw", diff --git a/src/main.ts b/src/main.ts index 115474f..214b365 100644 --- a/src/main.ts +++ b/src/main.ts @@ -58,7 +58,7 @@ import { Prompt } from "./Prompt"; import { around } from "monkey-around"; import { t } from "./lang/helpers"; import { MigrationPrompt } from "./MigrationPrompt"; -import { download, splitFolderAndFilename } from "./Utils"; +import { download, getIMGPathFromExcalidrawFile, splitFolderAndFilename } from "./Utils"; export default class ExcalidrawPlugin extends Plugin { public excalidrawFileModes: { [file: string]: string } = {}; @@ -516,10 +516,6 @@ export default class ExcalidrawPlugin extends Plugin { checkCallback: (checking: boolean) => { if (checking) { return this.app.workspace.activeLeaf.view.getViewType() == VIEW_TYPE_EXCALIDRAW; -/* if(this.app.workspace.activeLeaf.view.getViewType() == VIEW_TYPE_EXCALIDRAW) { - return !(this.app.workspace.activeLeaf.view as ExcalidrawView).compatibilityMode; - } - return false;*/ } else { const view = this.app.workspace.activeLeaf.view; if (view instanceof ExcalidrawView) { @@ -799,18 +795,18 @@ export default class ExcalidrawPlugin extends Plugin { const self = this; this.app.workspace.onLayoutReady(async () => { + + //watch filename change to rename .svg, .png; to sync to .md; to update links const renameEventHandler = async (file:TAbstractFile,oldPath:string) => { if(!(file instanceof TFile)) return; - if (!self.isExcalidrawFile(file)) return; - if (!self.settings.keepInSync) return; + if(!self.isExcalidrawFile(file)) return; + if(!self.settings.keepInSync) return; ['.svg','.png','.excalidraw'].forEach(async (ext:string)=>{ - const isLegacyFile:boolean = oldPath.endsWith(".excalidraw"); - const replaceExtension:string = isLegacyFile ? ".excalidraw" : ".md"; - const oldIMGpath = oldPath.substring(0,oldPath.lastIndexOf(replaceExtension)) + ext; + const oldIMGpath = getIMGPathFromExcalidrawFile(oldPath,ext); const imgFile = self.app.vault.getAbstractFileByPath(normalizePath(oldIMGpath)); if(imgFile && imgFile instanceof TFile) { - const newIMGpath = file.path.substring(0,file.path.lastIndexOf(replaceExtension)) + ext; + const newIMGpath = getIMGPathFromExcalidrawFile(file.path,ext); await self.app.vault.rename(imgFile,newIMGpath); } }); @@ -839,11 +835,9 @@ export default class ExcalidrawPlugin extends Plugin { const deleteEventHandler = async (file:TFile) => { if (!(file instanceof TFile)) return; //@ts-ignore - const isExcalidarwFile = ((file.unsaveCachedData) && (file.unsafeCachedData.search(/---\n[\s\S]*excalidraw-plugin:\s*(locked|unlocked)\n[\s\S]*---/gm)>-1)) + const isExcalidarwFile = (file.unsafeCachedData && file.unsafeCachedData.search(/---\n[\s\S]*excalidraw-plugin:\s*(locked|unlocked)\n[\s\S]*---/gm)>-1) || (file.extension=="excalidraw"); if(!isExcalidarwFile) return; - //@ts-ignore - //if (file.unsaveCachedData && !file.unsafeCachedData.search(/---\n[\s\S]*excalidraw-plugin:\s*(locked|unlocked)\n[\s\S]*---/gm)==-1) return; //close excalidraw view where this file is open const leaves = self.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW); @@ -856,10 +850,7 @@ export default class ExcalidrawPlugin extends Plugin { //delete PNG and SVG files as well if (self.settings.keepInSync) { ['.svg','.png','.excalidraw'].forEach(async (ext:string) => { - const isLegacyFile:boolean = file.extension == "excalidraw"; - const replaceExtension:string = isLegacyFile ? ".excalidraw" : ".md"; - - const imgPath = file.path.substring(0,file.path.lastIndexOf(replaceExtension)) + ext; + const imgPath = getIMGPathFromExcalidrawFile(file.path,ext); const imgFile = self.app.vault.getAbstractFileByPath(normalizePath(imgPath)); if(imgFile && imgFile instanceof TFile) { await self.app.vault.delete(imgFile); @@ -931,43 +922,18 @@ export default class ExcalidrawPlugin extends Plugin { public async loadSettings() { this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); -/* if(this.settings.libraryInVault) { - const filepath = this.settings.libraryLocation+".excalidrawlib"; - const file = this.app.vault.getAbstractFileByPath(filepath); - if(file && file instanceof TFile) { - this.stencilLibrary = await this.app.vault.read(file); - } else { - this.stencilLibrary = this.settings.library; - } - }*/ } async saveSettings() { await this.saveData(this.settings); -/* if(this.settings.libraryInVault) { - const filepath = this.settings.libraryLocation+".excalidrawlib"; - const f = splitFolderAndFilename(filepath); - await this.checkAndCreateFolder(f.folderpath); - const file = this.app.vault.getAbstractFileByPath(filepath); - if(file && file instanceof TFile) { - await this.app.vault.modify(file,this.stencilLibrary ? this.stencilLibrary : this.settings.library) - } else { - await this.app.vault.create(filepath,JSON.stringify(this.stencilLibrary ? this.stencilLibrary : this.settings.library)); - } - }*/ } public getStencilLibrary():string { - //if(this.settings.libraryInVault) return this.stencilLibrary; return this.settings.library; } public setStencilLibrary(library:string) { -/* if(this.settings.libraryInVault) { - this.stencilLibrary = library; - } else {*/ this.settings.library = library; - //} } public triggerEmbedUpdates(filepath?:string){ diff --git a/src/settings.ts b/src/settings.ts index fd65197..a0ff676 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -18,7 +18,7 @@ export interface ExcalidrawSettings { width: string, showLinkBrackets: boolean, linkPrefix: string, - autosave: boolean; + //autosave: boolean; allowCtrlClick: boolean, //if disabled only the link button in the view header will open links exportWithTheme: boolean, exportWithBackground: boolean, @@ -32,8 +32,6 @@ export interface ExcalidrawSettings { experimentalFileTag: string, loadCount: number, //version 1.2 migration counter drawingOpenCount: number, -// libraryInVault: boolean, //if true, library is stored in the vault in a file -// libraryLocation: string, //full path to the library file library: string, } @@ -43,9 +41,9 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = { drawingFilenamePrefix: 'Drawing ', drawingFilenameDateTime: 'YYYY-MM-DD HH.mm.ss', width: '400', - linkPrefix: ">> ", + linkPrefix: "🔸", showLinkBrackets: true, - autosave: false, + //autosave: false, allowCtrlClick: true, exportWithTheme: true, exportWithBackground: true, @@ -59,8 +57,6 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = { compatibilityMode: false, loadCount: 0, drawingOpenCount: 0, -// libraryInVault: false, -// libraryLocation: "Excalidraw/library", library: `{"type":"excalidrawlib","version":1,"library":[]}`, } @@ -98,7 +94,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab { await this.plugin.saveSettings(); })); - new Setting(containerEl) +/* new Setting(containerEl) .setName(t("AUTOSAVE_NAME")) .setDesc(t("AUTOSAVE_DESC")) .addToggle(toggle => toggle @@ -118,7 +114,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab { } } } - })); + }));*/ this.containerEl.createEl('h1', {text: t("FILENAME_HEAD")}); containerEl.createDiv('',(el) => { @@ -191,7 +187,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab { .setName(t("LINK_PREFIX_NAME")) .setDesc(t("LINK_PREFIX_DESC")) .addText(text => text - .setPlaceholder('>> ') + .setPlaceholder('🔸') .setValue(this.plugin.settings.linkPrefix) .onChange(async (value) => { this.plugin.settings.linkPrefix = value; @@ -277,49 +273,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab { this.plugin.settings.autoexportPNG = value; await this.plugin.saveSettings(); })); - -/* - this.containerEl.createEl('h1', {text: t("STENCIL_HEAD")}); - - const changeLibrary = async () => { - if(!this.plugin.settings.libraryInVault) return; - const filepath = this.plugin.settings.libraryLocation+".excalidrawlib"; - const f = splitFolderAndFilename(filepath); - await this.plugin.checkAndCreateFolder(f.folderpath); - const file = this.app.vault.getAbstractFileByPath(filepath); - if(file && file instanceof TFile) { - this.plugin.stencilLibrary = await this.app.vault.read(file); - } else { - this.plugin.stencilLibrary = this.plugin.settings.library; - } - } - - new Setting(containerEl) - .setName(t("STENCIL_INVAULT_NAME")) - .setDesc(t("STENCIL_INVAULT_DESC")) - .addToggle(toggle => toggle - .setValue(this.plugin.settings.libraryInVault) - .onChange(async (value) => { - this.plugin.settings.libraryInVault = value; - if(value) stencilLib.setDisabled(true); - - await changeLibrary(); - await this.plugin.saveSettings(); - })); - - const stencilLib = new Setting(containerEl) - .setName(t("STENCIL_PATH_NAME")) - .setDesc(t("STENCIL_PATH_DESC")) - .addText(text => text - .setPlaceholder('Excalidraw/library') - .setValue(this.plugin.settings.libraryLocation) - .onChange(async (value) => { - this.plugin.settings.libraryInVault = false; - this.plugin.stencilLibrary = null; - this.plugin.settings.libraryLocation = value; - await this.plugin.saveSettings(); - })); */ - + this.containerEl.createEl('h1', {text: t("COMPATIBILITY_HEAD")}); new Setting(containerEl)