This commit is contained in:
Zsolt Viczian
2021-07-12 23:07:40 +02:00
parent ae31ad0870
commit c45faef141
6 changed files with 60 additions and 202 deletions

View File

@@ -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",

View File

@@ -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 &&

View File

@@ -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;
}

View File

@@ -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",

View File

@@ -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){

View File

@@ -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)