From f2012de41cd9d00c6d986ba4a15484e00a5852e1 Mon Sep 17 00:00:00 2001 From: Zsolt Viczian Date: Tue, 20 Apr 2021 13:57:53 +0200 Subject: [PATCH] MVP --- data.json | 2 +- src/ExcalidrawView.ts | 37 +++++++++++++++++++++++-- src/constants.ts | 3 ++- src/main.ts | 63 ++++++++++++++++++++++++++++++++++++++++--- src/openDrawing.ts | 40 ++++++++++++++++++++++----- 5 files changed, 130 insertions(+), 15 deletions(-) diff --git a/data.json b/data.json index 0268b74..5bb7a5e 100644 --- a/data.json +++ b/data.json @@ -1 +1 @@ -{"folder":"excalidraw","templateFilePath":"","openFile":"excalidraw/new file.excalidraw","settings":{"folder":"excalidraw","templateFilePath":""}} \ No newline at end of file +{"folder":"excalidraw","templateFilePath":"excalidraw/Template.excalidraw","openFile":"excalidraw/new file.excalidraw","settings":{"folder":"excalidraw","templateFilePath":""}} \ No newline at end of file diff --git a/src/ExcalidrawView.ts b/src/ExcalidrawView.ts index 0501e51..16658a1 100644 --- a/src/ExcalidrawView.ts +++ b/src/ExcalidrawView.ts @@ -1,7 +1,7 @@ import { TextFileView, WorkspaceLeaf } from "obsidian"; import * as React from "react"; import * as ReactDOM from "react-dom"; -import Excalidraw from "@excalidraw/excalidraw"; +import Excalidraw, {exportToSvg} from "@excalidraw/excalidraw"; import { ExcalidrawElement } from "@excalidraw/excalidraw/types/element/types"; import { AppState } from "@excalidraw/excalidraw/types/types"; @@ -14,6 +14,12 @@ export default class ExcalidrawView extends TextFileView { this.getScene = null; } + async onClose() { + this.requestSave(); + } + + + // clear the view content clear() { ReactDOM.unmountComponentAtNode(this.contentEl); @@ -26,7 +32,17 @@ export default class ExcalidrawView extends TextFileView { else return ''; } + + setViewData (data: string, clear: boolean) { + if (this.app.workspace.layoutReady) { + this.loadDrawing(data,clear); + } else { + this.registerEvent(this.app.workspace.on('layout-ready', async () => this.loadDrawing(data,clear))); + } + } + + private loadDrawing (data:string, clear:boolean) :void { if(clear) this.clear(); const excalidrawData = JSON.parse(data); this.instantiateExcalidraw({ @@ -84,7 +100,7 @@ export default class ExcalidrawView extends TextFileView { window.addEventListener("resize", onResize); return () => window.removeEventListener("resize", onResize); }, [excalidrawWrapperRef]); - + this.getScene = function() { const el: ExcalidrawElement[] = excalidrawRef.current.getSceneElements(); const st: AppState = excalidrawRef.current.getAppState(); @@ -128,4 +144,21 @@ export default class ExcalidrawView extends TextFileView { ); }),(this as any).contentEl); } + + public static getSVG(data:string):SVGSVGElement { + try { + const excalidrawData = JSON.parse(data); + return exportToSvg({ + elements: excalidrawData.elements, + appState: { + exportBackground: true, + exportWithDarkMode: excalidrawData.appState?.theme=="light" ? false : true, + ... excalidrawData.appState,}, + exportPadding:10, + metadata: "Generated by Excalidraw-Obsidian plugin", + }); + } catch (error) { + return null; + } + } } \ No newline at end of file diff --git a/src/constants.ts b/src/constants.ts index a3895a7..76eb832 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -2,4 +2,5 @@ export const VIEW_TYPE_EXCALIDRAW = "excalidraw"; export const MAX_COLORS = 5; export const COLOR_FREQ = 6; export const BLANK_DRAWING = '{"type":"excalidraw","version":2,"source":"https://excalidraw.com","elements":[],"appState":{"gridSize":null,"viewBackgroundColor":"#ffffff"}}'; -export const PALETTE_ICON = ``; \ No newline at end of file +export const PALETTE_ICON = ``; +export const EMPTY_MESSAGE = "Hit enter to create a new drawing"; \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 9128408..16f6d3e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -8,7 +8,10 @@ import { PluginManifest, EventRef, Menu, - TAbstractFile + TAbstractFile, + MarkdownPostProcessorContext, + MarkdownView, + Editor, } from 'obsidian'; import { BLANK_DRAWING, VIEW_TYPE_EXCALIDRAW, PALETTE_ICON } from './constants'; import ExcalidrawView from './ExcalidrawView'; @@ -17,8 +20,9 @@ import { DEFAULT_SETTINGS, ExcalidrawSettingTab } from './settings'; -import {OpenFileDialog} from './openDrawing'; +import {openDialogAction, OpenFileDialog} from './openDrawing'; import {getDateString} from './utils' +import { exportToSvg } from '@excalidraw/excalidraw'; export default class ExcalidrawPlugin extends Plugin { @@ -45,22 +49,59 @@ export default class ExcalidrawPlugin extends Plugin { this.registerExtensions(["excalidraw"],"excalidraw"); + this.registerMarkdownCodeBlockProcessor('excalidraw', (source,el,ctx) => { + const parseError = (message: string) => { + el.createDiv("excalidraw-error",(el)=> { + el.createEl("p","Please provide a link to an excalidraw file: [[file.excalidraw]]"); + el.createEl("p",message); + el.createEl("p",source); + }) + } + + const filename = source.match(/\[{2}(.*)\]{2}/m); + if(filename.length==2) { + const file:TFile = (this.app.vault.getAbstractFileByPath(filename[1]) as TFile); + if(file) { + if(file.extension == "excalidraw") { + this.app.vault.read(file).then(async (content: string) => { + const svg = ExcalidrawView.getSVG(content); + if(svg) { + el.createDiv("excalidraw-svg",(el)=> { + el.appendChild(svg); + }) + + } else parseError("Parse error. Not a valid Excalidraw file."); + }); + } else parseError("Not an excalidraw file. Must have extension .excalidraw"); + } else parseError("File does not exist"); + } else parseError("No link to file found in codeblock."); + }); + await this.loadSettings(); this.addSettingTab(new ExcalidrawSettingTab(this.app, this)); this.openDialog = new OpenFileDialog(this.app, this); this.addRibbonIcon('excalidraw', 'Excalidraw', async () => { - this.openDialog.start(); + this.openDialog.start(openDialogAction.openFile); }); this.addCommand({ id: "excalidraw-open", name: "Open existing drawing or create new one", callback: () => { - this.openDialog.start(); + this.openDialog.start(openDialogAction.openFile); }, }); + this.addCommand({ + id: "excalidraw-insert-transclusion", + name: "Insert link to .excalidraw file into markdown document", + callback: () => { + this.openDialog.start(openDialogAction.insertLink); + }, + }); + + this.addCommand({ id: "excalidraw-autocreate", name: "Create a new drawing", @@ -69,6 +110,20 @@ export default class ExcalidrawPlugin extends Plugin { }, }); } + + public insertCodeblock(data:string) { + const activeView = this.app.workspace.getActiveViewOfType(MarkdownView); + if(activeView) { + const editor = activeView.editor; + let doc = editor.getDoc(); + doc.replaceSelection( + String.fromCharCode(96,96,96) + + "excalidraw\n[["+data+"]]\n" + + String.fromCharCode(96,96,96)); + editor.focus(); + } + + } private async loadSettings() { this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); diff --git a/src/openDrawing.ts b/src/openDrawing.ts index 4b0cf86..cf132e7 100644 --- a/src/openDrawing.ts +++ b/src/openDrawing.ts @@ -1,24 +1,32 @@ import { App, FuzzySuggestModal, TFile, TFolder, normalizePath, Vault, TAbstractFile, Instruction } from "obsidian"; import ExcalidrawPlugin from './main'; import ExcalidrawView from './view'; +import {EMPTY_MESSAGE} from './constants'; + +export enum openDialogAction { + openFile, + insertLink, +} export class OpenFileDialog extends FuzzySuggestModal { public app: App; private plugin: ExcalidrawPlugin; + private action: openDialogAction; + constructor(app: App, plugin: ExcalidrawPlugin) { super(app); this.app = app; + this.action = openDialogAction.openFile; this.plugin = plugin; - const EMPTY_MESSAGE = "Hit enter to create a new drawing"; - this.emptyStateText = EMPTY_MESSAGE; this.setInstructions([{ - command: "Select an existing drawing or type title for your new drawing, then hit enter.", - purpose: "The new drawing will be created in the default Excalidraw folder specified in Settings.", + command: "Type name of drawing to select.", + purpose: "", }]); + this.inputEl.onkeyup = (e) => { - if(e.key=="Enter") { + if(e.key=="Enter" && this.action == openDialogAction.openFile) { if (this.containerEl.innerText.includes(EMPTY_MESSAGE)) { this.plugin.createDrawing(this.plugin.settings.folder+'/'+this.inputEl.value+'.excalidraw'); this.close(); @@ -38,10 +46,28 @@ export class OpenFileDialog extends FuzzySuggestModal { } onChooseItem(item: TFile, _evt: MouseEvent | KeyboardEvent): void { - this.plugin.openDrawing(item); + switch(this.action) { + case(openDialogAction.openFile): + this.plugin.openDrawing(item); + break; + case(openDialogAction.insertLink): + this.plugin.insertCodeblock(item.path); + break; + } } - start(): void { + start(action:openDialogAction): void { + this.action = action; + switch(action) { + case (openDialogAction.openFile): + this.emptyStateText = EMPTY_MESSAGE; + this.setPlaceholder("Select existing drawing or type name of new and hit enter."); + break; + case (openDialogAction.insertLink): + this.emptyStateText = "No file matches your query."; + this.setPlaceholder("Select existing drawing to insert into document."); + break; + } try { let files = this.getItems(); this.open();