latex WIP

This commit is contained in:
Zsolt Viczian
2021-10-29 11:01:08 +02:00
parent 8a1cf72095
commit e6b5b0d125
9 changed files with 341 additions and 208 deletions

View File

@@ -31,6 +31,7 @@
"@types/node": "^15.12.4",
"@types/react-dom": "^17.0.9",
"cross-env": "^7.0.3",
"html2canvas": "^1.3.2",
"js-beautify": "1.13.3",
"nanoid": "^3.1.23",
"obsidian": "^0.12.16",

View File

@@ -10,156 +10,154 @@ import {
TFile
} from "obsidian"
import ExcalidrawView, { TextMode } from "./ExcalidrawView";
import { ExcalidrawData, getJSON, getSVGString } from "./ExcalidrawData";
import { ExcalidrawData} from "./ExcalidrawData";
import {
FRONTMATTER,
nanoid,
JSON_parse,
VIEW_TYPE_EXCALIDRAW,
MAX_IMAGE_SIZE
MAX_IMAGE_SIZE,
} from "./constants";
import { embedFontsInSVG, generateSVGString, getObsidianImage, getPNG, getSVG, loadSceneFiles, scaleLoadedImage, svgToBase64, wrapText } from "./Utils";
import { embedFontsInSVG, generateSVGString, getObsidianImage, getPNG, getSVG, loadSceneFiles, scaleLoadedImage, svgToBase64, tex2dataURL, wrapText } from "./Utils";
import { AppState } from "@zsviczian/excalidraw/types/types";
declare type ConnectionPoint = "top"|"bottom"|"left"|"right";
export interface ExcalidrawAutomate extends Window {
ExcalidrawAutomate: {
plugin: ExcalidrawPlugin;
elementsDict: {};
imagesDict: {};
style: {
strokeColor: string;
backgroundColor: string;
angle: number;
fillStyle: FillStyle;
strokeWidth: number;
storkeStyle: StrokeStyle;
roughness: number;
opacity: number;
strokeSharpness: StrokeSharpness;
fontFamily: number;
fontSize: number;
textAlign: string;
verticalAlign: string;
startArrowHead: string;
endArrowHead: string;
}
canvas: {
theme: string,
viewBackgroundColor: string,
gridSize: number
};
setFillStyle (val:number): void;
setStrokeStyle (val:number): void;
setStrokeSharpness (val:number): void;
setFontFamily (val:number): void;
setTheme (val:number): void;
addToGroup (objectIds:[]):string;
toClipboard (templatePath?:string): void;
getElements ():ExcalidrawElement[];
getElement (id:string):ExcalidrawElement;
create (
params?: {
filename?: string,
foldername?:string,
templatePath?:string,
onNewPane?: boolean,
frontmatterKeys?:{
"excalidraw-plugin"?: "raw"|"parsed",
"excalidraw-link-prefix"?: string,
"excalidraw-link-brackets"?: boolean,
"excalidraw-url-prefix"?: string
}
}
):Promise<string>;
createSVG (templatePath?:string, embedFont?:boolean):Promise<SVGSVGElement>;
createPNG (templatePath?:string):Promise<any>;
wrapText (text:string, lineLen:number):string;
addRect (topX:number, topY:number, width:number, height:number):string;
addDiamond (topX:number, topY:number, width:number, height:number):string;
addEllipse (topX:number, topY:number, width:number, height:number):string;
addBlob (topX:number, topY:number, width:number, height:number):string;
addText (
topX:number,
topY:number,
text:string,
formatting?: {
wrapAt?:number,
width?:number,
height?:number,
textAlign?: string,
box?: boolean|"box"|"blob"|"ellipse"|"diamond",
boxPadding?: number
},
id?:string
):string;
addLine(points: [[x:number,y:number]]):string;
addArrow (
points: [[x:number,y:number]],
formatting?: {
startArrowHead?:string,
endArrowHead?:string,
startObjectId?:string,
endObjectId?:string
}
):string ;
addImage(topX:number, topY:number, imageFile: TFile):Promise<string>;
connectObjects (
objectA: string,
connectionA: ConnectionPoint,
objectB: string,
connectionB: ConnectionPoint,
formatting?: {
numberOfPoints?: number,
startArrowHead?:string,
endArrowHead?:string,
padding?: number
}
):void;
clear (): void;
reset (): void;
isExcalidrawFile (f:TFile): boolean;
//view manipulation
targetView: ExcalidrawView;
setView (view:ExcalidrawView|"first"|"active"):ExcalidrawView;
getExcalidrawAPI ():any;
getViewElements ():ExcalidrawElement[];
deleteViewElements (el: ExcalidrawElement[]):boolean;
getViewSelectedElement ():ExcalidrawElement;
getViewSelectedElements ():ExcalidrawElement[];
viewToggleFullScreen (forceViewMode?:boolean):void;
connectObjectWithViewSelectedElement (
objectA:string,
connectionA: ConnectionPoint,
connectionB: ConnectionPoint,
formatting?: {
numberOfPoints?: number,
startArrowHead?:string,
endArrowHead?:string,
padding?: number
}
):boolean;
addElementsToView (repositionToCursor:boolean, save:boolean):Promise<boolean>;
onDropHook (data: {
ea: ExcalidrawAutomate,
event: React.DragEvent<HTMLDivElement>,
draggable: any, //Obsidian draggable object
type: "file"|"text"|"unknown",
payload: {
files: TFile[], //TFile[] array of dropped files
text: string, //string
},
excalidrawFile: TFile, //the file receiving the drop event
view: ExcalidrawView, //the excalidraw view receiving the drop
pointerPosition: {x:number, y:number} //the pointer position on canvas at the time of drop
}):boolean;
export interface ExcalidrawAutomate {
plugin: ExcalidrawPlugin;
elementsDict: {};
imagesDict: {};
style: {
strokeColor: string;
backgroundColor: string;
angle: number;
fillStyle: FillStyle;
strokeWidth: number;
storkeStyle: StrokeStyle;
roughness: number;
opacity: number;
strokeSharpness: StrokeSharpness;
fontFamily: number;
fontSize: number;
textAlign: string;
verticalAlign: string;
startArrowHead: string;
endArrowHead: string;
}
canvas: {
theme: string,
viewBackgroundColor: string,
gridSize: number
};
setFillStyle (val:number): void;
setStrokeStyle (val:number): void;
setStrokeSharpness (val:number): void;
setFontFamily (val:number): void;
setTheme (val:number): void;
addToGroup (objectIds:[]):string;
toClipboard (templatePath?:string): void;
getElements ():ExcalidrawElement[];
getElement (id:string):ExcalidrawElement;
create (
params?: {
filename?: string,
foldername?:string,
templatePath?:string,
onNewPane?: boolean,
frontmatterKeys?:{
"excalidraw-plugin"?: "raw"|"parsed",
"excalidraw-link-prefix"?: string,
"excalidraw-link-brackets"?: boolean,
"excalidraw-url-prefix"?: string
}
}
):Promise<string>;
createSVG (templatePath?:string, embedFont?:boolean):Promise<SVGSVGElement>;
createPNG (templatePath?:string):Promise<any>;
wrapText (text:string, lineLen:number):string;
addRect (topX:number, topY:number, width:number, height:number):string;
addDiamond (topX:number, topY:number, width:number, height:number):string;
addEllipse (topX:number, topY:number, width:number, height:number):string;
addBlob (topX:number, topY:number, width:number, height:number):string;
addText (
topX:number,
topY:number,
text:string,
formatting?: {
wrapAt?:number,
width?:number,
height?:number,
textAlign?: string,
box?: boolean|"box"|"blob"|"ellipse"|"diamond",
boxPadding?: number
},
id?:string
):string;
addLine(points: [[x:number,y:number]]):string;
addArrow (
points: [[x:number,y:number]],
formatting?: {
startArrowHead?:string,
endArrowHead?:string,
startObjectId?:string,
endObjectId?:string
}
):string ;
addImage(topX:number, topY:number, imageFile: TFile):Promise<string>;
addLaTex(topX:number, topY:number, tex: string, color?:string):Promise<string>;
connectObjects (
objectA: string,
connectionA: ConnectionPoint,
objectB: string,
connectionB: ConnectionPoint,
formatting?: {
numberOfPoints?: number,
startArrowHead?:string,
endArrowHead?:string,
padding?: number
}
):void;
clear (): void;
reset (): void;
isExcalidrawFile (f:TFile): boolean;
//view manipulation
targetView: ExcalidrawView;
setView (view:ExcalidrawView|"first"|"active"):ExcalidrawView;
getExcalidrawAPI ():any;
getViewElements ():ExcalidrawElement[];
deleteViewElements (el: ExcalidrawElement[]):boolean;
getViewSelectedElement ():ExcalidrawElement;
getViewSelectedElements ():ExcalidrawElement[];
viewToggleFullScreen (forceViewMode?:boolean):void;
connectObjectWithViewSelectedElement (
objectA:string,
connectionA: ConnectionPoint,
connectionB: ConnectionPoint,
formatting?: {
numberOfPoints?: number,
startArrowHead?:string,
endArrowHead?:string,
padding?: number
}
):boolean;
addElementsToView (repositionToCursor:boolean, save:boolean):Promise<boolean>;
onDropHook (data: {
ea: ExcalidrawAutomate,
event: React.DragEvent<HTMLDivElement>,
draggable: any, //Obsidian draggable object
type: "file"|"text"|"unknown",
payload: {
files: TFile[], //TFile[] array of dropped files
text: string, //string
},
excalidrawFile: TFile, //the file receiving the drop event
view: ExcalidrawView, //the excalidraw view receiving the drop
pointerPosition: {x:number, y:number} //the pointer position on canvas at the time of drop
}):boolean;
}
declare let window: ExcalidrawAutomate;
declare let window: any;
export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin):Promise<ExcalidrawAutomate> {
window.ExcalidrawAutomate = {
plugin: plugin,
elementsDict: {},
@@ -249,7 +247,7 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
return id;
},
async toClipboard(templatePath?:string) {
const template = templatePath ? (await getTemplate(templatePath)) : null;
const template = templatePath ? (await getTemplate(this.plugin,templatePath)) : null;
let elements = template ? template.elements : [];
elements = elements.concat(this.getElements());
navigator.clipboard.writeText(
@@ -283,7 +281,7 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
}
}
):Promise<string> {
const template = params?.templatePath ? (await getTemplate(params.templatePath,true)) : null;
const template = params?.templatePath ? (await getTemplate(this.plugin,params.templatePath,true)) : null;
let elements = template ? template.elements : [];
elements = elements.concat(this.getElements());
let frontmatter:string;
@@ -340,7 +338,7 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
},
async createSVG(templatePath?:string,embedFont:boolean = false):Promise<SVGSVGElement> {
const automateElements = this.getElements();
const template = templatePath ? (await getTemplate(templatePath,true)) : null;
const template = templatePath ? (await getTemplate(this.plugin,templatePath,true)) : null;
let elements = template ? template.elements : [];
elements = elements.concat(automateElements);
const svg = await getSVG(
@@ -364,7 +362,7 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
},
async createPNG(templatePath?:string, scale:number=1) {
const automateElements = this.getElements();
const template = templatePath ? (await getTemplate(templatePath,true)) : null;
const template = templatePath ? (await getTemplate(this.plugin,templatePath,true)) : null;
let elements = template ? template.elements : [];
elements = elements.concat(automateElements);
return getPNG(
@@ -477,12 +475,13 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
boxId = this.addRect(topX-boxPadding,topY-boxPadding,width+2*boxPadding,height+2*boxPadding);
}
}
const ea = window.ExcalidrawAutomate;
this.elementsDict[id] = {
text: text,
fontSize: window.ExcalidrawAutomate.style.fontSize,
fontFamily: window.ExcalidrawAutomate.style.fontFamily,
textAlign: formatting?.textAlign ? formatting.textAlign : window.ExcalidrawAutomate.style.textAlign,
verticalAlign: window.ExcalidrawAutomate.style.verticalAlign,
fontSize: ea.style.fontSize,
fontFamily: ea.style.fontFamily,
textAlign: formatting?.textAlign ? formatting.textAlign : ea.style.textAlign,
verticalAlign: ea.style.verticalAlign,
baseline: baseline,
... boxedElement(id,"text",topX,topY,width,height)
};
@@ -529,14 +528,15 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
},
async addImage(topX:number, topY:number, imageFile: TFile):Promise<string> {
const id = nanoid();
const image = await getObsidianImage(this.plugin.app,imageFile);
const image = await getObsidianImage(this.plugin,imageFile);
if(!image) return null;
this.imagesDict[image.fileId] = {
mimeType: image.mimeType,
id: image.fileId,
dataURL: image.dataURL,
created: image.created,
file: imageFile.path
file: imageFile.path,
tex: null
}
if (Math.max(image.size.width,image.size.height) > MAX_IMAGE_SIZE) {
const scale = MAX_IMAGE_SIZE/Math.max(image.size.width,image.size.height);
@@ -548,6 +548,23 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
this.elementsDict[id].scale = [1,1];
return id;
},
async addLaTex(topX:number, topY:number, tex:string, color:string = "black"):Promise<string> {
const id = nanoid();
const image = await tex2dataURL(tex, color);
if(!image) return null;
this.imagesDict[image.fileId] = {
mimeType: image.mimeType,
id: image.fileId,
dataURL: image.dataURL,
created: image.created,
file: null,
tex: tex
}
this.elementsDict[id] = boxedElement(id,"image",topX,topY,image.size.width,image.size.height);
this.elementsDict[id].fileId = image.fileId;
this.elementsDict[id].scale = [1,1];
return id;
},
connectObjects(objectA: string, connectionA: ConnectionPoint, objectB: string, connectionB: ConnectionPoint, formatting?:{numberOfPoints?: number,startArrowHead?:string,endArrowHead?:string, padding?: number}):void {
if(!(this.elementsDict[objectA] && this.elementsDict[objectB])) {
return;
@@ -723,6 +740,7 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
onDropHook:null,
};
await initFonts();
return window.ExcalidrawAutomate;
}
export function destroyExcalidrawAutomate() {
@@ -738,6 +756,7 @@ function normalizeLinePoints(points:[[x:number,y:number]],box:{x:number,y:number
}
function boxedElement(id:string,eltype:any,x:number,y:number,w:number,h:number) {
const ea = window.ExcalidrawAutomate;
return {
id: id,
type: eltype,
@@ -745,15 +764,15 @@ function boxedElement(id:string,eltype:any,x:number,y:number,w:number,h:number)
y: y,
width: w,
height: h,
angle: window.ExcalidrawAutomate.style.angle,
strokeColor: window.ExcalidrawAutomate.style.strokeColor,
backgroundColor: window.ExcalidrawAutomate.style.backgroundColor,
fillStyle: window.ExcalidrawAutomate.style.fillStyle,
strokeWidth: window.ExcalidrawAutomate.style.strokeWidth,
storkeStyle: window.ExcalidrawAutomate.style.storkeStyle,
roughness: window.ExcalidrawAutomate.style.roughness,
opacity: window.ExcalidrawAutomate.style.opacity,
strokeSharpness: window.ExcalidrawAutomate.style.strokeSharpness,
angle: ea.style.angle,
strokeColor: ea.style.strokeColor,
backgroundColor: ea.style.backgroundColor,
fillStyle: ea.style.fillStyle,
strokeWidth: ea.style.strokeWidth,
storkeStyle: ea.style.storkeStyle,
roughness: ea.style.roughness,
opacity: ea.style.opacity,
strokeSharpness: ea.style.strokeSharpness,
seed: Math.floor(Math.random() * 100000),
version: 1,
versionNounce: 1,
@@ -816,19 +835,19 @@ export function measureText (newText:string, fontSize:number, fontFamily:number)
return {w: width, h: height, baseline: baseline };
};
async function getTemplate(fileWithPath:string, loadFiles:boolean = false):Promise<{
async function getTemplate(plugin: ExcalidrawPlugin, fileWithPath:string, loadFiles:boolean = false):Promise<{
elements: any,
appState: any,
frontmatter: string,
files: any,
svgSnapshot: string
}> {
const app = window.ExcalidrawAutomate.plugin.app;
const app = plugin.app;
const vault = app.vault;
const file = app.metadataCache.getFirstLinkpathDest(normalizePath(fileWithPath),'');
if(file && file instanceof TFile) {
const data = (await vault.read(file)).replaceAll("\r\n","\n").replaceAll("\r","\n");
let excalidrawData:ExcalidrawData = new ExcalidrawData(window.ExcalidrawAutomate.plugin);
let excalidrawData:ExcalidrawData = new ExcalidrawData(plugin);
if(file.extension === "excalidraw") {
await excalidrawData.loadLegacyData(data,file);
@@ -848,7 +867,7 @@ async function getTemplate(fileWithPath:string, loadFiles:boolean = false):Promi
if(trimLocation == -1) trimLocation = data.search("# Drawing\n");
if(loadFiles) {
await loadSceneFiles(app,excalidrawData.files,(fileArray:any)=>{
await loadSceneFiles(plugin,excalidrawData.files, excalidrawData.equations, (fileArray:any)=>{
for(const f of fileArray) {
excalidrawData.scene.files[f.id] = f;
}

View File

@@ -114,12 +114,14 @@ export class ExcalidrawData {
private plugin: ExcalidrawPlugin;
public loaded: boolean = false;
public files:Map<FileId,string> = null; //fileId, path
public equations:Map<FileId,string> = null; //fileId, path
private compatibilityMode:boolean = false;
constructor(plugin: ExcalidrawPlugin) {
this.plugin = plugin;
this.app = plugin.app;
this.files = new Map<FileId,string>();
this.equations = new Map<FileId,string>();
}
/**
@@ -132,6 +134,7 @@ export class ExcalidrawData {
this.file = file;
this.textElements = new Map<string,{raw:string, parsed:string}>();
this.files.clear();
this.equations.clear();
this.compatibilityMode = false;
//I am storing these because if the settings change while a drawing is open parsing will run into errors during save
@@ -207,14 +210,21 @@ export class ExcalidrawData {
}
data = data.substring(data.indexOf("# Embedded files\n")+"# Embedded files\n".length);
//Load Embedded files
const REG_FILEID_FILEPATH = /([\w\d]*):\s*\[\[([^\]]*)]]\n/gm;
data = data.substring(data.indexOf("# Embedded files\n")+"# Embedded files\n".length);
res = data.matchAll(REG_FILEID_FILEPATH);
while(!(parts = res.next()).done) {
this.files.set(parts.value[1] as FileId,parts.value[2]);
}
//Load Equations
const REG_FILEID_EQUATION = /([\w\d]*):\s*\$\$(.*)(\$\$\s*\n)$/gm;
res = data.matchAll(REG_FILEID_EQUATION);
while(!(parts = res.next()).done) {
this.equations.set(parts.value[1] as FileId,parts.value[2]);
}
//Check to see if there are text elements in the JSON that were missed from the # Text Elements section
//e.g. if the entire text elements section was deleted.
this.findNewTextElementsInScene();
@@ -238,6 +248,7 @@ export class ExcalidrawData {
this.scene.appState.theme = isObsidianThemeDark() ? "dark" : "light";
}
this.files.clear();
this.equations.clear();
this.findNewTextElementsInScene();
await this.setTextMode(TextMode.raw,true); //legacy files are always displayed in raw mode.
return true;
@@ -503,13 +514,20 @@ export class ExcalidrawData {
for(const key of this.textElements.keys()){
outString += this.textElements.get(key).raw+' ^'+key+'\n\n';
}
outString += (this.equations.size>0 || this.files.size>0) ? '\n# Embedded files\n' : '';
if(this.equations.size>0) {
for(const key of this.equations.keys()) {
outString += key +': $$'+this.equations.get(key) + '$$\n';
}
}
if(this.files.size>0) {
outString += '\n# Embedded files\n';
for(const key of this.files.keys()) {
outString += key +': [['+this.files.get(key) + ']]\n';
}
outString += '\n';
}
outString += (this.equations.size>0 || this.files.size>0) ? '\n' : '';
const sceneJSONstring = JSON.stringify(this.scene,null,"\t");
return outString + getMarkdownDrawingSection(sceneJSONstring,this.svgSnapshot);
@@ -531,7 +549,7 @@ export class ExcalidrawData {
if(!scene.files || scene.files == {}) return false;
for(const key of Object.keys(scene.files)) {
if(!this.files.has(key as FileId)) {
if(!(this.files.has(key as FileId) || this.equations.has(key as FileId))) {
dirty = true;
let fname = "Pasted Image "+window.moment().format("YYYYMMDDHHmmss_SSS");
switch(scene.files[key].mimeType) {

View File

@@ -38,8 +38,6 @@ import { checkAndCreateFolder, download, embedFontsInSVG, generateSVGString, get
import { Prompt } from "./Prompt";
import { ClipboardData } from "@zsviczian/excalidraw/types/clipboard";
declare let window: ExcalidrawAutomate;
export enum TextMode {
parsed,
raw
@@ -444,7 +442,7 @@ export default class ExcalidrawView extends TextFileView {
if((this.app.workspace.activeLeaf === this.leaf) && this.excalidrawWrapperRef) {
this.excalidrawWrapperRef.current.focus();
}
loadSceneFiles(this.app,this.excalidrawData.files,(files:any)=>this.addFiles(files));
loadSceneFiles(this.plugin,this.excalidrawData.files, this.excalidrawData.equations, (files:any)=>this.addFiles(files));
} else {
this.instantiateExcalidraw({
elements: excalidrawData.elements,
@@ -640,7 +638,7 @@ export default class ExcalidrawView extends TextFileView {
React.useEffect(() => {
excalidrawRef.current.readyPromise.then((api) => {
this.excalidrawAPI = api;
loadSceneFiles(this.app,this.excalidrawData.files,(files:any)=>this.addFiles(files));
loadSceneFiles(this.plugin,this.excalidrawData.files,this.excalidrawData.equations, (files:any)=>this.addFiles(files));
});
}, [excalidrawRef]);
@@ -719,14 +717,15 @@ export default class ExcalidrawView extends TextFileView {
}
const el: ExcalidrawElement[] = this.excalidrawAPI.getSceneElements();
const st: AppState = this.excalidrawAPI.getAppState();
window.ExcalidrawAutomate.reset();
window.ExcalidrawAutomate.style.strokeColor = st.currentItemStrokeColor;
window.ExcalidrawAutomate.style.opacity = st.currentItemOpacity;
window.ExcalidrawAutomate.style.fontFamily = fontFamily ? fontFamily: st.currentItemFontFamily;
window.ExcalidrawAutomate.style.fontSize = st.currentItemFontSize;
window.ExcalidrawAutomate.style.textAlign = st.currentItemTextAlign;
const id:string = window.ExcalidrawAutomate.addText(currentPosition.x, currentPosition.y, text);
this.addElements(window.ExcalidrawAutomate.getElements(),false,true);
const ea = this.plugin.ea;
ea.reset();
ea.style.strokeColor = st.currentItemStrokeColor;
ea.style.opacity = st.currentItemOpacity;
ea.style.fontFamily = fontFamily ? fontFamily: st.currentItemFontFamily;
ea.style.fontSize = st.currentItemFontSize;
ea.style.textAlign = st.currentItemTextAlign;
const id:string = ea.addText(currentPosition.x, currentPosition.y, text);
this.addElements(ea.getElements(),false,true);
}
this.addElements = async (newElements:ExcalidrawElement[],repositionToCursor:boolean = false, save:boolean=false, images:any):Promise<boolean> => {
@@ -759,7 +758,12 @@ export default class ExcalidrawView extends TextFileView {
dataURL: images[k].dataURL,
created: images[k].created
});
this.excalidrawData.files.set(images[k].id,images[k].file);
if(images[k].file) {
this.excalidrawData.files.set(images[k].id,images[k].file);
}
if(images[k].tex) {
this.excalidrawData.equations.set(images[k].id,images[k].tex);
}
});
this.excalidrawAPI.addFiles(files);
}
@@ -1070,11 +1074,11 @@ export default class ExcalidrawView extends TextFileView {
const draggable = (this.app as any).dragManager.draggable;
const onDropHook = (type:"file"|"text"|"unknown", files:TFile[], text:string):boolean => {
if (window.ExcalidrawAutomate.onDropHook) {
if (this.plugin.ea.onDropHook) {
try {
return window.ExcalidrawAutomate.onDropHook({
return this.plugin.ea.onDropHook({
//@ts-ignore
ea: window.ExcalidrawAutomate, //the Excalidraw Automate object
ea: this.plugin.ea, //the Excalidraw Automate object
event: event, //React.DragEvent<HTMLDivElement>
draggable: draggable, //Obsidian draggable object
type: type, //"file"|"text"
@@ -1106,7 +1110,7 @@ export default class ExcalidrawView extends TextFileView {
const f = draggable.file;
const topX = currentPosition.x;
const topY = currentPosition.y;
const ea = window.ExcalidrawAutomate;
const ea = this.plugin.ea;
ea.reset();
ea.setView(this);
(async () => {

View File

@@ -7,16 +7,18 @@ import { IMAGE_TYPES } from "./constants";
import { ExcalidrawAutomate } from "./ExcalidrawAutomate";
import ExcalidrawView from "./ExcalidrawView";
import {t} from './lang/helpers'
import ExcalidrawPlugin from "./main";
declare let window: ExcalidrawAutomate;
export class InsertImageDialog extends FuzzySuggestModal<TFile> {
public app: App;
public plugin: ExcalidrawPlugin;
private view: ExcalidrawView;
constructor(app: App) {
super(app);
this.app = app;
constructor(plugin: ExcalidrawPlugin) {
super(plugin.app);
this.plugin = plugin;
this.app = plugin.app;
this.limit = 20;
this.setInstructions([{
command: t("SELECT_FILE"),
@@ -37,7 +39,7 @@ export class InsertImageDialog extends FuzzySuggestModal<TFile> {
onChooseItem(item: TFile, _evt: MouseEvent | KeyboardEvent): void {
const ea = window.ExcalidrawAutomate;
const ea = this.plugin.ea;
ea.reset();
ea.setView(this.view);
(async () => {

View File

@@ -2,14 +2,13 @@ import Excalidraw,{exportToSvg} from "@zsviczian/excalidraw";
import { App, normalizePath, TAbstractFile, TFile, TFolder, Vault, WorkspaceLeaf } from "obsidian";
import { Random } from "roughjs/bin/math";
import { BinaryFileData, DataURL, Zoom } from "@zsviczian/excalidraw/types/types";
import { nanoid } from "nanoid";
import { CASCADIA_FONT, IMAGE_TYPES, VIRGIL_FONT } from "./constants";
import {ExcalidrawAutomate} from './ExcalidrawAutomate';
import { CASCADIA_FONT, fileid, IMAGE_TYPES, VIRGIL_FONT } from "./constants";
import ExcalidrawPlugin from "./main";
import { ExcalidrawElement, FileId } from "@zsviczian/excalidraw/types/element/types";
import { ExportSettings } from "./ExcalidrawView";
import { ExcalidrawSettings } from "./settings";
import { html_beautify } from "js-beautify";
import html2canvas from "html2canvas";
declare module "obsidian" {
interface Workspace {
@@ -20,7 +19,7 @@ declare module "obsidian" {
}
}
declare let window: ExcalidrawAutomate;
declare let window: any;
export declare type MimeType = "image/svg+xml" | "image/png" | "image/jpeg" | "image/gif" | "application/octet-stream";
@@ -190,7 +189,7 @@ export const getNewOrAdjacentLeaf = (plugin: ExcalidrawPlugin, leaf: WorkspaceLe
return plugin.app.workspace.createLeafBySplit(leaf);
}
export const getObsidianImage = async (app: App, file: TFile)
export const getObsidianImage = async (plugin: ExcalidrawPlugin, file: TFile)
:Promise<{
mimeType: MimeType,
fileId: FileId,
@@ -198,14 +197,15 @@ export const getObsidianImage = async (app: App, file: TFile)
created: number,
size: {height: number, width: number},
}> => {
if(!app || !file) return null;
const isExcalidrawFile = window.ExcalidrawAutomate.isExcalidrawFile(file);
if(!plugin || !file) return null;
const app = plugin.app;
const isExcalidrawFile = plugin.ea.isExcalidrawFile(file);
if (!(IMAGE_TYPES.contains(file.extension) || isExcalidrawFile)) {
return null;
}
const ab = await app.vault.readBinary(file);
const excalidrawSVG = isExcalidrawFile
? svgToBase64((await window.ExcalidrawAutomate.createSVG(file.path,true)).outerHTML) as DataURL
? svgToBase64((await plugin.ea.createSVG(file.path,true)).outerHTML) as DataURL
: null;
let mimeType:MimeType = "image/svg+xml";
if (!isExcalidrawFile) {
@@ -263,7 +263,7 @@ const generateIdFromFile = async (file: ArrayBuffer):Promise<FileId> => {
.join("") as FileId;
} catch (error) {
console.error(error);
id = nanoid(40) as FileId;
id = fileid() as FileId;
}
return id;
};
@@ -363,14 +363,15 @@ export const embedFontsInSVG = (svg:SVGSVGElement):SVGSVGElement => {
}
export const loadSceneFiles = async (app:App, filesMap: Map<FileId, string>,addFiles:Function) => {
const entries = filesMap.entries();
export const loadSceneFiles = async (plugin:ExcalidrawPlugin, filesMap: Map<FileId, string>, equationsMap: Map<FileId, string>, addFiles:Function) => {
const app = plugin.app;
let entries = filesMap.entries();
let entry;
let files:BinaryFileData[] = [];
while(!(entry = entries.next()).done) {
const file = app.vault.getAbstractFileByPath(entry.value[1]);
if(file && file instanceof TFile) {
const data = await getObsidianImage(app,file);
const data = await getObsidianImage(plugin,file);
files.push({
mimeType : data.mimeType,
id: entry.value[0],
@@ -382,6 +383,24 @@ export const loadSceneFiles = async (app:App, filesMap: Map<FileId, string>,addF
}
}
entries = equationsMap.entries();
while(!(entry = entries.next()).done) {
const tex = entry.value[1];
const data = await tex2dataURL(tex);
if(data) {
files.push({
mimeType : data.mimeType,
id: entry.value[0],
dataURL: data.dataURL,
created: data.created,
//@ts-ignore
size: data.size,
});
}
}
try { //in try block because by the time files are loaded the user may have closed the view
addFiles(files);
} catch(e) {
@@ -414,4 +433,28 @@ export const scaleLoadedImage = (scene:any, files:any):[boolean,any] => {
}
}
export const isObsidianThemeDark = () => document.body.classList.contains("theme-dark");
export const isObsidianThemeDark = () => document.body.classList.contains("theme-dark");
export async function tex2dataURL(tex:string, color:string="black"):Promise<{
mimeType: MimeType,
fileId: FileId,
dataURL: DataURL,
created: number,
size: {height: number, width: number},
}> {
const div = document.body.createDiv();
div.style.display = "table"; //this will ensure div fits width of formula exactly
//@ts-ignore
const eq = window.MathJax.tex2chtml("\\sum_{a}^{b}\\frac{x}{2}",{display: true, scale: 4}); //scale to ensure good resolution
eq.style.margin = "3px";
eq.style.color = color;
div.appendChild(eq);
const canvas = await html2canvas(div, {backgroundColor:null}); //transparent
return {
mimeType: "image/png",
fileId: fileid() as FileId,
dataURL: canvas.toDataURL() as DataURL,
created: Date.now(),
size: {height: canvas.height, width: canvas.width}
}
}

View File

@@ -1,8 +1,10 @@
//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 { FileId } from "@zsviczian/excalidraw/types/element/types";
import {customAlphabet} from "nanoid";
export const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',8);
export const fileid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',40);
export const IMAGE_TYPES = ['jpeg', 'jpg', 'png', 'gif', 'svg'];
export const MAX_IMAGE_SIZE = 500;
export const FRONTMATTER_KEY = "excalidraw-plugin";

View File

@@ -12,9 +12,9 @@ import {
MenuItem,
TAbstractFile,
Tasks,
MarkdownRenderer,
ViewState,
Notice,
loadMathJax,
} from "obsidian";
import {
BLANK_DRAWING,
@@ -53,7 +53,8 @@ import {
} from "./InsertImageDialog";
import {
initExcalidrawAutomate,
destroyExcalidrawAutomate
destroyExcalidrawAutomate,
ExcalidrawAutomate
} from "./ExcalidrawAutomate";
import { Prompt } from "./Prompt";
import { around } from "monkey-around";
@@ -83,6 +84,7 @@ export default class ExcalidrawPlugin extends Plugin {
private observer: MutationObserver;
private fileExplorerObserver: MutationObserver;
public opencount:number = 0;
public ea:ExcalidrawAutomate;
constructor(app: App, manifest: PluginManifest) {
super(app, manifest);
@@ -96,8 +98,8 @@ export default class ExcalidrawPlugin extends Plugin {
await this.loadSettings();
this.addSettingTab(new ExcalidrawSettingTab(this.app, this));
await initExcalidrawAutomate(this);
this.ea = await initExcalidrawAutomate(this);
this.registerView(
VIEW_TYPE_EXCALIDRAW,
(leaf: WorkspaceLeaf) => new ExcalidrawView(leaf, this)
@@ -128,6 +130,8 @@ export default class ExcalidrawPlugin extends Plugin {
patches.imageElementLaunchNotice();
this.switchToExcalidarwAfterLoad()
this.app.workspace.onLayoutReady(()=>loadMathJax());
}
private switchToExcalidarwAfterLoad() {
@@ -415,7 +419,7 @@ export default class ExcalidrawPlugin extends Plugin {
private registerCommands() {
this.openDialog = new OpenFileDialog(this.app, this);
this.insertLinkDialog = new InsertLinkDialog(this.app);
this.insertImageDialog = new InsertImageDialog(this.app);
this.insertImageDialog = new InsertImageDialog(this);
this.addRibbonIcon(ICON_NAME, t("CREATE_NEW"), async (e) => {
this.createDrawing(this.getNextDefaultFilename(), e.ctrlKey||e.metaKey);
@@ -688,10 +692,11 @@ export default class ExcalidrawPlugin extends Plugin {
const prompt = new Prompt(this.app, t("ENTER_LATEX"),'','$\\theta$');
prompt.openAndGetValue( async (formula:string)=> {
if(!formula) return;
const el = createEl('p');
await MarkdownRenderer.renderMarkdown(formula,el,'',this)
view.addText(el.getText());
el.empty();
const ea = this.ea;
ea.reset();
await ea.addLaTex(0,0,formula);
ea.setView(view);
ea.addElementsToView(true,true);
});
return true;
}

View File

@@ -2275,6 +2275,16 @@
"mixin-deep" "^1.2.0"
"pascalcase" "^0.1.1"
"base64-arraybuffer@^0.2.0":
"integrity" "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ=="
"resolved" "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz"
"version" "0.2.0"
"base64-arraybuffer@^1.0.1":
"integrity" "sha512-vFIUq7FdLtjZMhATwDul5RZWv2jpXQ09Pd6jcVEOvIsqCWTRFD/ONHNfyOS8dA/Ippi5dsIgpyKWKZaAKZltbA=="
"resolved" "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.1.tgz"
"version" "1.0.1"
"base64-js@^1.0.2":
"integrity" "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
"resolved" "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz"
@@ -3278,6 +3288,13 @@
"resolved" "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz"
"version" "0.0.4"
"css-line-break@2.0.1":
"integrity" "sha512-gwKYIMUn7xodIcb346wgUhE2Dt5O1Kmrc16PWi8sL4FTfyDj8P5095rzH7+O8CTZudJr+uw2GCI/hwEkDJFI2w=="
"resolved" "https://registry.npmjs.org/css-line-break/-/css-line-break-2.0.1.tgz"
"version" "2.0.1"
dependencies:
"base64-arraybuffer" "^0.2.0"
"css-loader@0.28.7":
"integrity" "sha512-GxMpax8a/VgcfRrVy0gXD6yLd5ePYbXX/5zGgTVYp4wXtJklS8Z2VaUArJgc//f6/Dzil7BaJObdSv8eKKCPgg=="
"resolved" "https://registry.npmjs.org/css-loader/-/css-loader-0.28.7.tgz"
@@ -5079,6 +5096,14 @@
"pretty-error" "^2.0.2"
"toposort" "^1.0.0"
"html2canvas@^1.3.2":
"integrity" "sha512-4+zqv87/a1LsaCrINV69wVLGG8GBZcYBboz1JPWEgiXcWoD9kroLzccsBRU/L9UlfV2MAZ+3J92U9IQPVMDeSQ=="
"resolved" "https://registry.npmjs.org/html2canvas/-/html2canvas-1.3.2.tgz"
"version" "1.3.2"
dependencies:
"css-line-break" "2.0.1"
"text-segmentation" "^1.0.2"
"htmlparser2@^6.1.0":
"integrity" "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A=="
"resolved" "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz"
@@ -9315,6 +9340,13 @@
"read-pkg-up" "^1.0.1"
"require-main-filename" "^1.0.1"
"text-segmentation@^1.0.2":
"integrity" "sha512-uTqvLxdBrVnx/CFQOtnf8tfzSXFm+1Qxau7Xi54j4OPTZokuDOX8qncQzrg2G8ZicAMOM8TgzFAYTb+AqNO4Cw=="
"resolved" "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.2.tgz"
"version" "1.0.2"
dependencies:
"utrie" "^1.0.1"
"text-table@~0.2.0", "text-table@0.2.0":
"integrity" "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ="
"resolved" "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
@@ -9735,6 +9767,13 @@
"resolved" "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz"
"version" "1.0.1"
"utrie@^1.0.1":
"integrity" "sha512-JPaDXF3vzgZxfeEwutdGzlrNoVFL5UvZcbO6Qo9D4GoahrieUPoMU8GCpVpR7MQqcKhmShIh8VlbEN3PLM3EBg=="
"resolved" "https://registry.npmjs.org/utrie/-/utrie-1.0.1.tgz"
"version" "1.0.1"
dependencies:
"base64-arraybuffer" "^1.0.1"
"uuid@^3.0.1", "uuid@^3.3.2":
"integrity" "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
"resolved" "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz"