mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
5 Commits
1.4.7
...
1.4.8-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08528d9a88 | ||
|
|
3e9ef99226 | ||
|
|
f50ecd95c3 | ||
|
|
8b477a0e16 | ||
|
|
78891a1065 |
@@ -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
165
src/EmbeddedFileLoader.ts
Normal 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;
|
||||
};
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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
103
src/LaTeX.ts
Normal 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}
|
||||
}
|
||||
}
|
||||
272
src/Utils.ts
272
src/Utils.ts
@@ -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(" "," "))));
|
||||
}
|
||||
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(){};
|
||||
49
src/main.ts
49
src/main.ts
@@ -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> {
|
||||
|
||||
@@ -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"))
|
||||
|
||||
16
styles.css
16
styles.css
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
62
yarn.lock
62
yarn.lock
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user