Compare commits

..

8 Commits
1.2.2 ... 1.2.8

Author SHA1 Message Date
zsviczian
dd476b564a workaround to solve issue #94
trim part after final closing } curly bracket
2021-07-14 11:55:40 +02:00
zsviczian
b7a7c9473e dirty changed to file.path
to ensure the plugin won't accidentally overwrite the next file
2021-07-14 11:29:30 +02:00
Zsolt Viczian
cc7dc16810 1.2.7 autosave+template warning 2021-07-13 22:47:38 +02:00
Zsolt Viczian
c45faef141 1.2.6 2021-07-12 23:07:40 +02:00
Zsolt Viczian
ae31ad0870 1.2.5 fame update for .png and .svg when migrating 2021-07-12 07:03:31 +02:00
Zsolt Viczian
ba88ced2ba 1.2.4 2021-07-11 23:28:46 +02:00
Zsolt Viczian
803fb9e234 1.2.3 2021-07-11 08:42:29 +02:00
Zsolt Viczian
d77249088f 1.2.2 text lock/unlock solved 2021-07-10 23:32:25 +02:00
10 changed files with 212 additions and 118 deletions

View File

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

View File

@@ -6,6 +6,7 @@ import {
} from "@excalidraw/excalidraw/types/element/types";
import {
normalizePath,
Notice,
TFile
} from "obsidian"
import ExcalidrawView from "./ExcalidrawView";
@@ -516,5 +517,4 @@ async function getTemplate(fileWithPath: string):Promise<{elements: any,appState
+ String.fromCharCode(96)+String.fromCharCode(96)+String.fromCharCode(96)+'json\n'
+ data + '\n'
+ String.fromCharCode(96)+String.fromCharCode(96)+String.fromCharCode(96);
//+data.replaceAll("[","&#91;");
}

View File

@@ -18,11 +18,11 @@ const DRAWING_REG = /\n# Drawing\n(```json\n)?(.*)(```)?/gm;
export const REG_LINK_BACKETS = /(!)?\[\[([^|\]]+)\|?(.+)?]]|(!)?\[(.*)\]\((.*)\)/g;
export function getJSON(data:string):string {
const findJSON = DRAWING_REG;
const res = data.matchAll(findJSON);
const res = data.matchAll(DRAWING_REG);
const parts = res.next();
if(parts.value && parts.value.length>1) {
return parts.value[2];
const result = parts.value[2];
return result.substr(0,result.lastIndexOf("}")+1); //this is a workaround in case sync merges two files together and one version is still an old version without the ```codeblock
}
return data;
}
@@ -72,7 +72,8 @@ export class ExcalidrawData {
let parts = data.matchAll(DRAWING_REG).next();
if(!(parts.value && parts.value.length>1)) return false; //JSON not found or invalid
if(!this.scene) { //scene was not loaded from .excalidraw
this.scene = JSON_parse(parts.value[2]);
const scene = parts.value[2];
this.scene = JSON_parse(scene.substr(0,scene.lastIndexOf("}")+1)); //this is a workaround to address when files are mereged by sync and one version is still an old markdown without the codeblock ```
}
//Trim data to remove the JSON string
data = data.substring(0,parts.value.index);
@@ -354,4 +355,4 @@ export class ExcalidrawData {
return showLinkBrackets != this.showLinkBrackets;
}
}
}

View File

@@ -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;
@@ -58,8 +59,9 @@ export default class ExcalidrawView extends TextFileView {
private excalidrawRef: React.MutableRefObject<any> = null;
private justLoaded: boolean = false;
private plugin: ExcalidrawPlugin;
private dirty: boolean = false;
private dirty: string = null;
public autosaveTimer: any = null;
public autosaving:boolean = false;
public isTextLocked:boolean = false;
private lockedElement:HTMLElement;
private unlockedElement:HTMLElement;
@@ -81,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("&#91;","["));
else this.app.vault.create(filepath,JSON.stringify(scene));//.replaceAll("&#91;","["));
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) {
@@ -133,6 +135,7 @@ export default class ExcalidrawView extends TextFileView {
async save(preventReload:boolean=true) {
this.preventReload = preventReload;
this.dirty = null;
await super.save();
}
@@ -141,9 +144,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");
@@ -151,22 +154,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) {
@@ -216,16 +225,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)=> {
@@ -265,16 +264,16 @@ export default class ExcalidrawView extends TextFileView {
public setupAutosaveTimer() {
const timer = async () => {
//console.log("ExcalidrawView.autosaveTimer(), dirty", this.dirty);
if(this.dirty) {
console.log("autosave",Date.now());
this.dirty = false;
if(this dirty && (this.dirty == this.file?.path)) {
this.dirty = null;
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
@@ -306,9 +305,10 @@ export default class ExcalidrawView extends TextFileView {
async setViewData (data: string, clear: boolean = false) {
this.app.workspace.onLayoutReady(async ()=>{
//console.log("ExcalidrawView.setViewData()");
this.dirty = null;
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();
@@ -322,25 +322,26 @@ export default class ExcalidrawView extends TextFileView {
}
if(clear) this.clear();
this.loadDrawing(true)
this.dirty = false;
});
}
private loadDrawing (justloaded:boolean) {
private loadDrawing(justloaded:boolean) {
//console.log("ExcalidrawView.loadDrawing, justloaded", justloaded);
this.justLoaded = justloaded; //a flag to trigger zoom to fit after the drawing has been loaded
const excalidrawData = this.excalidrawData.scene;
if(this.excalidrawRef) {
if(justloaded) this.excalidrawRef.current.resetScene();
this.excalidrawRef.current.updateScene({
elements: excalidrawData.elements,
appState: excalidrawData.appState,
commitToHistory: false,
});
if(justloaded) this.excalidrawRef.current.scrollToContent();
} else {
(async() => {
this.instantiateExcalidraw({
elements: excalidrawData.elements,
appState: excalidrawData.appState,
// scrollToContent: true,
libraryItems: await this.getLibrary(),
});
})();
@@ -387,7 +388,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 {
@@ -420,7 +421,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;
}
@@ -441,7 +442,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()
@@ -452,14 +453,14 @@ 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 : [];
}
private instantiateExcalidraw(initdata: any) {
//console.log("ExcalidrawView.instantiateExcalidraw()");
this.dirty = false;
this.dirty = null;
const reactElement = React.createElement(() => {
let previousSceneVersion = 0;
let currentPosition = {x:0, y:0};
@@ -581,9 +582,27 @@ export default class ExcalidrawView extends TextFileView {
if(!excalidrawRef?.current) return;
excalidrawRef.current.refresh();
};
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,
null,
@@ -593,22 +612,15 @@ export default class ExcalidrawView extends TextFileView {
className: "excalidraw-wrapper",
ref: excalidrawWrapperRef,
key: "abc",
onTouchEnd: (e: TouchEvent) => {
//@ts-ignore
if (!this.app.isMobile) return;
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
let event = new MouseEvent('dblclick', {
'view': window,
'bubbles': true,
'cancelable': true,
});
e.target.dispatchEvent(event);
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;
@@ -619,15 +631,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"));
},
},
React.createElement(Excalidraw.default, {
ref: excalidrawRef,
@@ -651,23 +657,26 @@ export default class ExcalidrawView extends TextFileView {
onChange: (et:ExcalidrawElement[],st:AppState) => {
if(this.justLoaded) {
this.justLoaded = false;
previousSceneVersion = Excalidraw.getSceneVersion(et);
const e = new KeyboardEvent("keydown", {bubbles : true, cancelable : true, shiftKey : true, code:"Digit1"});
this.contentEl.querySelector("canvas")?.dispatchEvent(e);
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 &&
st.draggingElement == null && st.editingGroupId == null &&
st.editingLinearElement == null ) {
const sceneVersion = Excalidraw.getSceneVersion(et);
const sceneVersion = getSceneVersion(et);
if(sceneVersion != previousSceneVersion) {
previousSceneVersion = sceneVersion;
this.dirty=true;
this.dirty=this.file?.path;
}
}
},
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();
})();
}
@@ -687,7 +696,6 @@ export default class ExcalidrawView extends TextFileView {
exportWithDarkMode: exportSettings.withTheme ? (scene.appState?.theme=="light" ? false : true) : false,
... scene.appState,},
exportPadding:10,
//metadata: "Generated by Excalidraw-Obsidian plugin",
});
} catch (error) {
return null;
@@ -710,4 +718,4 @@ export default class ExcalidrawView extends TextFileView {
return null;
}
}
}
}

42
src/Utils.ts Normal file
View File

@@ -0,0 +1,42 @@
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);
}
/**
* 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;
}

View File

@@ -1,5 +1,4 @@
//This is to avoid brackets littering graph view with links
//export function JSON_stringify(x:any):string {return JSON.stringify(x).replaceAll("[","&#91;");}
//This is only for backward compatibility because an early version of obsidian included an encoding to avoid fantom links from littering Obsidian graph view
export function JSON_parse(x:string):any {return JSON.parse(x.replaceAll("&#91;","["));}
import {customAlphabet} from "nanoid";
@@ -13,7 +12,7 @@ export const MAX_COLORS = 5;
export const COLOR_FREQ = 6;
export const RERENDER_EVENT = "excalidraw-embed-rerender";
export const BLANK_DRAWING = '{"type":"excalidraw","version":2,"source":"https://excalidraw.com","elements":[],"appState":{"gridSize":null,"viewBackgroundColor":"#ffffff"}}';
export const FRONTMATTER = ["---","",`${FRONTMATTER_KEY}: unlocked`,"","---", "", ""].join("\n");
export const FRONTMATTER = ["---","",`${FRONTMATTER_KEY}: unlocked`,"","---", "==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠==", "",""].join("\n");
export const EMPTY_MESSAGE = "Hit enter to create a new drawing";
export const LOCK_ICON_NAME = "lock";
export const LOCK_ICON = `<path fill="currentColor" stroke="currentColor" d="M35.715 42.855h28.57v-10.71c0-3.946-1.394-7.313-4.183-10.102-2.793-2.79-6.157-4.188-10.102-4.188-3.945 0-7.309 1.399-10.102 4.188-2.789 2.789-4.183 6.156-4.183 10.102zm46.43 5.36v32.14c0 1.489-.524 2.754-1.563 3.797-1.043 1.043-2.309 1.563-3.797 1.563h-53.57c-1.488 0-2.754-.52-3.797-1.563-1.04-1.043-1.563-2.308-1.563-3.797v-32.14c0-1.488.524-2.754 1.563-3.797 1.043-1.04 2.309-1.563 3.797-1.563H25v-10.71c0-6.848 2.457-12.727 7.367-17.637S43.157 7.145 50 7.145c6.844 0 12.723 2.453 17.633 7.363C72.543 19.418 75 25.297 75 32.145v10.71h1.785c1.488 0 2.754.524 3.797 1.563 1.04 1.043 1.563 2.309 1.563 3.797zm0 0"/>`

View File

@@ -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",
@@ -31,7 +32,7 @@ export default {
SAVE_AS_SVG: "Save as SVG into Vault (CTRL/META+CLICK to export)",
OPEN_LINK: "Open selected text as link\n(SHIFT+CLICK to open in a new pane)",
EXPORT_EXCALIDRAW: "Export to an .Excalidraw file",
UNLOCK_TO_EDIT: "UNLOCK Text Elements to edit",
UNLOCK_TO_EDIT: "UNLOCKED Text Elements automatically to allow edit",
LINK_BUTTON_CLICK_NO_TEXT: 'Select a Text Element containing an internal or external link.\n'+
'SHIFT CLICK this button to open the link in a new pane.\n'+
'CTRL/META CLICK the Text Element on the canvas has the same effect!',

View File

@@ -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, getIMGPathFromExcalidrawFile, 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);
@@ -156,7 +159,7 @@ export default class ExcalidrawPlugin extends Plugin {
if(parts.fheight) img.setAttribute("height",parts.fheight);
img.addClass(parts.style);
img.setAttribute("src","data:image/svg+xml;base64,"+btoa(unescape(encodeURIComponent(svg.outerHTML))));
img.setAttribute("src","data:image/svg+xml;base64,"+btoa(unescape(encodeURIComponent(svg.outerHTML.replaceAll("&nbsp;"," ")))));
return img;
}
@@ -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"),
@@ -505,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) {
@@ -671,6 +678,16 @@ export default class ExcalidrawPlugin extends Plugin {
const fname = this.getNewUniqueFilepath(filename,normalizePath(file.path.substr(0,file.path.lastIndexOf(file.name))));
console.log(fname);
const result = await this.app.vault.create(fname,FRONTMATTER + exportSceneToMD(data));
if (this.settings.keepInSync) {
['.svg','.png'].forEach( (ext:string)=>{
const oldIMGpath = file.path.substring(0,file.path.lastIndexOf(".excalidraw")) + ext;
const imgFile = this.app.vault.getAbstractFileByPath(normalizePath(oldIMGpath));
if(imgFile && imgFile instanceof TFile) {
const newIMGpath = fname.substr(0,fname.lastIndexOf(".md")) + ext;
this.app.vault.rename(imgFile,newIMGpath);
}
});
}
if (!keepOriginal) this.app.vault.delete(file);
return result;
}
@@ -778,16 +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 oldIMGpath = oldPath.substring(0,oldPath.lastIndexOf('.md')) + 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('.md')) + ext;
const newIMGpath = getIMGPathFromExcalidrawFile(file.path,ext);
await self.app.vault.rename(imgFile,newIMGpath);
}
});
@@ -816,7 +835,9 @@ export default class ExcalidrawPlugin extends Plugin {
const deleteEventHandler = async (file:TFile) => {
if (!(file instanceof TFile)) return;
//@ts-ignore
if (file.unsaveCachedData && !file.unsafeCachedData.search(/---\n[\s\S]*excalidraw-plugin:\s*(locked|unlocked)\n[\s\S]*---/gm)==-1) return;
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;
//close excalidraw view where this file is open
const leaves = self.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
@@ -829,7 +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 imgPath = file.path.substring(0,file.path.lastIndexOf('.md')) + 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);
@@ -847,6 +868,9 @@ export default class ExcalidrawPlugin extends Plugin {
for (let i=0;i<leaves.length;i++) {
(leaves[i].view as ExcalidrawView).save();
}
this.settings.drawingOpenCount += this.opencount;
this.settings.loadCount++;
this.saveSettings();
}
self.registerEvent(
self.app.workspace.on("quit",quitEventHandler)
@@ -881,7 +905,9 @@ export default class ExcalidrawPlugin extends Plugin {
excalidrawLeaves.forEach((leaf) => {
this.setMarkdownView(leaf);
});
this.settings.drawingOpenCount += this.opencount;
this.settings.loadCount++;
this.saveSettings();
}
public embedDrawing(data:string) {
@@ -894,7 +920,7 @@ export default class ExcalidrawPlugin extends Plugin {
}
private async loadSettings() {
public async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
@@ -902,6 +928,14 @@ export default class ExcalidrawPlugin extends Plugin {
await this.saveData(this.settings);
}
public getStencilLibrary():string {
return this.settings.library;
}
public setStencilLibrary(library:string) {
this.settings.library = library;
}
public triggerEmbedUpdates(filepath?:string){
const e = document.createEvent("Event")
e.initEvent(RERENDER_EVENT,true,false);
@@ -961,10 +995,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 +1049,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);
}
}

View File

@@ -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,
@@ -16,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,
@@ -25,12 +27,12 @@ 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,
library: string,
}
export const DEFAULT_SETTINGS: ExcalidrawSettings = {
@@ -39,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,
@@ -50,12 +52,12 @@ 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,
library: `{"type":"excalidrawlib","version":1,"library":[]}`,
}
export class ExcalidrawSettingTab extends PluginSettingTab {
@@ -92,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
@@ -112,7 +114,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
}
}
}
}));
}));*/
this.containerEl.createEl('h1', {text: t("FILENAME_HEAD")});
containerEl.createDiv('',(el) => {
@@ -185,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;
@@ -271,7 +273,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
this.plugin.settings.autoexportPNG = value;
await this.plugin.saveSettings();
}));
this.containerEl.createEl('h1', {text: t("COMPATIBILITY_HEAD")});
new Setting(containerEl)

View File

@@ -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");
}
}*/