From ba88ced2bae68ab0b031bf5bd3e274c0b8e613f7 Mon Sep 17 00:00:00 2001 From: Zsolt Viczian Date: Sun, 11 Jul 2021 23:28:46 +0200 Subject: [PATCH] 1.2.4 --- manifest.json | 2 +- src/ExcalidrawView.ts | 65 ++++++++++++++++++------------------ src/Utils.ts | 30 +++++++++++++++++ src/lang/locale/en.ts | 12 +++++++ src/main.ts | 76 +++++++++++++++++++++++++++++++++++++------ src/settings.ts | 54 ++++++++++++++++++++++++++++-- styles.css | 4 +-- 7 files changed, 196 insertions(+), 47 deletions(-) create mode 100644 src/Utils.ts diff --git a/manifest.json b/manifest.json index bb29a6d..2642efa 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-excalidraw-plugin", "name": "Excalidraw", - "version": "1.2.3", + "version": "1.2.4", "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 6732fe7..62036df 100644 --- a/src/ExcalidrawView.ts +++ b/src/ExcalidrawView.ts @@ -9,7 +9,7 @@ import { } from "obsidian"; import * as React from "react"; import * as ReactDOM from "react-dom"; -import Excalidraw, {exportToSvg, getSceneVersion} from "@excalidraw/excalidraw"; +import Excalidraw, {exportToSvg, getSceneVersion, loadLibraryFromBlob} from "@excalidraw/excalidraw"; import { ExcalidrawElement } from "@excalidraw/excalidraw/types/element/types"; import { AppState, @@ -33,6 +33,7 @@ import ExcalidrawPlugin from './main'; import {ExcalidrawAutomate} from './ExcalidrawAutomate'; import { t } from "./lang/helpers"; import { ExcalidrawData, REG_LINK_BACKETS } from "./ExcalidrawData"; +import { download } from "./Utils"; declare let window: ExcalidrawAutomate; @@ -216,16 +217,6 @@ export default class ExcalidrawView extends TextFileView { } } - download(encoding:string,data:any,filename:string) { - let element = document.createElement('a'); - element.setAttribute('href', (encoding ? encoding + ',' : '') + data); - element.setAttribute('download', filename); - element.style.display = 'none'; - document.body.appendChild(element); - element.click(); - document.body.removeChild(element); - } - onload() { //console.log("ExcalidrawView.onload()"); this.addAction(DISK_ICON_NAME,t("FORCE_SAVE"),async (ev)=> { @@ -307,8 +298,8 @@ export default class ExcalidrawView extends TextFileView { this.app.workspace.onLayoutReady(async ()=>{ //console.log("ExcalidrawView.setViewData()"); this.compatibilityMode = this.file.extension == "excalidraw"; - this.plugin.settings.drawingOpenCount++; - this.plugin.saveSettings(); + await this.plugin.loadSettings(); + this.plugin.opencount++; if(this.compatibilityMode) { this.unlockedElement.hide(); this.lockedElement.hide(); @@ -390,7 +381,7 @@ export default class ExcalidrawView extends TextFileView { .setIcon(ICON_NAME) .onClick( async (ev) => { if(!this.getScene || !this.file) return; - this.download('data:text/plain;charset=utf-8',encodeURIComponent(JSON.stringify(this.getScene())), this.file.basename+'.excalidraw'); + download('data:text/plain;charset=utf-8',encodeURIComponent(JSON.stringify(this.getScene())), this.file.basename+'.excalidraw'); }); }); } else { @@ -423,7 +414,7 @@ export default class ExcalidrawView extends TextFileView { const self = this; reader.onloadend = function() { let base64data = reader.result; - self.download(null,base64data,self.file.basename+'.png'); + download(null,base64data,self.file.basename+'.png'); } return; } @@ -444,7 +435,7 @@ export default class ExcalidrawView extends TextFileView { let svg = await ExcalidrawView.getSVG(this.getScene(),exportSettings); if(!svg) return null; svg = ExcalidrawView.embedFontsInSVG(svg); - this.download("data:image/svg+xml;base64",btoa(unescape(encodeURIComponent(svg.outerHTML))),this.file.basename+'.svg'); + download("data:image/svg+xml;base64",btoa(unescape(encodeURIComponent(svg.outerHTML))),this.file.basename+'.svg'); return; } this.saveSVG() @@ -455,7 +446,7 @@ export default class ExcalidrawView extends TextFileView { } async getLibrary() { - const data = JSON_parse(this.plugin.settings.library); + const data = JSON_parse(this.plugin.getStencilLibrary()); return data?.library ? data.library : []; } @@ -640,7 +631,25 @@ export default class ExcalidrawView extends TextFileView { } }*/ - let timestamp = (new Date()).getTime(); + let timestamp = 0; + + const dblclickEvent = (e: Event):boolean => { + if((e.target instanceof HTMLCanvasElement) && this.getSelectedText(true)) { //text element is selected + const now = (new Date()).getTime(); + if(now-timestamp < 600) { //double click + if(this.isTextLocked) { + e.preventDefault(); + e.stopPropagation(); + new Notice(t("UNLOCK_TO_EDIT")); + } + timestamp = 0; + this.lock(false); + return true; + } + timestamp = now; + } + return false; + } return React.createElement( React.Fragment, @@ -651,19 +660,13 @@ export default class ExcalidrawView extends TextFileView { className: "excalidraw-wrapper", ref: excalidrawWrapperRef, key: "abc", + onTouchEnd: (e: TouchEvent) => { + if (dblclickEvent(e)) return; + }, onClick: (e:MouseEvent):any => { - if(this.isTextLocked && (e.target instanceof HTMLCanvasElement) && this.getSelectedText(true)) { //text element is selected - const now = (new Date()).getTime(); - if(now-timestamp < 600) { //double click - e.preventDefault(); - e.stopPropagation(); - this.lock(false); - new Notice(t("UNLOCK_TO_EDIT")); - timestamp = now; - return; - } - timestamp = now; - } + //@ts-ignore + if(this.app.isMobile) return; + if(this.isTextLocked && dblclickEvent(e)) return; if(!(e.ctrlKey||e.metaKey)) return; if(!(this.plugin.settings.allowCtrlClick)) return; if(!this.getSelectedId()) return; @@ -735,7 +738,7 @@ export default class ExcalidrawView extends TextFileView { }, onLibraryChange: (items:LibraryItems) => { (async () => { - this.plugin.settings.library = EXCALIDRAW_LIB_HEADER+JSON.stringify(items)+'}'; + this.plugin.setStencilLibrary(EXCALIDRAW_LIB_HEADER+JSON.stringify(items)+'}'); await this.plugin.saveSettings(); })(); } diff --git a/src/Utils.ts b/src/Utils.ts new file mode 100644 index 0000000..3d87d91 --- /dev/null +++ b/src/Utils.ts @@ -0,0 +1,30 @@ +import { normalizePath, TFolder } from "obsidian"; + +/** + * Splits a full path including a folderpath and a filename into separate folderpath and filename components + * @param filepath + */ +export function splitFolderAndFilename(filepath: string):{folderpath: string, filename: string} { + let folderpath: string, filename:string; + const lastIndex = filepath.lastIndexOf("/"); + return { + folderpath: normalizePath(filepath.substr(0,lastIndex)), + filename: lastIndex==-1 ? filepath : filepath.substr(lastIndex+1), + }; +} + +/** + * Download data as file from Obsidian, to store on local device + * @param encoding + * @param data + * @param filename + */ +export function download(encoding:string,data:any,filename:string) { + let element = document.createElement('a'); + element.setAttribute('href', (encoding ? encoding + ',' : '') + data); + element.setAttribute('download', filename); + element.style.display = 'none'; + document.body.appendChild(element); + element.click(); + document.body.removeChild(element); +} \ No newline at end of file diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts index 9ffc6db..96e0242 100644 --- a/src/lang/locale/en.ts +++ b/src/lang/locale/en.ts @@ -10,6 +10,7 @@ export default { CREATE_NEW : "New Excalidraw drawing", CONVERT_FILE_KEEP_EXT: "*.excalidraw => *.excalidraw.md", CONVERT_FILE_REPLACE_EXT: "*.excalidraw => *.md (Logseq compatibility)", + DOWNLOAD_LIBRARY: "Export stencil library as an *.excalidrawlib file", OPEN_EXISTING_NEW_PANE: "Open an existing drawing - IN A NEW PANE", OPEN_EXISTING_ACTIVE_PANE: "Open an existing drawing - IN THE CURRENT ACTIVE PANE", TRANSCLUDE: "Transclude (embed) a drawing", @@ -108,6 +109,17 @@ 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 e0e07bc..037314a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,5 @@ import { TFile, - TFolder, Plugin, WorkspaceLeaf, addIcon, @@ -16,6 +15,7 @@ import { MarkdownRenderer, ViewState, Notice, + TFolder, } from "obsidian"; import { @@ -58,17 +58,20 @@ import { Prompt } from "./Prompt"; import { around } from "monkey-around"; import { t } from "./lang/helpers"; import { MigrationPrompt } from "./MigrationPrompt"; +import { download, splitFolderAndFilename } from "./Utils"; export default class ExcalidrawPlugin extends Plugin { public excalidrawFileModes: { [file: string]: string } = {}; private _loaded: boolean = false; public settings: ExcalidrawSettings; + //public stencilLibrary: any = null; private openDialog: OpenFileDialog; private activeExcalidrawView: ExcalidrawView = null; public lastActiveExcalidrawFilePath: string = null; private hover: {linkText: string, sourcePath: string} = {linkText: null, sourcePath: null}; private observer: MutationObserver; private fileExplorerObserver: MutationObserver; + public opencount:number = 0; constructor(app: App, manifest: PluginManifest) { super(app, manifest); @@ -102,7 +105,7 @@ export default class ExcalidrawPlugin extends Plugin { //inspiration taken from kanban: //https://github.com/mgmeyers/obsidian-kanban/blob/44118e25661bff9ebfe54f71ae33805dc88ffa53/src/main.ts#L267 this.registerMonkeyPatches(); - if(this.settings.loadCount<3) this.migrationNotice(); + if(this.settings.loadCount<1) this.migrationNotice(); } @@ -110,7 +113,7 @@ export default class ExcalidrawPlugin extends Plugin { const self = this; this.app.workspace.onLayoutReady(async () => { self.settings.loadCount++; - self.saveSettings(); + //self.saveSettings(); const files = this.app.vault.getFiles().filter((f)=>f.extension=="excalidraw"); if(files.length>0) { const prompt = new MigrationPrompt(self.app, self); @@ -239,7 +242,7 @@ export default class ExcalidrawPlugin extends Plugin { //@ts-ignore this.app.workspace.on('hover-link',hoverEvent) ); - + //monitoring for div.popover.hover-popover.file-embed.is-loaded to be added to the DOM tree this.observer = new MutationObserver((m)=>{ if(m.length == 0) return; @@ -411,6 +414,14 @@ export default class ExcalidrawPlugin extends Plugin { this.app.workspace.on("file-menu", fileMenuHandlerConvertReplaceExtension) ); + this.addCommand({ + id: "excalidraw-download-lib", + name: t("DOWNLOAD_LIBRARY"), + callback: () => { + download('data:text/plain;charset=utf-8',encodeURIComponent(this.settings.library), 'my-obsidian-library.excalidrawlib'); + }, + }); + this.addCommand({ id: "excalidraw-open", name: t("OPEN_EXISTING_NEW_PANE"), @@ -847,6 +858,9 @@ export default class ExcalidrawPlugin extends Plugin { for (let i=0;i { this.setMarkdownView(leaf); }); - + this.settings.drawingOpenCount += this.opencount; + this.settings.loadCount++; + this.saveSettings(); } public embedDrawing(data:string) { @@ -894,12 +910,45 @@ export default class ExcalidrawPlugin extends Plugin { } - private async loadSettings() { + 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){ @@ -961,10 +1010,7 @@ export default class ExcalidrawPlugin extends Plugin { public async createDrawing(filename: string, onNewPane: boolean, foldername?: string, initData?:string) { const folderpath = normalizePath(foldername ? foldername: this.settings.folder); - const folder = this.app.vault.getAbstractFileByPath(folderpath); - if (!(folder && folder instanceof TFolder)) { - await this.app.vault.createFolder(folderpath); - } + await this.checkAndCreateFolder(folderpath); //create folder if it does not exist const fname = this.getNewUniqueFilepath(filename,folderpath); @@ -1018,4 +1064,14 @@ export default class ExcalidrawPlugin extends Plugin { const fileCache = this.app.metadataCache.getFileCache(f); return !!fileCache?.frontmatter && !!fileCache.frontmatter[FRONTMATTER_KEY]; } + + /** + * Open or create a folderpath if it does not exist + * @param folderpath + */ + public async checkAndCreateFolder(folderpath:string) { + let folder = this.app.vault.getAbstractFileByPath(folderpath); + if(folder && folder instanceof TFolder) return; + await this.app.vault.createFolder(folderpath); + } } \ No newline at end of file diff --git a/src/settings.ts b/src/settings.ts index be0422a..fd65197 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -1,12 +1,14 @@ import { App, PluginSettingTab, - Setting + Setting, + TFile } from 'obsidian'; import { VIEW_TYPE_EXCALIDRAW } from './constants'; import ExcalidrawView from './ExcalidrawView'; import { t } from './lang/helpers'; import type ExcalidrawPlugin from "./main"; +import { splitFolderAndFilename } from './Utils'; export interface ExcalidrawSettings { folder: string, @@ -25,12 +27,14 @@ export interface ExcalidrawSettings { autoexportPNG: boolean, autoexportExcalidraw: boolean, syncExcalidraw: boolean, - library: string, compatibilityMode: boolean, experimentalFileType: boolean, 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, } export const DEFAULT_SETTINGS: ExcalidrawSettings = { @@ -50,12 +54,14 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = { autoexportPNG: false, autoexportExcalidraw: false, syncExcalidraw: false, - library: `{"type":"excalidrawlib","version":1,"library":[]}`, experimentalFileType: false, experimentalFileTag: "✏️", compatibilityMode: false, loadCount: 0, drawingOpenCount: 0, +// libraryInVault: false, +// libraryLocation: "Excalidraw/library", + library: `{"type":"excalidrawlib","version":1,"library":[]}`, } export class ExcalidrawSettingTab extends PluginSettingTab { @@ -271,6 +277,48 @@ 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")}); diff --git a/styles.css b/styles.css index 7dd9c5d..6be0ce3 100644 --- a/styles.css +++ b/styles.css @@ -63,7 +63,7 @@ button.ToolIcon_type_button[title="Export"] { flex-grow: 1; } - +/* @font-face { font-family: "Virgil"; src: url("https://excalidraw.com/Virgil.woff2"); @@ -71,4 +71,4 @@ button.ToolIcon_type_button[title="Export"] { @font-face { font-family: "Cascadia"; src: url("https://excalidraw.com/Cascadia.woff2"); -} +}*/