Compare commits

...

5 Commits

Author SHA1 Message Date
Zsolt Viczian
08528d9a88 1.4.8-beta-2 2021-11-14 23:13:06 +01:00
Zsolt Viczian
3e9ef99226 Reworked recursive image loading 2021-11-14 20:12:11 +01:00
Zsolt Viczian
f50ecd95c3 removed SVG snapshot 2021-11-14 12:08:19 +01:00
Zsolt Viczian
8b477a0e16 Added missing scale to createPNG interface 2021-11-14 09:41:53 +01:00
Zsolt Viczian
78891a1065 1.4.8-beta 2021-11-10 23:05:13 +01:00
11 changed files with 414 additions and 425 deletions

View File

@@ -32,7 +32,6 @@
"@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",
"rollup": "^2.52.3",

165
src/EmbeddedFileLoader.ts Normal file
View File

@@ -0,0 +1,165 @@
import { FileId } from "@zsviczian/excalidraw/types/element/types";
import { BinaryFileData, DataURL } from "@zsviczian/excalidraw/types/types";
import { App, Notice, TFile } from "obsidian";
import { fileid, IMAGE_TYPES } from "./constants";
import { ExcalidrawData } from "./ExcalidrawData";
import ExcalidrawView, { ExportSettings } from "./ExcalidrawView";
import { tex2dataURL } from "./LaTeX";
import ExcalidrawPlugin from "./main";
import {getImageSize, svgToBase64 } from "./Utils";
export declare type MimeType = "image/svg+xml" | "image/png" | "image/jpeg" | "image/gif" | "application/octet-stream";
export class EmbeddedFilesLoader {
private plugin:ExcalidrawPlugin;
private processedFiles: Set<string> = new Set<string>();
constructor(plugin: ExcalidrawPlugin) {
this.plugin = plugin;
}
public async getObsidianImage (file: TFile)
:Promise<{
mimeType: MimeType,
fileId: FileId,
dataURL: DataURL,
created: number,
size: {height: number, width: number},
}> {
if(!this.plugin || !file) return null;
//to block infinite loop of recursive loading of images
if((file.extension==="md" || file.extension === "excalidraw") && this.processedFiles.has(file.path)) {
new Notice("Stopped loading infinite image embed loop at repeated instance of " + file.path,6000);
return null;
}
this.processedFiles.add(file.path);
const app = this.plugin.app;
const isExcalidrawFile = this.plugin.ea.isExcalidrawFile(file);
if (!(IMAGE_TYPES.contains(file.extension) || isExcalidrawFile)) {
return null;
}
const ab = await app.vault.readBinary(file);
const getExcalidrawSVG = async () => {
const exportSettings:ExportSettings = {
withBackground: false,
withTheme: false
};
this.plugin.ea.reset();
const svg = await this.plugin.ea.createSVG(file.path,true,exportSettings,this);
const dURL = svgToBase64(svg.outerHTML) as DataURL;
return dURL as DataURL;
}
const excalidrawSVG = isExcalidrawFile
? await getExcalidrawSVG()
: null;
let mimeType:MimeType = "image/svg+xml";
if (!isExcalidrawFile) {
switch (file.extension) {
case "png": mimeType = "image/png";break;
case "jpeg":mimeType = "image/jpeg";break;
case "jpg": mimeType = "image/jpeg";break;
case "gif": mimeType = "image/gif";break;
case "svg": mimeType = "image/svg+xml";break;
default: mimeType = "application/octet-stream";
}
}
const dataURL = excalidrawSVG ?? (file.extension==="svg" ? await getSVGData(app,file) : await getDataURL(ab,mimeType));
const size = await getImageSize(excalidrawSVG??app.vault.getResourcePath(file));
return {
mimeType: mimeType,
fileId: await generateIdFromFile(ab),
dataURL: dataURL,
created: file.stat.mtime,
size: size
}
}
public async loadSceneFiles (
excalidrawData: ExcalidrawData,
view: ExcalidrawView,
addFiles:Function,
sourcePath:string
) {
const app = this.plugin.app;
let entries = excalidrawData.getFileEntries();
let entry;
let files:BinaryFileData[] = [];
while(!(entry = entries.next()).done) {
const file = app.metadataCache.getFirstLinkpathDest(entry.value[1],sourcePath);
if(file && file instanceof TFile) {
const data = await this.getObsidianImage(file);
if(data) {
files.push({
mimeType : data.mimeType,
id: entry.value[0],
dataURL: data.dataURL,
created: data.created,
//@ts-ignore
size: data.size,
});
}
}
}
entries = excalidrawData.getEquationEntries();
while(!(entry = entries.next()).done) {
const tex = entry.value[1];
const data = await tex2dataURL(tex, this.plugin);
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,view);
} catch(e) {
}
}
}
const getSVGData = async (app: App, file: TFile): Promise<DataURL> => {
const svg = await app.vault.read(file);
return svgToBase64(svg) as DataURL;
}
const getDataURL = async (file: ArrayBuffer,mimeType: string): Promise<DataURL> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const dataURL = reader.result as DataURL;
resolve(dataURL);
};
reader.onerror = (error) => reject(error);
reader.readAsDataURL(new Blob([new Uint8Array(file)],{type:'mimeType'}));
});
};
const generateIdFromFile = async (file: ArrayBuffer):Promise<FileId> => {
let id: FileId;
try {
const hashBuffer = await window.crypto.subtle.digest(
"SHA-1",
file,
);
id =
// convert buffer to byte array
Array.from(new Uint8Array(hashBuffer))
// convert to hex string
.map((byte) => byte.toString(16).padStart(2, "0"))
.join("") as FileId;
} catch (error) {
console.error(error);
id = fileid() as FileId;
}
return id;
};

View File

@@ -9,7 +9,7 @@ import {
normalizePath,
TFile
} from "obsidian"
import ExcalidrawView, { TextMode } from "./ExcalidrawView";
import ExcalidrawView, { ExportSettings, TextMode } from "./ExcalidrawView";
import { ExcalidrawData} from "./ExcalidrawData";
import {
FRONTMATTER,
@@ -17,8 +17,10 @@ import {
VIEW_TYPE_EXCALIDRAW,
MAX_IMAGE_SIZE,
} from "./constants";
import { embedFontsInSVG, getObsidianImage, getPNG, getSVG, loadSceneFiles, scaleLoadedImage, tex2dataURL, wrapText } from "./Utils";
import { embedFontsInSVG, getPNG, getSVG, scaleLoadedImage, wrapText } from "./Utils";
import { AppState } from "@zsviczian/excalidraw/types/types";
import { EmbeddedFilesLoader } from "./EmbeddedFileLoader";
import { tex2dataURL } from "./LaTeX";
declare type ConnectionPoint = "top"|"bottom"|"left"|"right";
@@ -71,8 +73,8 @@ export interface ExcalidrawAutomate {
}
}
):Promise<string>;
createSVG (templatePath?:string, embedFont?:boolean, withTheme?:boolean, withBackgroundColor?:boolean):Promise<SVGSVGElement>;
createPNG (templatePath?:string):Promise<any>;
createSVG (templatePath?:string, embedFont?:boolean, exportSettings?:ExportSettings, loader?:EmbeddedFilesLoader):Promise<SVGSVGElement>;
createPNG (templatePath?:string, scale?:number, loader?:EmbeddedFilesLoader):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;
@@ -247,7 +249,7 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin):Promise<E
return id;
},
async toClipboard(templatePath?:string) {
const template = templatePath ? (await getTemplate(this.plugin,templatePath)) : null;
const template = templatePath ? (await getTemplate(this.plugin,templatePath, false, new EmbeddedFilesLoader(this.plugin) )) : null;
let elements = template ? template.elements : [];
elements = elements.concat(this.getElements());
navigator.clipboard.writeText(
@@ -281,7 +283,9 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin):Promise<E
}
}
):Promise<string> {
const template = params?.templatePath ? (await getTemplate(this.plugin,params.templatePath,true)) : null;
const template = params?.templatePath
? (await getTemplate(this.plugin,params.templatePath,true, new EmbeddedFilesLoader(this.plugin)))
: null;
let elements = template ? template.elements : [];
elements = elements.concat(this.getElements());
let frontmatter:string;
@@ -336,9 +340,14 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin):Promise<E
: frontmatter + await plugin.exportSceneToMD(JSON.stringify(scene,null,"\t"))
);
},
async createSVG(templatePath?:string,embedFont:boolean = false,withTheme?:boolean, withBackgroundColor?:boolean):Promise<SVGSVGElement> {
async createSVG(
templatePath?:string,
embedFont:boolean = false,
exportSettings?:ExportSettings,
loader:EmbeddedFilesLoader = new EmbeddedFilesLoader(this.plugin)
):Promise<SVGSVGElement> {
const automateElements = this.getElements();
const template = templatePath ? (await getTemplate(this.plugin,templatePath,true)) : null;
const template = templatePath ? (await getTemplate(this.plugin,templatePath,true,loader)) : null;
let elements = template ? template.elements : [];
elements = elements.concat(automateElements);
const svg = await getSVG(
@@ -354,18 +363,22 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin):Promise<E
files: template?.files ?? {}
},
{
withBackground: (withBackgroundColor === undefined) ? plugin.settings.exportWithBackground : withBackgroundColor,
withTheme: (withTheme === undefined) ? plugin.settings.exportWithTheme : withTheme
withBackground: (exportSettings === undefined) ? plugin.settings.exportWithBackground : exportSettings.withBackground,
withTheme: (exportSettings === undefined) ? plugin.settings.exportWithTheme : exportSettings.withTheme
}
)
return embedFont ? embedFontsInSVG(svg) : svg;
},
async createPNG(templatePath?:string, scale:number=1) {
async createPNG(
templatePath?:string,
scale:number=1,
loader:EmbeddedFilesLoader = new EmbeddedFilesLoader(this.plugin)
) {
const automateElements = this.getElements();
const template = templatePath ? (await getTemplate(this.plugin,templatePath,true)) : null;
const template = templatePath ? (await getTemplate(this.plugin,templatePath,true,loader)) : null;
let elements = template ? template.elements : [];
elements = elements.concat(automateElements);
return getPNG(
return await getPNG(
{
type: "excalidraw",
version: 2,
@@ -528,7 +541,8 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin):Promise<E
},
async addImage(topX:number, topY:number, imageFile: TFile):Promise<string> {
const id = nanoid();
const image = await getObsidianImage(this.plugin,imageFile);
const loader = new EmbeddedFilesLoader(this.plugin)
const image = await loader.getObsidianImage(imageFile);
if(!image) return null;
this.imagesDict[image.fileId] = {
mimeType: image.mimeType,
@@ -835,12 +849,16 @@ export function measureText (newText:string, fontSize:number, fontFamily:number)
return {w: width, h: height, baseline: baseline };
};
async function getTemplate(plugin: ExcalidrawPlugin, fileWithPath:string, loadFiles:boolean = false):Promise<{
async function getTemplate(
plugin: ExcalidrawPlugin,
fileWithPath:string,
loadFiles:boolean = false,
loader:EmbeddedFilesLoader
):Promise<{
elements: any,
appState: any,
frontmatter: string,
files: any,
svgSnapshot: string
}> {
const app = plugin.app;
const vault = app.vault;
@@ -857,7 +875,6 @@ async function getTemplate(plugin: ExcalidrawPlugin, fileWithPath:string, loadFi
appState: excalidrawData.scene.appState,
frontmatter: "",
files: excalidrawData.scene.files,
svgSnapshot: null,
};
}
@@ -867,22 +884,23 @@ async function getTemplate(plugin: ExcalidrawPlugin, fileWithPath:string, loadFi
let trimLocation = data.search("# Text Elements\n");
if(trimLocation == -1) trimLocation = data.search("# Drawing\n");
let scene = excalidrawData.scene;
if(loadFiles) {
await loadSceneFiles(plugin,excalidrawData, null, (fileArray:any, view:any)=>{
await loader.loadSceneFiles(excalidrawData, null, (fileArray:any, view:any)=>{
if(!fileArray) return;
for(const f of fileArray) {
excalidrawData.scene.files[f.id] = f;
}
let foo;
[foo,excalidrawData] = scaleLoadedImage(excalidrawData,fileArray);
[foo,scene] = scaleLoadedImage(excalidrawData.scene,fileArray);
},templatePath);
}
return {
elements: excalidrawData.scene.elements,
appState: excalidrawData.scene.appState,
elements: scene.elements,
appState: scene.appState,
frontmatter: data.substring(0,trimLocation),
files: excalidrawData.scene.files,
svgSnapshot: excalidrawData.svgSnapshot
files: scene.files,
};
};
return {
@@ -890,7 +908,6 @@ async function getTemplate(plugin: ExcalidrawPlugin, fileWithPath:string, loadFi
appState: {},
frontmatter: null,
files: [],
svgSnapshot: null,
}
}

View File

@@ -1,4 +1,4 @@
import { App, normalizePath, TFile } from "obsidian";
import { App, TFile } from "obsidian";
import {
nanoid,
FRONTMATTER_KEY_CUSTOM_PREFIX,
@@ -11,8 +11,8 @@ import {
JSON_parse
} from "./constants";
import { TextMode } from "./ExcalidrawView";
import { getAttachmentsFolderAndFilePath, getBinaryFileFromDataURL, isObsidianThemeDark, wrapText } from "./Utils";
import { ExcalidrawImageElement, ExcalidrawTextElement, FileId } from "@zsviczian/excalidraw/types/element/types";
import { getAttachmentsFolderAndFilePath, getBinaryFileFromDataURL, getIMGFilename, isObsidianThemeDark, wrapText } from "./Utils";
import { ExcalidrawImageElement, FileId } from "@zsviczian/excalidraw/types/element/types";
import { BinaryFiles, SceneData } from "@zsviczian/excalidraw/types/types";
type SceneDataWithFiles = SceneData & { files: BinaryFiles};
@@ -54,7 +54,7 @@ export const REGEX_LINK = {
export const REG_LINKINDEX_HYPERLINK = /^\w+:\/\//;
const DRAWING_REG = /\n%%\n# Drawing\n[^`]*(```json\n)([\s\S]*?)```/gm; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/182
const DRAWING_REG = /\n# Drawing\n[^`]*(```json\n)([\s\S]*?)```/gm; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/182
const DRAWING_REG_FALLBACK = /\n# Drawing\n(```json\n)?(.*)(```)?(%%)?/gm;
export function getJSON(data:string):[string,number] {
let res = data.matchAll(DRAWING_REG);
@@ -73,36 +73,14 @@ export function getJSON(data:string):[string,number] {
return [data,parts.value ? parts.value.index : 0];
}
//extracts SVG snapshot from Excalidraw Markdown string
const SVG_REG = /.*?```html\n([\s\S]*?)```/gm;
export function getSVGString(data:string):string {
let res = data.matchAll(SVG_REG);
let parts;
parts = res.next();
if(parts.value && parts.value.length>1) {
return parts.value[1].replaceAll("\n","");
}
return null;
}
export function getMarkdownDrawingSection(jsonString: string,svgString: string) {
export function getMarkdownDrawingSection(jsonString: string) {
return '%%\n# Drawing\n'
+ String.fromCharCode(96)+String.fromCharCode(96)+String.fromCharCode(96)+'json\n'
+ jsonString + '\n'
+ String.fromCharCode(96)+String.fromCharCode(96)+String.fromCharCode(96)
+ (svgString ? //&& this.settings.saveSVGSnapshots
'\n\n# SVG snapshot\n'
+ "==⚠ Remove all linebreaks from SVG string before use. Linebreaks were added to improve markdown view speed. ⚠==\n"
+ String.fromCharCode(96)+String.fromCharCode(96)+String.fromCharCode(96)+'html\n'
+ svgString + '\n'
+ String.fromCharCode(96)+String.fromCharCode(96)+String.fromCharCode(96)
: '')
+ '\n%%';
+ String.fromCharCode(96)+String.fromCharCode(96)+String.fromCharCode(96)+'\n%%';
}
export class ExcalidrawData {
public svgSnapshot: string = null;
private textElements:Map<string,{raw:string, parsed:string}> = null;
public scene:any = null;
private file:TFile = null;
@@ -175,8 +153,6 @@ export class ExcalidrawData {
this.scene.appState.theme = isObsidianThemeDark() ? "dark" : "light";
}
this.svgSnapshot = getSVGString(data.substr(pos+scene.length));
data = data.substring(0,pos);
//The Markdown # Text Elements take priority over the JSON text elements. Assuming the scenario in which the link was updated due to filename changes
@@ -530,7 +506,7 @@ export class ExcalidrawData {
const sceneJSONstring = JSON.stringify(this.scene,null,"\t");
return outString + getMarkdownDrawingSection(sceneJSONstring,this.svgSnapshot);
return outString + getMarkdownDrawingSection(sceneJSONstring);
}
private async syncFiles(scene:SceneDataWithFiles):Promise<boolean> {

View File

@@ -31,12 +31,14 @@ import {
IMAGE_TYPES
} from './constants';
import ExcalidrawPlugin from './main';
import {ExcalidrawAutomate, repositionElementsToCursor} from './ExcalidrawAutomate';
import { repositionElementsToCursor} from './ExcalidrawAutomate';
import { t } from "./lang/helpers";
import { ExcalidrawData, REG_LINKINDEX_HYPERLINK, REGEX_LINK } from "./ExcalidrawData";
import { checkAndCreateFolder, download, embedFontsInSVG, generateSVGString, getNewOrAdjacentLeaf, getNewUniqueFilepath, getPNG, getSVG, loadSceneFiles, rotatedDimensions, scaleLoadedImage, splitFolderAndFilename, svgToBase64, updateEquation, viewportCoordsToSceneCoords } from "./Utils";
import { checkAndCreateFolder, download, embedFontsInSVG, getIMGFilename, getNewOrAdjacentLeaf, getNewUniqueFilepath, getPNG, getSVG, rotatedDimensions, scaleLoadedImage, splitFolderAndFilename, svgToBase64, viewportCoordsToSceneCoords } from "./Utils";
import { Prompt } from "./Prompt";
import { ClipboardData } from "@zsviczian/excalidraw/types/clipboard";
import { updateEquation } from "./LaTeX";
import { EmbeddedFilesLoader } from "./EmbeddedFileLoader";
export enum TextMode {
parsed,
@@ -120,7 +122,7 @@ export default class ExcalidrawView extends TextFileView {
if (!this.getScene) return false;
scene = this.getScene();
}
const filepath = this.file.path.substring(0,this.file.path.lastIndexOf(this.compatibilityMode ? '.excalidraw':'.md')) + '.svg';
const filepath = getIMGFilename(this.file.path,'svg'); //.substring(0,this.file.path.lastIndexOf(this.compatibilityMode ? '.excalidraw':'.md')) + '.svg';
const file = this.app.vault.getAbstractFileByPath(normalizePath(filepath));
(async () => {
const exportSettings: ExportSettings = {
@@ -142,7 +144,7 @@ export default class ExcalidrawView extends TextFileView {
scene = this.getScene();
}
const filepath = this.file.path.substring(0,this.file.path.lastIndexOf(this.compatibilityMode ? '.excalidraw':'.md')) + '.png';
const filepath = getIMGFilename(this.file.path,'png'); //this.file.path.substring(0,this.file.path.lastIndexOf(this.compatibilityMode ? '.excalidraw':'.md')) + '.png';
const file = this.app.vault.getAbstractFileByPath(normalizePath(filepath));
(async () => {
@@ -169,8 +171,6 @@ export default class ExcalidrawView extends TextFileView {
if(await this.excalidrawData.syncElements(scene) && !this.autosaving) {
await this.loadDrawing(false);
}
//generate SVG preview snapshot
this.excalidrawData.svgSnapshot = await generateSVGString(this.getScene(),this.plugin.settings);
}
await super.save();
}
@@ -194,8 +194,27 @@ export default class ExcalidrawView extends TextFileView {
if(this.plugin.settings.autoexportExcalidraw) this.saveExcalidraw(scene);
}
const header = this.data.substring(0,trimLocation)
let header = this.data.substring(0,trimLocation)
.replace(/excalidraw-plugin:\s.*\n/,FRONTMATTER_KEY+": " + ( (this.textMode == TextMode.raw) ? "raw\n" : "parsed\n"));
if (header.search(/cssclass:[\s]*excalidraw-hide-preview-text/) === -1) {
header = header.replace(/(excalidraw-plugin:\s.*\n)/,"$1cssclass: excalidraw-hide-preview-text\n");
}
const ext = this.plugin.settings.autoexportSVG
? "svg"
: ( this.plugin.settings.autoexportPNG
? "png"
: null);
if(ext) {
const REG_IMG = /(^---[\w\W]*?---\n)(!\[\[.*?]]\n(%%\n)?)/m; //(%%\n)? because of 1.4.8-beta... to be backward compatible with anyone who installed that version
if(header.match(REG_IMG)) {
header = header.replace(REG_IMG,"$1![["+getIMGFilename(this.file.path,ext)+"]]\n");
} else {
header = header.replace(/(^---[\w\W]*?---\n)/m, "$1![["+getIMGFilename(this.file.path,ext)+"]]\n");
}
}
return header + this.excalidrawData.generateMD();
}
if(this.compatibilityMode) {
@@ -450,7 +469,7 @@ export default class ExcalidrawView extends TextFileView {
*
* @param justloaded - a flag to trigger zoom to fit after the drawing has been loaded
*/
private async loadDrawing(justloaded:boolean) {
private async loadDrawing(justloaded:boolean) {
const excalidrawData = this.excalidrawData.scene;
this.justLoaded = justloaded;
if(this.excalidrawRef) {
@@ -469,8 +488,8 @@ export default class ExcalidrawView extends TextFileView {
if((this.app.workspace.activeLeaf === this.leaf) && this.excalidrawWrapperRef) {
this.excalidrawWrapperRef.current.focus();
}
loadSceneFiles(
this.plugin,
const loader = new EmbeddedFilesLoader(this.plugin);
loader.loadSceneFiles(
this.excalidrawData,
this,
(files:any, view:ExcalidrawView) => addFiles(files,view),
@@ -656,8 +675,8 @@ export default class ExcalidrawView extends TextFileView {
React.useEffect(() => {
excalidrawRef.current.readyPromise.then((api) => {
this.excalidrawAPI = api;
loadSceneFiles(
this.plugin,
const loader = new EmbeddedFilesLoader(this.plugin);
loader.loadSceneFiles(
this.excalidrawData,
this,
(files:any, view:ExcalidrawView)=>addFiles(files,view),

103
src/LaTeX.ts Normal file
View File

@@ -0,0 +1,103 @@
import { BinaryFileData, DataURL } from "@zsviczian/excalidraw/types/types";
import ExcalidrawView from "./ExcalidrawView";
import ExcalidrawPlugin from "./main";
import {MimeType} from "./EmbeddedFileLoader";
import { FileId } from "@zsviczian/excalidraw/types/element/types";
import { getImageSize, svgToBase64 } from "./Utils";
import { fileid } from "./constants";
import html2canvas from "html2canvas";
declare let window: any;
export const updateEquation = async (
equation: string,
fileId: string,
view: ExcalidrawView,
addFiles:Function,
plugin: ExcalidrawPlugin
) => {
const data = await tex2dataURL(equation, plugin);
if(data) {
let files:BinaryFileData[] = [];
files.push({
mimeType : data.mimeType,
id: fileId as FileId,
dataURL: data.dataURL,
created: data.created,
//@ts-ignore
size: data.size,
});
addFiles(files,view);
}
}
export async function tex2dataURL(tex:string, plugin:ExcalidrawPlugin):Promise<{
mimeType: MimeType,
fileId: FileId,
dataURL: DataURL,
created: number,
size: {height: number, width: number},
}> {
//if network is slow, or not available, or mathjax has not yet fully loaded
try {
return await mathjaxSVG(tex, plugin);
} catch(e) {
//fallback
return await mathjaxImage2html(tex);
}
}
async function mathjaxSVG (tex:string, plugin:ExcalidrawPlugin):Promise<{
mimeType: MimeType,
fileId: FileId,
dataURL: DataURL,
created: number,
size: {height: number, width: number},
}> {
const eq = plugin.mathjax.tex2svg(tex,{display: true, scale: 4});
const svg = eq.querySelector("svg");
if(svg) {
const dataURL = svgToBase64(svg.outerHTML);
return {
mimeType: "image/svg+xml",
fileId: fileid() as FileId,
dataURL: dataURL as DataURL,
created: Date.now(),
size: await getImageSize(dataURL)
}
}
return null;
}
async function mathjaxImage2html(tex:string):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(tex,{display: true, scale: 4}); //scale to ensure good resolution
eq.style.margin = "3px";
eq.style.color = "black";
//ipad support - removing mml as that was causing phantom double-image blur.
const el = eq.querySelector("mjx-assistive-mml");
if(el) {
el.parentElement.removeChild(el);
}
div.appendChild(eq);
window.MathJax.typeset();
const canvas = await html2canvas(div, {backgroundColor:null}); //transparent
document.body.removeChild(div);
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,15 +1,12 @@
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 { CASCADIA_FONT, fileid, IMAGE_TYPES, VIRGIL_FONT } from "./constants";
import { Zoom } from "@zsviczian/excalidraw/types/types";
import { CASCADIA_FONT, VIRGIL_FONT } from "./constants";
import ExcalidrawPlugin from "./main";
import { ExcalidrawElement, FileId } from "@zsviczian/excalidraw/types/element/types";
import ExcalidrawView, { ExportSettings } from "./ExcalidrawView";
import { ExcalidrawSettings } from "./settings";
import { html_beautify } from "js-beautify";
import html2canvas from "html2canvas";
import { ExcalidrawData } from "./ExcalidrawData";
import { ExportSettings } from "./ExcalidrawView";
declare module "obsidian" {
interface Workspace {
@@ -20,16 +17,11 @@ declare module "obsidian" {
}
}
declare let window: any;
export declare type MimeType = "image/svg+xml" | "image/png" | "image/jpeg" | "image/gif" | "application/octet-stream";
/**
* Splits a full path including a folderpath and a filename into separate folderpath and filename components
* @param filepath
*/
export function splitFolderAndFilename(filepath: string):{folderpath: string, filename: string} {
let folderpath: string, filename:string;
const lastIndex = filepath.lastIndexOf("/");
return {
folderpath: normalizePath(filepath.substr(0,lastIndex)),
@@ -155,7 +147,6 @@ export const rotatedDimensions = (
];
}
export const viewportCoordsToSceneCoords = (
{ clientX, clientY }: { clientX: number; clientY: number },
{
@@ -190,99 +181,9 @@ export const getNewOrAdjacentLeaf = (plugin: ExcalidrawPlugin, leaf: WorkspaceLe
return plugin.app.workspace.createLeafBySplit(leaf);
}
export const getObsidianImage = async (plugin: ExcalidrawPlugin, file: TFile)
:Promise<{
mimeType: MimeType,
fileId: FileId,
dataURL: DataURL,
created: number,
size: {height: number, width: number},
}> => {
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 getExcalidrawSVG = async () => {
plugin.ea.reset();
return svgToBase64((await plugin.ea.createSVG(file.path,true,false,false)).outerHTML) as DataURL;
}
const excalidrawSVG = isExcalidrawFile
? await getExcalidrawSVG()
: null;
let mimeType:MimeType = "image/svg+xml";
if (!isExcalidrawFile) {
switch (file.extension) {
case "png": mimeType = "image/png";break;
case "jpeg":mimeType = "image/jpeg";break;
case "jpg": mimeType = "image/jpeg";break;
case "gif": mimeType = "image/gif";break;
case "svg": mimeType = "image/svg+xml";break;
default: mimeType = "application/octet-stream";
}
}
return {
mimeType: mimeType,
fileId: await generateIdFromFile(ab),
dataURL: excalidrawSVG ?? (file.extension==="svg" ? await getSVGData(app,file) : await getDataURL(ab)),
created: file.stat.mtime,
size: await getImageSize(excalidrawSVG??app.vault.getResourcePath(file))
}
}
const getSVGData = async (app: App, file: TFile): Promise<DataURL> => {
const svg = await app.vault.read(file);
return svgToBase64(svg) as DataURL;
}
export const svgToBase64 = (svg:string):string => {
return "data:image/svg+xml;base64,"+btoa(unescape(encodeURIComponent(svg.replaceAll("&nbsp;"," "))));
}
const getDataURL = async (file: ArrayBuffer): Promise<DataURL> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const dataURL = reader.result as DataURL;
resolve(dataURL);
};
reader.onerror = (error) => reject(error);
reader.readAsDataURL(new Blob([new Uint8Array(file)]));
});
};
const generateIdFromFile = async (file: ArrayBuffer):Promise<FileId> => {
let id: FileId;
try {
const hashBuffer = await window.crypto.subtle.digest(
"SHA-1",
file,
);
id =
// convert buffer to byte array
Array.from(new Uint8Array(hashBuffer))
// convert to hex string
.map((byte) => byte.toString(16).padStart(2, "0"))
.join("") as FileId;
} catch (error) {
console.error(error);
id = fileid() as FileId;
}
return id;
};
const getImageSize = async (src:string):Promise<{height:number, width:number}> => {
return new Promise((resolve, reject) => {
let img = new Image()
img.onload = () => resolve({height: img.height, width:img.width});
img.onerror = reject;
img.src = src;
})
}
export const getBinaryFileFromDataURL = (dataURL:string):ArrayBuffer => {
if(!dataURL) return null;
@@ -313,7 +214,7 @@ export const getAttachmentsFolderAndFilePath = async (app:App, activeViewFilePat
export const getSVG = async (scene:any, exportSettings:ExportSettings):Promise<SVGSVGElement> => {
try {
return exportToSvg({
return await exportToSvg({
elements: scene.elements,
appState: {
exportBackground: exportSettings.withBackground,
@@ -327,18 +228,6 @@ export const getSVG = async (scene:any, exportSettings:ExportSettings):Promise<S
}
}
export const generateSVGString = async (scene:any, settings: ExcalidrawSettings):Promise<string> => {
const exportSettings: ExportSettings = {
withBackground: settings.exportWithBackground,
withTheme: settings.exportWithTheme
}
const svg = await getSVG(scene,exportSettings);
if(svg) {
return wrapText(html_beautify(svg.outerHTML,{"indent_with_tabs": true}),4096,true);
}
return null;
}
export const getPNG = async (scene:any, exportSettings:ExportSettings, scale:number = 1) => {
try {
return await Excalidraw.exportToBlob({
@@ -369,76 +258,13 @@ export const embedFontsInSVG = (svg:SVGSVGElement):SVGSVGElement => {
return svg;
}
export const loadSceneFiles = async (
plugin:ExcalidrawPlugin,
excalidrawData: ExcalidrawData,
view: ExcalidrawView,
addFiles:Function,
sourcePath:string
) => {
const app = plugin.app;
let entries = excalidrawData.getFileEntries();
let entry;
let files:BinaryFileData[] = [];
while(!(entry = entries.next()).done) {
const file = app.metadataCache.getFirstLinkpathDest(entry.value[1],sourcePath);
if(file && file instanceof TFile) {
const data = await getObsidianImage(plugin,file);
files.push({
mimeType : data.mimeType,
id: entry.value[0],
dataURL: data.dataURL,
created: data.created,
//@ts-ignore
size: data.size,
});
}
}
entries = excalidrawData.getEquationEntries();
while(!(entry = entries.next()).done) {
const tex = entry.value[1];
const data = await tex2dataURL(tex, plugin);
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,view);
} catch(e) {
}
}
export const updateEquation = async (
equation: string,
fileId: string,
view: ExcalidrawView,
addFiles:Function,
plugin: ExcalidrawPlugin
) => {
const data = await tex2dataURL(equation, plugin);
if(data) {
let files:BinaryFileData[] = [];
files.push({
mimeType : data.mimeType,
id: fileId as FileId,
dataURL: data.dataURL,
created: data.created,
//@ts-ignore
size: data.size,
});
addFiles(files,view);
}
export const getImageSize = async (src:string):Promise<{height:number, width:number}> => {
return new Promise((resolve, reject) => {
let img = new Image()
img.onload = () => resolve({height: img.height, width:img.width});
img.onerror = reject;
img.src = src;
})
}
export const scaleLoadedImage = (scene:any, files:any):[boolean,any] => {
@@ -468,75 +294,9 @@ export const scaleLoadedImage = (scene:any, files:any):[boolean,any] => {
export const isObsidianThemeDark = () => document.body.classList.contains("theme-dark");
export async function tex2dataURL(tex:string, plugin:ExcalidrawPlugin):Promise<{
mimeType: MimeType,
fileId: FileId,
dataURL: DataURL,
created: number,
size: {height: number, width: number},
}> {
//if network is slow, or not available, or mathjax has not yet fully loaded
try {
return await mathjaxSVG(tex, plugin);
} catch(e) {
//fallback
return await mathjaxImage2html(tex);
}
export function getIMGFilename(path:string,extension:string):string {
return path.substring(0,path.lastIndexOf('.')) + '.' + extension;
}
async function mathjaxSVG (tex:string, plugin:ExcalidrawPlugin):Promise<{
mimeType: MimeType,
fileId: FileId,
dataURL: DataURL,
created: number,
size: {height: number, width: number},
}> {
const eq = plugin.mathjax.tex2svg(tex,{display: true, scale: 4});
const svg = eq.querySelector("svg");
if(svg) {
const dataURL = svgToBase64(svg.outerHTML);
return {
mimeType: "image/svg+xml",
fileId: fileid() as FileId,
dataURL: dataURL as DataURL,
created: Date.now(),
size: await getImageSize(dataURL)
}
}
return null;
}
async function mathjaxImage2html(tex:string):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(tex,{display: true, scale: 4}); //scale to ensure good resolution
eq.style.margin = "3px";
eq.style.color = "black";
//ipad support - removing mml as that was causing phantom double-image blur.
const el = eq.querySelector("mjx-assistive-mml");
if(el) {
el.parentElement.removeChild(el);
}
div.appendChild(eq);
window.MathJax.typeset();
const canvas = await html2canvas(div, {backgroundColor:null}); //transparent
document.body.removeChild(div);
return {
mimeType: "image/png",
fileId: fileid() as FileId,
dataURL: canvas.toDataURL() as DataURL,
created: Date.now(),
size: {height: canvas.height, width: canvas.width}
}
}
//export const debug = console.log.bind(window.console);
//export const debug = function(){};

View File

@@ -36,7 +36,7 @@ import {
DARK_BLANK_DRAWING
} from "./constants";
import ExcalidrawView, {ExportSettings, TextMode} from "./ExcalidrawView";
import {getJSON, getMarkdownDrawingSection, getSVGString} from "./ExcalidrawData";
import {getJSON, getMarkdownDrawingSection} from "./ExcalidrawData";
import {
ExcalidrawSettings,
DEFAULT_SETTINGS,
@@ -60,7 +60,7 @@ import {
import { Prompt } from "./Prompt";
import { around } from "monkey-around";
import { t } from "./lang/helpers";
import { checkAndCreateFolder, download, embedFontsInSVG, generateSVGString, getAttachmentsFolderAndFilePath, getIMGPathFromExcalidrawFile, getNewUniqueFilepath, getPNG, getSVG, isObsidianThemeDark, splitFolderAndFilename, svgToBase64 } from "./Utils";
import { checkAndCreateFolder, download, embedFontsInSVG, getAttachmentsFolderAndFilePath, getIMGFilename, getIMGPathFromExcalidrawFile, getNewUniqueFilepath, getPNG, getSVG, isObsidianThemeDark, splitFolderAndFilename, svgToBase64 } from "./Utils";
import { OneOffs } from "./OneOffs";
import { FileId } from "@zsviczian/excalidraw/types/element/types";
import { MATHJAX_DATAURL } from "./mathjax";
@@ -227,32 +227,28 @@ export default class ExcalidrawPlugin extends Plugin {
img.addClass(imgAttributes.style);
const [scene,pos] = getJSON(content);
const svgSnapshot = getSVGString(content.substr(pos+scene.length));
this.ea.reset();
//Removed in 1.4.0 when implementing ImageElement. Key reason for removing this
//is to use SVG snapshot in file, to avoid resource intensive process to generating PNG
//due to the need to load excalidraw plus all linked images
/* if(!this.settings.displaySVGInPreview) {
if(!this.settings.displaySVGInPreview) {
const width = parseInt(imgAttributes.fwidth);
let scale = 1;
if(width>=800) scale = 2;
if(width>=1600) scale = 3;
if(width>=2400) scale = 4;
const png = await getPNG(JSON_parse(scene),exportSettings, scale);
const png = await this.ea.createPNG(file.path,scale);
//const png = await getPNG(JSON_parse(scene),exportSettings, scale);
if(!png) return null;
img.src = URL.createObjectURL(png);
return img;
}*/
}
const svgSnapshot = (await this.ea.createSVG(file.path,true,exportSettings)).outerHTML;
let svg:SVGSVGElement = null;
if(svgSnapshot) {
const el = document.createElement('div');
el.innerHTML = svgSnapshot;
const firstChild = el.firstChild;
if(firstChild instanceof SVGSVGElement) {
svg=firstChild;
}
} else {
svg = await getSVG(JSON_parse(scene),exportSettings);
const el = document.createElement('div');
el.innerHTML = svgSnapshot;
const firstChild = el.firstChild;
if(firstChild instanceof SVGSVGElement) {
svg=firstChild;
}
if(!svg) return null;
svg = embedFontsInSVG(svg);
@@ -347,9 +343,17 @@ export default class ExcalidrawPlugin extends Plugin {
if(m.length == 0) return;
if(!this.hover.linkText) return;
const file = this.app.metadataCache.getFirstLinkpathDest(this.hover.linkText, this.hover.sourcePath?this.hover.sourcePath:"");
if(!file || !(file instanceof TFile) || !this.isExcalidrawFile(file)) return
if(!file || !(file instanceof TFile) || !this.isExcalidrawFile(file)) return;
if(file.extension == "excalidraw") {
const svgFileName = getIMGFilename(file.path,"svg");
const svgFile = this.app.vault.getAbstractFileByPath(svgFileName);
if(svgFile && svgFile instanceof TFile) return; //If auto export SVG or PNG is enabled it will be inserted at the top of the excalidraw file. No need to manually insert hover preview
const pngFileName = getIMGFilename(file.path,"png");
const pngFile = this.app.vault.getAbstractFileByPath(pngFileName);
if(pngFile && pngFile instanceof TFile) return; //If auto export SVG or PNG is enabled it will be inserted at the top of the excalidraw file. No need to manually insert hover preview
if(file.extension === "excalidraw") {
observerForLegacyFileFormat(m,file);
return;
}
@@ -1139,7 +1143,7 @@ export default class ExcalidrawPlugin extends Plugin {
return this.settings.matchTheme && isObsidianThemeDark() ? DARK_BLANK_DRAWING : BLANK_DRAWING;
}
const blank = this.settings.matchTheme && isObsidianThemeDark() ? DARK_BLANK_DRAWING : BLANK_DRAWING;
return FRONTMATTER + '\n' + getMarkdownDrawingSection(blank,'<SVG></SVG>');
return FRONTMATTER + '\n' + getMarkdownDrawingSection(blank);
}
/**
@@ -1150,7 +1154,6 @@ export default class ExcalidrawPlugin extends Plugin {
public async exportSceneToMD(data:string): Promise<string> {
if(!data) return "";
const excalidrawData = JSON_parse(data);
const svgString = await generateSVGString(excalidrawData,this.settings);
const textElements = excalidrawData.elements?.filter((el:any)=> el.type=="text")
let outString = '# Text Elements\n';
let id:string;
@@ -1165,7 +1168,7 @@ export default class ExcalidrawPlugin extends Plugin {
}
outString += te.text+' ^'+id+'\n\n';
}
return outString + getMarkdownDrawingSection(JSON.stringify(JSON_parse(data),null,"\t"),svgString);
return outString + getMarkdownDrawingSection(JSON.stringify(JSON_parse(data),null,"\t"));
}
public async createDrawing(filename: string, onNewPane: boolean, foldername?: string, initData?:string):Promise<string> {

View File

@@ -14,8 +14,7 @@ export interface ExcalidrawSettings {
templateFilePath: string,
drawingFilenamePrefix: string,
drawingFilenameDateTime: string,
// saveSVGSnapshots: boolean,
//displaySVGInPreview: boolean,
displaySVGInPreview: boolean,
width: string,
matchTheme: boolean,
matchThemeAlways: boolean,
@@ -53,8 +52,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
templateFilePath: 'Excalidraw/Template.excalidraw',
drawingFilenamePrefix: 'Drawing ',
drawingFilenameDateTime: 'YYYY-MM-DD HH.mm.ss',
// saveSVGSnapshots: true,
//displaySVGInPreview: true,
displaySVGInPreview: true,
width: '400',
matchTheme: false,
matchThemeAlways: false,
@@ -199,18 +197,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
text.setValue(this.plugin.settings.drawingFilenameDateTime);
filenameEl.innerHTML = getFilenameSample();
this.applySettingsUpdate();
}));
/* new Setting(containerEl)
.setName(t("SVG_IN_MD_NAME"))
.setDesc(t("SVG_IN_MD_DESC"))
.addToggle(toggle => toggle
.setValue(this.plugin.settings.saveSVGSnapshots)
.onChange(async (value) => {
this.plugin.settings.saveSVGSnapshots = value;
this.applySettingsUpdate();
}));*/
}));
this.containerEl.createEl('h1', {text: t("DISPLAY_HEAD")});
@@ -367,8 +354,8 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
this.containerEl.createEl('h1', {text: t("EMBED_HEAD")});
//Removed in 1.4.0 when implementing ImageElement.
/* new Setting(containerEl)
new Setting(containerEl)
.setName(t("EMBED_PREVIEW_SVG_NAME"))
.setDesc(t("EMBED_PREVIEW_SVG_DESC"))
.addToggle(toggle => toggle
@@ -376,7 +363,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
.onChange(async (value) => {
this.plugin.settings.displaySVGInPreview = value;
this.applySettingsUpdate();
}));*/
}));
new Setting(containerEl)
.setName(t("EMBED_WIDTH_NAME"))

View File

@@ -95,3 +95,19 @@ li[data-testid] {
margin-bottom: 20px;
}
/*hide text elements in markdown preview*/
.markdown-preview-view.excalidraw-hide-preview-text {
font-size: 0px;
}
.markdown-preview-view mark,
.markdown-preview-view.excalidraw-hide-preview-text span:not(.image-embed),
.markdown-preview-view.excalidraw-hide-preview-text h1,
.markdown-preview-view.excalidraw-hide-preview-text h2,
.markdown-preview-view.excalidraw-hide-preview-text h3,
.markdown-preview-view.excalidraw-hide-preview-text h4,
.markdown-preview-view.excalidraw-hide-preview-text h5 {
display: none;
}

View File

@@ -1077,11 +1077,6 @@
"resolved" "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz"
"version" "1.0.4"
"abbrev@1":
"integrity" "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
"resolved" "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz"
"version" "1.1.1"
"accepts@~1.3.4", "accepts@~1.3.5", "accepts@~1.3.7":
"integrity" "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA=="
"resolved" "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz"
@@ -3003,7 +2998,7 @@
dependencies:
"delayed-stream" "~1.0.0"
"commander@^2.11.0", "commander@^2.19.0":
"commander@^2.11.0":
"integrity" "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
"resolved" "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz"
"version" "2.20.3"
@@ -3063,14 +3058,6 @@
"readable-stream" "^2.2.2"
"typedarray" "^0.0.6"
"config-chain@^1.1.12":
"integrity" "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ=="
"resolved" "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz"
"version" "1.1.13"
dependencies:
"ini" "^1.3.4"
"proto-list" "~1.2.1"
"configstore@^3.0.0":
"integrity" "sha512-nlOhI4+fdzoK5xmJ+NY+1gZK56bwEaWZr8fYuXohZ9Vkc1o3a4T/R3M+yE/w7x/ZVJ1zF8c+oaOvF0dztdUgmA=="
"resolved" "https://registry.npmjs.org/configstore/-/configstore-3.1.5.tgz"
@@ -3790,16 +3777,6 @@
"jsbn" "~0.1.0"
"safer-buffer" "^2.1.0"
"editorconfig@^0.15.3":
"integrity" "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g=="
"resolved" "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz"
"version" "0.15.3"
dependencies:
"commander" "^2.19.0"
"lru-cache" "^4.1.5"
"semver" "^5.6.0"
"sigmund" "^1.0.1"
"ee-first@1.1.1":
"integrity" "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
"resolved" "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz"
@@ -6089,17 +6066,6 @@
"resolved" "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz"
"version" "2.6.4"
"js-beautify@1.13.3":
"integrity" "sha512-mi4/bWIsWFqE2/Yr8cr7EtHbbGKCBkUgPotkyTFphpsRUuyRG8gxBqH9QbonJTV8Gw8RtjPquoYFxuWEjz2HLg=="
"resolved" "https://registry.npmjs.org/js-beautify/-/js-beautify-1.13.3.tgz"
"version" "1.13.3"
dependencies:
"config-chain" "^1.1.12"
"editorconfig" "^0.15.3"
"glob" "^7.1.3"
"mkdirp" "^1.0.4"
"nopt" "^5.0.0"
"js-tokens@^3.0.0 || ^4.0.0", "js-tokens@^4.0.0":
"integrity" "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
"resolved" "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
@@ -6488,7 +6454,7 @@
"resolved" "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz"
"version" "1.0.1"
"lru-cache@^4.0.1", "lru-cache@^4.1.5":
"lru-cache@^4.0.1":
"integrity" "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g=="
"resolved" "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz"
"version" "4.1.5"
@@ -6716,11 +6682,6 @@
dependencies:
"minimist" "^1.2.5"
"mkdirp@^1.0.4":
"integrity" "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
"resolved" "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz"
"version" "1.0.4"
"moment@2.29.1":
"integrity" "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
"resolved" "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz"
@@ -6873,13 +6834,6 @@
"resolved" "https://registry.npmjs.org/node-releases/-/node-releases-1.1.77.tgz"
"version" "1.1.77"
"nopt@^5.0.0":
"integrity" "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ=="
"resolved" "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz"
"version" "5.0.0"
dependencies:
"abbrev" "1"
"normalize-package-data@^2.3.2", "normalize-package-data@^2.3.4":
"integrity" "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA=="
"resolved" "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz"
@@ -7886,11 +7840,6 @@
"object-assign" "^4.1.1"
"react-is" "^16.8.1"
"proto-list@~1.2.1":
"integrity" "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk="
"resolved" "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz"
"version" "1.2.4"
"proxy-addr@~2.0.5":
"integrity" "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="
"resolved" "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz"
@@ -8657,7 +8606,7 @@
dependencies:
"semver" "^5.0.3"
"semver@^5.0.3", "semver@^5.1.0", "semver@^5.3.0", "semver@^5.5.0", "semver@^5.6.0", "semver@2 || 3 || 4 || 5":
"semver@^5.0.3", "semver@^5.1.0", "semver@^5.3.0", "semver@^5.5.0", "semver@2 || 3 || 4 || 5":
"integrity" "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
"resolved" "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz"
"version" "5.7.1"
@@ -8815,11 +8764,6 @@
"get-intrinsic" "^1.0.2"
"object-inspect" "^1.9.0"
"sigmund@^1.0.1":
"integrity" "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA="
"resolved" "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz"
"version" "1.0.1"
"signal-exit@^3.0.0", "signal-exit@^3.0.2":
"integrity" "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
"resolved" "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz"