mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
latex WIP
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
69
src/Utils.ts
69
src/Utils.ts
@@ -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}
|
||||
}
|
||||
}
|
||||
@@ -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("[","["));}
|
||||
|
||||
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";
|
||||
|
||||
23
src/main.ts
23
src/main.ts
@@ -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;
|
||||
}
|
||||
|
||||
39
yarn.lock
39
yarn.lock
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user