mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
MVP
This commit is contained in:
@@ -1 +1 @@
|
||||
{"folder":"excalidraw","templateFilePath":"","openFile":"excalidraw/new file.excalidraw","settings":{"folder":"excalidraw","templateFilePath":""}}
|
||||
{"folder":"excalidraw","templateFilePath":"excalidraw/Template.excalidraw","openFile":"excalidraw/new file.excalidraw","settings":{"folder":"excalidraw","templateFilePath":""}}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = `<path fill="currentColor" stroke="currentColor" d="M52.6,0C40.5,0,28.8,3.8,20.2,11.6S6,31.4,6,47.4c0,26,20.2,52.6,48.8,52.6h0.4c0,0,0,0,0.1,0c5.9-0.1,11.6-2.1,15.8-5.9 c4.2-3.8,6.9-9.4,6.9-16.4c0-3.9-1.8-6.9-2.8-9.3c0,0,0,0,0-0.1c-1.4-3.5-0.9-5.1,0.4-6.8c1.3-1.8,4-3.5,7-5.7 c6.1-4.4,13.5-11.2,13.4-25.9c0-5.2-2.9-12.5-9.7-18.7C79.5,4.9,68.6,0,52.6,0L52.6,0z M52.6,4c15.2,0,25,4.6,31.1,10.1 c6.1,5.5,8.3,12.1,8.3,15.8c0.1,13.5-5.9,18.5-11.8,22.7c-2.9,2.1-5.8,3.9-7.8,6.6c-2,2.6-2.6,6.3-0.9,10.6c0,0,0,0,0,0.1 c1.1,2.8,2.4,5.4,2.4,7.9c0,6-2.2,10.4-5.6,13.5s-8.1,4.8-13.2,4.9h-0.4C28.7,96,10,71.3,10,47.4c0-15,5.1-25.7,12.9-32.8 S41.3,4,52.6,4z M53,10c-2.6,0-4.9,1.4-6.5,3.4c-1.6,2-2.5,4.7-2.5,7.6c0,2.9,0.9,5.5,2.5,7.6c1.6,2,3.9,3.4,6.5,3.4 c2.6,0,4.9-1.4,6.5-3.4c1.6-2,2.5-4.7,2.5-7.6s-0.9-5.5-2.5-7.6C57.9,11.4,55.6,10,53,10z M53,14c1.2,0,2.4,0.6,3.4,1.9 c1,1.2,1.6,3.1,1.6,5.1s-0.7,3.9-1.6,5.1c-1,1.2-2.1,1.9-3.4,1.9s-2.4-0.6-3.4-1.9c-1-1.2-1.6-3.1-1.6-5.1c0-2.1,0.7-3.9,1.6-5.1 C50.6,14.6,51.8,14,53,14z M31,20c-2.6,0-4.9,1.4-6.5,3.4c-1.6,2-2.5,4.7-2.5,7.6s0.9,5.5,2.5,7.6c1.6,2,3.9,3.4,6.5,3.4 s4.9-1.4,6.5-3.4c1.6-2,2.5-4.7,2.5-7.6s-0.9-5.5-2.5-7.6C35.9,21.4,33.6,20,31,20z M75,20c-2.6,0-4.9,1.4-6.5,3.4 c-1.6,2-2.5,4.7-2.5,7.6s0.9,5.5,2.5,7.6c1.6,2,3.9,3.4,6.5,3.4s4.9-1.4,6.5-3.4c1.6-2,2.5-4.7,2.5-7.6s-0.9-5.5-2.5-7.6 C79.9,21.4,77.6,20,75,20z M31,24c1.2,0,2.4,0.6,3.4,1.9S36,28.9,36,31s-0.7,3.9-1.6,5.1c-1,1.2-2.1,1.9-3.4,1.9 c-1.2,0-2.4-0.6-3.4-1.9c-1-1.2-1.6-3.1-1.6-5.1s0.7-3.9,1.6-5.1S29.8,24,31,24z M75,24c1.2,0,2.4,0.6,3.4,1.9 c1,1.2,1.6,3.1,1.6,5.1s-0.7,3.9-1.6,5.1c-1,1.2-2.1,1.9-3.4,1.9s-2.4-0.6-3.4-1.9c-1-1.2-1.6-3.1-1.6-5.1s0.7-3.9,1.6-5.1 S73.8,24,75,24z M29,46c-2.6,0-4.9,1.4-6.5,3.4c-1.6,2-2.5,4.7-2.5,7.6s0.9,5.5,2.5,7.6c1.6,2,3.9,3.4,6.5,3.4s4.9-1.4,6.5-3.4 S38,59.9,38,57s-0.9-5.5-2.5-7.6C33.9,47.4,31.6,46,29,46z M29,50c1.2,0,2.4,0.6,3.4,1.9c1,1.2,1.6,3.1,1.6,5.1s-0.7,3.9-1.6,5.1 c-1,1.2-2.1,1.9-3.4,1.9c-1.2,0-2.4-0.6-3.4-1.9c-1-1.2-1.6-3.1-1.6-5.1s0.7-3.9,1.6-5.1C26.6,50.6,27.8,50,29,50z M54,66 c-6.6,0-12,5.4-12,12s5.4,12,12,12s12-5.4,12-12S60.6,66,54,66z M54,70c4.6,0,8,3.4,8,8s-3.4,8-8,8s-8-3.4-8-8S49.4,70,54,70z"/>`;
|
||||
export const PALETTE_ICON = `<path fill="currentColor" stroke="currentColor" d="M52.6,0C40.5,0,28.8,3.8,20.2,11.6S6,31.4,6,47.4c0,26,20.2,52.6,48.8,52.6h0.4c0,0,0,0,0.1,0c5.9-0.1,11.6-2.1,15.8-5.9 c4.2-3.8,6.9-9.4,6.9-16.4c0-3.9-1.8-6.9-2.8-9.3c0,0,0,0,0-0.1c-1.4-3.5-0.9-5.1,0.4-6.8c1.3-1.8,4-3.5,7-5.7 c6.1-4.4,13.5-11.2,13.4-25.9c0-5.2-2.9-12.5-9.7-18.7C79.5,4.9,68.6,0,52.6,0L52.6,0z M52.6,4c15.2,0,25,4.6,31.1,10.1 c6.1,5.5,8.3,12.1,8.3,15.8c0.1,13.5-5.9,18.5-11.8,22.7c-2.9,2.1-5.8,3.9-7.8,6.6c-2,2.6-2.6,6.3-0.9,10.6c0,0,0,0,0,0.1 c1.1,2.8,2.4,5.4,2.4,7.9c0,6-2.2,10.4-5.6,13.5s-8.1,4.8-13.2,4.9h-0.4C28.7,96,10,71.3,10,47.4c0-15,5.1-25.7,12.9-32.8 S41.3,4,52.6,4z M53,10c-2.6,0-4.9,1.4-6.5,3.4c-1.6,2-2.5,4.7-2.5,7.6c0,2.9,0.9,5.5,2.5,7.6c1.6,2,3.9,3.4,6.5,3.4 c2.6,0,4.9-1.4,6.5-3.4c1.6-2,2.5-4.7,2.5-7.6s-0.9-5.5-2.5-7.6C57.9,11.4,55.6,10,53,10z M53,14c1.2,0,2.4,0.6,3.4,1.9 c1,1.2,1.6,3.1,1.6,5.1s-0.7,3.9-1.6,5.1c-1,1.2-2.1,1.9-3.4,1.9s-2.4-0.6-3.4-1.9c-1-1.2-1.6-3.1-1.6-5.1c0-2.1,0.7-3.9,1.6-5.1 C50.6,14.6,51.8,14,53,14z M31,20c-2.6,0-4.9,1.4-6.5,3.4c-1.6,2-2.5,4.7-2.5,7.6s0.9,5.5,2.5,7.6c1.6,2,3.9,3.4,6.5,3.4 s4.9-1.4,6.5-3.4c1.6-2,2.5-4.7,2.5-7.6s-0.9-5.5-2.5-7.6C35.9,21.4,33.6,20,31,20z M75,20c-2.6,0-4.9,1.4-6.5,3.4 c-1.6,2-2.5,4.7-2.5,7.6s0.9,5.5,2.5,7.6c1.6,2,3.9,3.4,6.5,3.4s4.9-1.4,6.5-3.4c1.6-2,2.5-4.7,2.5-7.6s-0.9-5.5-2.5-7.6 C79.9,21.4,77.6,20,75,20z M31,24c1.2,0,2.4,0.6,3.4,1.9S36,28.9,36,31s-0.7,3.9-1.6,5.1c-1,1.2-2.1,1.9-3.4,1.9 c-1.2,0-2.4-0.6-3.4-1.9c-1-1.2-1.6-3.1-1.6-5.1s0.7-3.9,1.6-5.1S29.8,24,31,24z M75,24c1.2,0,2.4,0.6,3.4,1.9 c1,1.2,1.6,3.1,1.6,5.1s-0.7,3.9-1.6,5.1c-1,1.2-2.1,1.9-3.4,1.9s-2.4-0.6-3.4-1.9c-1-1.2-1.6-3.1-1.6-5.1s0.7-3.9,1.6-5.1 S73.8,24,75,24z M29,46c-2.6,0-4.9,1.4-6.5,3.4c-1.6,2-2.5,4.7-2.5,7.6s0.9,5.5,2.5,7.6c1.6,2,3.9,3.4,6.5,3.4s4.9-1.4,6.5-3.4 S38,59.9,38,57s-0.9-5.5-2.5-7.6C33.9,47.4,31.6,46,29,46z M29,50c1.2,0,2.4,0.6,3.4,1.9c1,1.2,1.6,3.1,1.6,5.1s-0.7,3.9-1.6,5.1 c-1,1.2-2.1,1.9-3.4,1.9c-1.2,0-2.4-0.6-3.4-1.9c-1-1.2-1.6-3.1-1.6-5.1s0.7-3.9,1.6-5.1C26.6,50.6,27.8,50,29,50z M54,66 c-6.6,0-12,5.4-12,12s5.4,12,12,12s12-5.4,12-12S60.6,66,54,66z M54,70c4.6,0,8,3.4,8,8s-3.4,8-8,8s-8-3.4-8-8S49.4,70,54,70z"/>`;
|
||||
export const EMPTY_MESSAGE = "Hit enter to create a new drawing";
|
||||
63
src/main.ts
63
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());
|
||||
|
||||
@@ -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<TFile> {
|
||||
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<TFile> {
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user