debugging file update race condition

This commit is contained in:
Zsolt Viczian
2021-11-27 18:29:56 +01:00
parent f7bbe2e446
commit 73616e5084
12 changed files with 589 additions and 200 deletions

View File

@@ -1,13 +1,16 @@
import { FileId } from "@zsviczian/excalidraw/types/element/types";
import { BinaryFileData, DataURL } from "@zsviczian/excalidraw/types/types";
import { link } from "fs";
import { App, MarkdownRenderer, Notice, TFile } from "obsidian";
import { CASCADIA_FONT, fileid, FRONTMATTER_KEY_FONT, IMAGE_TYPES, VIRGIL_FONT } from "./constants";
import { ExcalidrawData } from "./ExcalidrawData";
import { CASCADIA_FONT, fileid, FRONTMATTER_KEY_CSS, FRONTMATTER_KEY_FONT, FRONTMATTER_KEY_FONTCOLOR, IMAGE_TYPES, nanoid, VIRGIL_FONT } from "./constants";
import { createSVG } from "./ExcalidrawAutomate";
import { ExcalidrawData, getTransclusion } from "./ExcalidrawData";
import ExcalidrawView, { ExportSettings } from "./ExcalidrawView";
import { t } from "./lang/helpers";
import de from "./lang/locale/de";
import { tex2dataURL } from "./LaTeX";
import ExcalidrawPlugin from "./main";
import {errorlog, getImageSize, svgToBase64 } from "./Utils";
import {debug, errorlog, getImageSize, getLinkParts, LinkParts, svgToBase64 } from "./Utils";
export declare type MimeType = "image/svg+xml" | "image/png" | "image/jpeg" | "image/gif" | "application/octet-stream";
export type FileData = BinaryFileData & {
@@ -29,10 +32,24 @@ export class EmbeddedFile {
private plugin: ExcalidrawPlugin;
public mimeType: MimeType="application/octet-stream";
public size: Size ={height:0,width:0};
public linkParts: LinkParts;
constructor(plugin: ExcalidrawPlugin, hostPath: string, imgPath:string) {
this.file = plugin.app.metadataCache.getFirstLinkpathDest(imgPath,hostPath);
this.plugin = plugin;
this.resetImage(hostPath,imgPath);
}
public resetImage(hostPath: string, imgPath:string) {
this.imgInverted = this.img = "";
this.mtime = 0;
this.linkParts = getLinkParts(imgPath);
if(!this.linkParts.path) {
new Notice("Excalidraw Error\nIncorrect embedded filename: "+imgPath);
return;
}
if(!this.linkParts.width) this.linkParts.width = this.plugin.settings.mdSVGwidth;
if(!this.linkParts.height) this.linkParts.height = this.plugin.settings.mdSVGmaxHeight;
this.file = this.plugin.app.metadataCache.getFirstLinkpathDest(this.linkParts.path,hostPath);
}
private fileChanged():boolean {
@@ -40,6 +57,7 @@ export class EmbeddedFile {
}
setImage(imgBase64:string,mimeType:MimeType,size:Size,isDark:boolean,isSVGwithBitmap:boolean) {
if(!this.file) return;
if(this.fileChanged()) this.imgInverted = this.img = "";
this.mtime = this.file.stat.mtime;
this.size = size;
@@ -49,10 +67,10 @@ export class EmbeddedFile {
case false: this.img = imgBase64; break;
}
this.isSVGwithBitmap = isSVGwithBitmap;
if(isSVGwithBitmap) this.loadImg(!isDark);
}
async loadImg(isDark:boolean) {
if(!this.file) return;
const img = isDark ? this.imgInverted : this.img;
if(img!=="") return; //already loaded
const loader = new EmbeddedFilesLoader(this.plugin,isDark);
@@ -66,12 +84,14 @@ export class EmbeddedFile {
}
public isLoaded(isDark:boolean):boolean {
if(!this.file) return true;
if(this.fileChanged()) return false;
if (this.isSVGwithBitmap && isDark) return this.imgInverted !== "";
return this.img !=="";
}
public getImage(isDark:boolean) {
if(!this.file) return "";
if(isDark && this.isSVGwithBitmap) return this.imgInverted;
return this.img; //images that are not SVGwithBitmap, only the light string is stored, since inverted and non-inverted are ===
}
@@ -83,14 +103,16 @@ export class EmbeddedFilesLoader {
private processedFiles: Map<string,number> = new Map<string,number>();
private isDark:boolean;
public terminate=false;
public uid:string;
constructor(plugin: ExcalidrawPlugin, isDark?:boolean) {
this.plugin = plugin;
this.isDark = isDark;
this.uid = nanoid();
}
public async getObsidianImage (
file: TFile
inFile: TFile | EmbeddedFile
):Promise<{
mimeType: MimeType,
fileId: FileId,
@@ -99,7 +121,18 @@ export class EmbeddedFilesLoader {
hasSVGwithBitmap: boolean,
size: {height: number, width: number},
}> {
if(!this.plugin || !file) return null;
if(!this.plugin || !inFile) return null;
const file:TFile = inFile instanceof EmbeddedFile ? inFile.file : inFile;
const linkParts = inFile instanceof EmbeddedFile
? inFile.linkParts
: {
original: file.path,
path: file.path,
isBlockRef: false,
ref: null,
width: this.plugin.settings.mdSVGwidth,
height: this.plugin.settings.mdSVGmaxHeight
}
//to block infinite loop of recursive loading of images
let count=this.processedFiles.has(file.path) ? this.processedFiles.get(file.path):0;
if(file.extension==="md" && count>2) {
@@ -116,12 +149,22 @@ export class EmbeddedFilesLoader {
const ab = await app.vault.readBinary(file);
const getExcalidrawSVG = async (isDark:boolean) => {
debug({where:"EmbeddedFileLoader.getExcalidrawSVG",uid:this.uid,file:file.name});
const exportSettings:ExportSettings = {
withBackground: false,
withTheme: false,
};
this.plugin.ea.reset();
const svg = await this.plugin.ea.createSVG(file.path,true,exportSettings,this,null);
const svg = await createSVG(
file.path,
true,
exportSettings,
this,
null,
null,
null,
[],
this.plugin
);
//https://stackoverflow.com/questions/51154171/remove-css-filter-on-child-elements
const imageList = svg.querySelectorAll("image:not([href^='data:image/svg'])");
if(imageList.length>0) hasSVGwithBitmap = true;
@@ -158,7 +201,7 @@ export class EmbeddedFilesLoader {
?? (file.extension==="svg"
? await getSVGData(app,file)
: (file.extension==="md"
? await convertMarkdownToSVG(this.plugin,file)
? await convertMarkdownToSVG(this.plugin,file,linkParts)
: await getDataURL(ab,mimeType)
));
const size = await getImageSize(excalidrawSVG
@@ -178,11 +221,11 @@ export class EmbeddedFilesLoader {
public async loadSceneFiles (
excalidrawData: ExcalidrawData,
view: ExcalidrawView,
addFiles:Function
) {
const app = this.plugin.app;
const entries = excalidrawData.getFileEntries();
debug({where:"EmbeddedFileLoader.loadSceneFiles",uid:this.uid,isDark:this.isDark,sceneTheme:excalidrawData.scene.appState.theme});
if(this.isDark===undefined) {
this.isDark = excalidrawData.scene.appState.theme==="dark";
}
@@ -192,7 +235,8 @@ export class EmbeddedFilesLoader {
const embeddedFile:EmbeddedFile = entry.value[1];
const updateImage:boolean = !embeddedFile.isLoaded(this.isDark) || embeddedFile.isSVGwithBitmap;
if(!embeddedFile.isLoaded(this.isDark)) {
const data = await this.getObsidianImage(embeddedFile.file);
debug({where:"EmbeddedFileLoader.loadSceneFiles",uid:this.uid,status:"embedded Files are not loaded"});
const data = await this.getObsidianImage(embeddedFile);
if(data) {
files.push({
mimeType : data.mimeType,
@@ -237,8 +281,9 @@ export class EmbeddedFilesLoader {
}
if(this.terminate) return;
debug({where:"EmbeddedFileLoader.loadSceneFiles",uid:this.uid,status:"add Files"});
try { //in try block because by the time files are loaded the user may have closed the view
addFiles(files,view);
addFiles(files);
} catch(e) {
errorlog({where:"EmbeddedFileLoader.loadSceneFiles", error: e});
}
@@ -250,53 +295,74 @@ const getSVGData = async (app: App, file: TFile): Promise<DataURL> => {
return svgToBase64(svg) as DataURL;
}
const mdSVGwidth = 640;
const mdSVGmaxHeight = 800;
const convertMarkdownToSVG = async (plugin: ExcalidrawPlugin, file: TFile): Promise<DataURL> => {
const text = await plugin.app.vault.cachedRead(file);
const convertMarkdownToSVG = async (plugin: ExcalidrawPlugin, file: TFile, linkParts: LinkParts): Promise<DataURL> => {
//const text = await plugin.app.vault.cachedRead(file);
const [text,line] = await getTransclusion(linkParts,plugin.app,file);
const fileCache = plugin.app.metadataCache.getFileCache(file);
let fontName = "Virgil";
let fontBase64 = VIRGIL_FONT;
//get styles
let fontName:string;
let fontDef:string;
let font = plugin.settings.mdFont;
if (fileCache?.frontmatter && fileCache.frontmatter[FRONTMATTER_KEY_FONT]!=null) {
const font = fileCache.frontmatter[FRONTMATTER_KEY_FONT];
switch(font){
case "Virgil": fontName = "Virgil";fontBase64 = VIRGIL_FONT; break;
case "Cascadia": fontName = "Cascadia";fontBase64 = CASCADIA_FONT; break;
default:
const f = plugin.app.metadataCache.getFirstLinkpathDest(font,file.path);
if(f) {
const ab = await plugin.app.vault.readBinary(f);
const mimeType="application/font-woff";
fontName = f.basename;
fontBase64 = ` @font-face {font-family: "${fontName}";src: url("${await getDataURL(ab,mimeType)}") format("${f.extension}");}`;
}
}
font = fileCache.frontmatter[FRONTMATTER_KEY_FONT];
}
switch(font){
case "Virgil": fontName = "Virgil";fontDef = VIRGIL_FONT; break;
case "Cascadia": fontName = "Cascadia";fontDef = CASCADIA_FONT; break;
default:
const f = plugin.app.metadataCache.getFirstLinkpathDest(font,file.path);
if(f) {
const ab = await plugin.app.vault.readBinary(f);
const mimeType=f.extension.startsWith("woff")?"application/font-woff":"font/truetype";
fontName = f.basename;
fontDef = ` @font-face {font-family: "${fontName}";src: url("${await getDataURL(ab,mimeType)}") format("${f.extension==="ttf"?"truetype":f.extension}");}`;
const split = fontDef.split(";base64,",2);
fontDef = split[0]+";charset=utf-8;base64,"+split[1];
} else {
fontName = "Virgil";fontDef = VIRGIL_FONT;
}
}
const fontColor = fileCache?.frontmatter ? fileCache.frontmatter[FRONTMATTER_KEY_FONTCOLOR] : plugin.settings.mdFontColor;
const span = createEl("span");
span.setAttribute("xmlns","http://www.w3.org/1999/xhtml");
span.setAttribute("style","font-family: "+fontName+";");
await MarkdownRenderer.renderMarkdown(text,span,file.path,plugin);
span.querySelectorAll(":scope > *[class^='frontmatter']").forEach((el)=>span.removeChild(el));
const xml = new XMLSerializer().serializeToString(span);
let svgStyle = ' width="'+mdSVGwidth+'px" height="100%"';
let foreignObjectStyle = ' width="'+mdSVGwidth+'px" height="100%"';
let svg= '<svg xmlns="http://www.w3.org/2000/svg"'+svgStyle+'><foreignObject x="0" y="0"'+foreignObjectStyle+'>' +
xml+'</foreignObject><defs><style>'+ fontBase64 +'</style></defs></svg>';
const parser = new DOMParser();
const doc = parser.parseFromString(svg,"image/svg+xml");
const svgEl = doc.firstElementChild;
//construct SVG
const div = createDiv();
div.appendChild(svgEl);
document.body.appendChild(div);
const height = svgEl.firstElementChild.scrollHeight;
const svgHeight = height <= mdSVGmaxHeight ? height : mdSVGmaxHeight;
document.body.removeChild(div);
svgStyle = ' width="'+mdSVGwidth+'px" height="'+svgHeight+'px"';
foreignObjectStyle = ' width="'+mdSVGwidth+'px" height="'+svgHeight+'px"';
svg= '<svg xmlns="http://www.w3.org/2000/svg"'+svgStyle+'><foreignObject x="0" y="0"'+foreignObjectStyle+'>' +
xml+'</foreignObject><defs><style>'+ fontBase64 +'</style></defs></svg>';
return svgToBase64(svg) as DataURL;
div.setAttribute("xmlns","http://www.w3.org/1999/xhtml");
div.style.fontFamily = fontName;
if(fontColor) div.style.color = fontColor;
div.style.fontSize = "initial";
await MarkdownRenderer.renderMarkdown(text,div,file.path,plugin);
div.querySelectorAll(":scope > *[class^='frontmatter']").forEach((el)=>div.removeChild(el));
//brute force to swap <a> to <u> because links anyway don't work when the foreignObject is
//encapsulated in an img element. <a> does not render with an underline, <u> will.
const xml = (new XMLSerializer().serializeToString(div)).replaceAll("<a ","<u ").replaceAll("</a>","</u>");
let svgStyle = ' width="'+linkParts.width+'px" height="100%"';
let foreignObjectStyle = ' width="'+linkParts.width+'px" height="100%"';
const svg = () => '<svg xmlns="http://www.w3.org/2000/svg"'+svgStyle+'>'
+ '<foreignObject x="0" y="0"'+foreignObjectStyle+'>'
+ xml
+ '</foreignObject><defs><style>'
+ fontDef
+ '</style></defs></svg>';
//get SVG size
const parser = new DOMParser();
const doc = parser.parseFromString(svg(),"image/svg+xml");
const svgEl = doc.firstElementChild;
const host = createDiv();
host.appendChild(svgEl);
document.body.appendChild(host);
const height = svgEl.firstElementChild.firstElementChild.scrollHeight;
const svgHeight = height <= linkParts.height ? height : linkParts.height;
document.body.removeChild(host);
//finalize SVG
svgStyle = ' width="'+linkParts.width+'px" height="'+svgHeight+'px"';
foreignObjectStyle = ' width="'+linkParts.width+'px" height="'+svgHeight+'px"';
return svgToBase64(svg()) as DataURL;
}
const getDataURL = async (file: ArrayBuffer,mimeType: string): Promise<DataURL> => {
@@ -307,7 +373,7 @@ const getDataURL = async (file: ArrayBuffer,mimeType: string): Promise<DataURL>
resolve(dataURL);
};
reader.onerror = (error) => reject(error);
reader.readAsDataURL(new Blob([new Uint8Array(file)],{type:'mimeType'}));
reader.readAsDataURL(new Blob([new Uint8Array(file)],{type:mimeType}));
});
};

View File

@@ -19,7 +19,7 @@ import {
VIEW_TYPE_EXCALIDRAW,
MAX_IMAGE_SIZE,
} from "./constants";
import { embedFontsInSVG, getPNG, getSVG, scaleLoadedImage, wrapText } from "./Utils";
import { debug, embedFontsInSVG, getPNG, getSVG, scaleLoadedImage, wrapText } from "./Utils";
import { AppState, DataURL } from "@zsviczian/excalidraw/types/types";
import { EmbeddedFilesLoader, FileData, MimeType } from "./EmbeddedFileLoader";
import { tex2dataURL } from "./LaTeX";
@@ -362,29 +362,17 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin):Promise<E
loader:EmbeddedFilesLoader = new EmbeddedFilesLoader(this.plugin),
theme?:string,
):Promise<SVGSVGElement> {
const automateElements = this.getElements();
const template = templatePath ? (await getTemplate(this.plugin,templatePath,true,loader)) : null;
let elements = template?.elements ?? [];
elements = elements.concat(automateElements);
const svg = await getSVG(
{//createDrawing
type: "excalidraw",
version: 2,
source: "https://excalidraw.com",
elements: elements,
appState: {
theme: theme??(template?.appState?.theme ?? this.canvas.theme),
viewBackgroundColor: template?.appState?.viewBackgroundColor ?? this.canvas.viewBackgroundColor,
},
files: template?.files ?? {}
},
{
withBackground: exportSettings?.withBackground ?? plugin.settings.exportWithBackground,
withTheme: exportSettings?.withTheme ?? plugin.settings.exportWithTheme
}
)
if(template?.hasSVGwithBitmap) svg.setAttribute("hasbitmap","true");
return embedFont ? embedFontsInSVG(svg) : svg;
return await createSVG(
templatePath,
embedFont,
exportSettings,
loader,
theme,
this.canvas.theme,
this.canvas.viewBackgroundColor,
this.getElements(),
this.plugin
);
},
async createPNG(
templatePath?:string,
@@ -393,28 +381,17 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin):Promise<E
loader:EmbeddedFilesLoader = new EmbeddedFilesLoader(this.plugin),
theme?:string
) {
const automateElements = this.getElements();
const template = templatePath ? (await getTemplate(this.plugin,templatePath,true,loader)) : null;
let elements = template?.elements ?? [];
elements = elements.concat(automateElements);
return await getPNG(
{
type: "excalidraw",
version: 2,
source: "https://excalidraw.com",
elements: elements,
appState: {
theme: theme??(template?.appState?.theme ?? this.canvas.theme),
viewBackgroundColor: template?.appState?.viewBackgroundColor ?? this.canvas.viewBackgroundColor,
},
files: template?.files ?? {}
},
{
withBackground: exportSettings?.withBackground ?? plugin.settings.exportWithBackground,
withTheme: exportSettings?.withTheme ?? plugin.settings.exportWithTheme
},
scale
)
return await createPNG(
templatePath,
scale,
exportSettings,
loader,
theme,
this.canvas.theme,
this.canvas.viewBackgroundColor,
this.getElements(),
this.plugin
)
},
wrapText(text:string, lineLen:number):string {
return wrapText(text,lineLen,this.plugin.settings.forceWrap);
@@ -913,7 +890,8 @@ async function getTemplate(
let scene = excalidrawData.scene;
if(loadFiles) {
await loader.loadSceneFiles(excalidrawData, null, (fileArray:FileData[], view:any)=>{
debug({where:"getTemplate",template:file.name,loader:loader.uid});
await loader.loadSceneFiles(excalidrawData, (fileArray:FileData[])=>{
if(!fileArray || fileArray.length===0) return;
for(const f of fileArray) {
if(f.hasSVGwithBitmap) hasSVGwithBitmap = true;
@@ -946,6 +924,76 @@ async function getTemplate(
}
}
export async function createPNG(
templatePath:string = undefined,
scale:number=1,
exportSettings:ExportSettings,
loader:EmbeddedFilesLoader = new EmbeddedFilesLoader(this.plugin),
forceTheme:string = undefined,
canvasTheme: string = undefined,
canvasBackgroundColor: string = undefined,
automateElements: ExcalidrawElement[] = [],
plugin: ExcalidrawPlugin,
) {
const template = templatePath ? (await getTemplate(this.plugin,templatePath,true,loader)) : null;
let elements = template?.elements ?? [];
elements = elements.concat(automateElements);
return await getPNG(
{
type: "excalidraw",
version: 2,
source: "https://excalidraw.com",
elements: elements,
appState: {
theme: forceTheme??(template?.appState?.theme ?? canvasTheme),
viewBackgroundColor: template?.appState?.viewBackgroundColor ?? canvasBackgroundColor,
},
files: template?.files ?? {}
},
{
withBackground: exportSettings?.withBackground ?? plugin.settings.exportWithBackground,
withTheme: exportSettings?.withTheme ?? plugin.settings.exportWithTheme
},
scale
)
}
export async function createSVG(
templatePath:string = undefined,
embedFont:boolean = false,
exportSettings:ExportSettings,
loader:EmbeddedFilesLoader = new EmbeddedFilesLoader(this.plugin),
forceTheme:string = undefined,
canvasTheme: string = undefined,
canvasBackgroundColor: string = undefined,
automateElements: ExcalidrawElement[] = [],
plugin: ExcalidrawPlugin,
):Promise<SVGSVGElement> {
const template = templatePath ? (await getTemplate(plugin,templatePath,true,loader)) : null;
let elements = template?.elements ?? [];
elements = elements.concat(automateElements);
const svg = await getSVG(
{//createDrawing
type: "excalidraw",
version: 2,
source: "https://excalidraw.com",
elements: elements,
appState: {
theme: forceTheme??(template?.appState?.theme ?? canvasTheme),
viewBackgroundColor: template?.appState?.viewBackgroundColor ?? canvasBackgroundColor,
},
files: template?.files ?? {}
},
{
withBackground: exportSettings?.withBackground ?? plugin.settings.exportWithBackground,
withTheme: exportSettings?.withTheme ?? plugin.settings.exportWithTheme
}
)
if(template?.hasSVGwithBitmap) svg.setAttribute("hasbitmap","true");
return embedFont ? embedFontsInSVG(svg) : svg;
}
function estimateLineBound(points:any):[number,number,number,number] {
let minX = Infinity;
let maxX = -Infinity;

View File

@@ -12,7 +12,7 @@ import {
JSON_parse
} from "./constants";
import { TextMode } from "./ExcalidrawView";
import { getAttachmentsFolderAndFilePath, getBinaryFileFromDataURL, getIMGFilename, isObsidianThemeDark, wrapText } from "./Utils";
import { getAttachmentsFolderAndFilePath, getBinaryFileFromDataURL, getIMGFilename, getLinkParts, isObsidianThemeDark, LinkParts, wrapText } from "./Utils";
import { ExcalidrawImageElement, FileId } from "@zsviczian/excalidraw/types/element/types";
import { BinaryFiles, SceneData } from "@zsviczian/excalidraw/types/types";
import { nonWhiteSpace } from "html2canvas/dist/types/css/syntax/parser";
@@ -357,50 +357,10 @@ export class ExcalidrawData {
* @param text
* @returns [string,number] - the transcluded text, and the line number for the location of the text
*/
public async getTransclusion (text:string):Promise<[string,number]> {
//file-name#^blockref
//1 2 3
const REG_FILE_BLOCKREF = /(.*)#(\^)?(.*)/g;
const parts=text.matchAll(REG_FILE_BLOCKREF).next();
if(!parts.done && !parts.value[1]) return [text,0]; //filename not found
const filename = parts.done ? text : parts.value[1];
const file = this.app.metadataCache.getFirstLinkpathDest(filename,this.file.path);
if(!file || !(file instanceof TFile)) return [text,0];
const contents = await this.app.vault.cachedRead(file);
if(parts.done) { //no blockreference
return([contents.substr(0,this.plugin.settings.pageTransclusionCharLimit),0]);
}
const isParagraphRef = parts.value[2] ? true : false; //does the reference contain a ^ character?
const id = parts.value[3]; //the block ID or heading text
const blocks = (await this.app.metadataCache.blockCache.getForFile({isCancelled: ()=>false},file)).blocks.filter((block:any)=>block.node.type!="comment");
if(!blocks) return [text,0];
if(isParagraphRef) {
let para = blocks.filter((block:any)=>block.node.id == id)[0]?.node;
if(!para) return [text,0];
if(["blockquote","listItem"].includes(para.type)) para = para.children[0]; //blockquotes are special, they have one child, which has the paragraph
const startPos = para.position.start.offset;
const lineNum = para.position.start.line;
const endPos = para.children[para.children.length-1]?.position.start.offset-1; //alternative: filter((c:any)=>c.type=="blockid")[0]
return [contents.substr(startPos,endPos-startPos),lineNum]
} else {
const headings = blocks.filter((block:any)=>block.display.startsWith("#"));
let startPos:number = null;
let lineNum:number = 0;
let endPos:number = null;
for(let i=0;i<headings.length;i++) {
if(startPos && !endPos) {
endPos = headings[i].node.position.start.offset-1;
return [contents.substr(startPos,endPos-startPos),lineNum];
}
if(!startPos && headings[i].node.children[0]?.value == id) {
startPos = headings[i].node.children[0]?.position.start.offset; //
lineNum = headings[i].node.children[0]?.position.start.line; //
}
}
if(startPos) return [contents.substr(startPos),lineNum];
return [text,0];
}
public async getTransclusion (link:string):Promise<[string,number]> {
const linkParts = getLinkParts(link);
const file = this.app.metadataCache.getFirstLinkpathDest(linkParts.path,this.file.path);
return await getTransclusion(getLinkParts(link),this.app,file,this.plugin.settings.pageTransclusionCharLimit);
}
/**
@@ -505,7 +465,7 @@ export class ExcalidrawData {
}
if(this.files.size>0) {
for(const key of this.files.keys()) {
outString += key +': [['+this.files.get(key).file.path + ']]\n';
outString += key +': [['+this.files.get(key).linkParts.original + ']]\n';
}
}
outString += (this.equations.size>0 || this.files.size>0) ? '\n' : '';
@@ -744,4 +704,49 @@ export class ExcalidrawData {
}
return false;
}
}
export const getTransclusion = async (linkParts:LinkParts,app:App,file:TFile,charCountLimit?:number):Promise<[string,number]> => {
//file-name#^blockref
//1 2 3
if(!linkParts.path) return [linkParts.original.trim(),0]; //filename not found
if(!file || !(file instanceof TFile)) return [linkParts.original.trim(),0];
const contents = await app.vault.read(file);
if(!linkParts.ref) { //no blockreference
return charCountLimit ? [contents.substr(0,charCountLimit).trim(),0] : [contents.trim(),0];
}
//const isParagraphRef = parts.value[2] ? true : false; //does the reference contain a ^ character?
//const id = parts.value[3]; //the block ID or heading text
const blocks = (await app.metadataCache.blockCache.getForFile({isCancelled: ()=>false},file)).blocks.filter((block:any)=>block.node.type!="comment");
if(!blocks) return [linkParts.original.trim(),0];
if(linkParts.isBlockRef) {
let para = blocks.filter((block:any)=>block.node.id == linkParts.ref)[0]?.node;
if(!para) return [linkParts.original.trim(),0];
if(["blockquote","listItem"].includes(para.type)) para = para.children[0]; //blockquotes are special, they have one child, which has the paragraph
const startPos = para.position.start.offset;
const lineNum = para.position.start.line;
const endPos = para.children[para.children.length-1]?.position.start.offset-1; //alternative: filter((c:any)=>c.type=="blockid")[0]
return [contents.substr(startPos,endPos-startPos).trim(),lineNum]
} else {
const headings = blocks.filter((block:any)=>block.display.search(/^#+\s/)===0);// startsWith("#"));
let startPos:number = null;
let lineNum:number = 0;
let endPos:number = null;
for(let i=0;i<headings.length;i++) {
if(startPos && !endPos) {
endPos = headings[i].node.position.start.offset-1;
return [contents.substr(startPos,endPos-startPos).trim(),lineNum];
}
const c = headings[i].node.children[0];
const cc = c?.children;
if(!startPos && (c?.value === linkParts.ref || (cc?cc[0]?.value===linkParts.ref:false) ) ) {
startPos = headings[i].node.children[0]?.position.start.offset; //
lineNum = headings[i].node.children[0]?.position.start.line; //
}
}
if(startPos) return [contents.substr(startPos).trim(),lineNum];
return [linkParts.original.trim(),0];
}
}

View File

@@ -34,7 +34,7 @@ import ExcalidrawPlugin from './main';
import { repositionElementsToCursor} from './ExcalidrawAutomate';
import { t } from "./lang/helpers";
import { ExcalidrawData, REG_LINKINDEX_HYPERLINK, REGEX_LINK } from "./ExcalidrawData";
import { checkAndCreateFolder, debug, download, embedFontsInSVG, errorlog, getIMGFilename, getNewOrAdjacentLeaf, getNewUniqueFilepath, getPNG, getSVG, isObsidianThemeDark, rotatedDimensions, scaleLoadedImage, splitFolderAndFilename, svgToBase64, viewportCoordsToSceneCoords } from "./Utils";
import { checkAndCreateFolder, debug, download, embedFontsInSVG, errorlog, getIMGFilename, getLinkParts, getNewOrAdjacentLeaf, getNewUniqueFilepath, getPNG, getSVG, isObsidianThemeDark, rotatedDimensions, scaleLoadedImage, splitFolderAndFilename, svgToBase64, viewportCoordsToSceneCoords } from "./Utils";
import { Prompt } from "./Prompt";
import { ClipboardData } from "@zsviczian/excalidraw/types/clipboard";
import { updateEquation } from "./LaTeX";
@@ -56,7 +56,7 @@ export interface ExportSettings {
const REG_LINKINDEX_INVALIDCHARS = /[<>:"\\|?*]/g;
export const addFiles = (files:FileData[], view: ExcalidrawView,isDark?:boolean) => {
export const addFiles = async (files:FileData[], view: ExcalidrawView,isDark?:boolean) => {
if(!files || files.length === 0 || !view) return;
const [dirty, scene] = scaleLoadedImage(view.getScene(),files);
if(isDark===undefined) isDark = scene.appState.theme;
@@ -68,7 +68,7 @@ export const addFiles = (files:FileData[], view: ExcalidrawView,isDark?:boolean)
commitToHistory: false,
});
}
files.forEach((f:FileData)=>{
for(const f of files) {
if(view.excalidrawData.hasFile(f.id)) {
const embeddedFile = view.excalidrawData.getFile(f.id);
embeddedFile.setImage(
@@ -83,7 +83,7 @@ export const addFiles = (files:FileData[], view: ExcalidrawView,isDark?:boolean)
const latex = view.excalidrawData.getEquation(f.id).latex;
view.excalidrawData.setEquation(f.id,{latex,isLoaded:true});
}
});
};
view.excalidrawAPI.addFiles(files);
}
@@ -308,6 +308,25 @@ export default class ExcalidrawView extends TextFileView {
}
await this.save(true); //in case pasted images haven't been saved yet
if(this.excalidrawData.hasFile(selectedImage.fileId)) {
if(ev.altKey) {
const ef = this.excalidrawData.getFile(selectedImage.fileId);
if(ef.file.extension==="md" && !this.plugin.isExcalidrawFile(ef.file)) {
const prompt = new Prompt(
this.app,
"Customize the link",
ef.linkParts.original,
'',
"Do not add [[square brackets]] around the filename!<br>Follow this format when editing your link:<br><mark>filename#^blockref|WIDTHxMAXHEIGHT</mark>"
);
prompt.openAndGetValue( async (link:string)=> {
if(!link) return;
ef.resetImage(this.file.path,link);
await this.save(true);
await this.loadSceneFiles();
});
return;
}
}
linkText = this.excalidrawData.getFile(selectedImage.fileId).file.path;
}
}
@@ -450,6 +469,8 @@ export default class ExcalidrawView extends TextFileView {
// clear the view content
clear() {
if(!this.excalidrawRef) return;
if(this.activeLoader) this.activeLoader.terminate=true;
this.nextLoader = null;
this.excalidrawAPI.resetScene();
this.excalidrawAPI.history.clear();
}
@@ -494,20 +515,27 @@ export default class ExcalidrawView extends TextFileView {
}
private activeLoader:EmbeddedFilesLoader = null;
private async loadSceneFiles(isDark?:boolean) {
if(this.activeLoader) this.activeLoader.terminate=true;
private nextLoader:EmbeddedFilesLoader = null;
public async loadSceneFiles(isDark?:boolean) {
const loader = new EmbeddedFilesLoader(this.plugin,isDark);
if(isDark !== undefined) this.excalidrawData.scene.appState.theme = isDark ? "dark" : "light";
this.activeLoader = loader;
//debug({where:"ExcalidrawView.loadSceneFiles",file:this.file.name,dataTheme:this.excalidrawData.scene.appState.theme,before:"loader.loadSceneFiles",isDark})
loader.loadSceneFiles(
this.excalidrawData,
this,
(files:FileData[], view:ExcalidrawView) => {
this.activeLoader = null;
if(!files || !view) return;
addFiles(files,view,isDark);
});
debug({where:"ExcalidrawView.loadSceneFiles",status:"loader created",file:this.file.name,loader:loader.uid});
const runLoader = (l:EmbeddedFilesLoader) => {
this.nextLoader = null;
this.activeLoader = l;
debug({where:"ExcalidrawView.loadSceneFiles",status:"loader initiated",file:this.file.name,loader:l.uid});
if(isDark !== undefined) this.excalidrawData.scene.appState.theme = isDark ? "dark" : "light";
//debug({where:"ExcalidrawView.loadSceneFiles",file:this.file.name,dataTheme:this.excalidrawData.scene.appState.theme,before:"loader.loadSceneFiles",isDark})
l.loadSceneFiles(
this.excalidrawData,
(files:FileData[]) => {
if(!files) return;
addFiles(files,this,isDark);
this.activeLoader = null;
if(this.nextLoader) runLoader(this.nextLoader);
});
}
if(!this.activeLoader) runLoader(loader); else this.nextLoader=loader;
}
/**
@@ -1031,21 +1059,28 @@ export default class ExcalidrawView extends TextFileView {
this.altKeyDown = e.altKey;
if(e[CTRL_OR_CMD] && !e.shiftKey && !e.altKey) { //.ctrlKey||e.metaKey) && !e.shiftKey && !e.altKey) {
let linktext = "";
const selectedElement = getTextElementAtPointer(currentPosition);
if(!selectedElement) return;
if(!selectedElement || !selectedElement.text) {
const selectedImgElement = getImageElementAtPointer(currentPosition)
if(!selectedImgElement || !selectedImgElement.fileId) return;
if(!this.excalidrawData.hasFile(selectedImgElement.fileId)) return;
const ef = this.excalidrawData.getFile(selectedImgElement.fileId);
const ref = ef.linkParts.ref ? "#"+(ef.linkParts.isBlockRef?"^":"")+ef.linkParts.ref:"";
linktext = this.excalidrawData.getFile(selectedImgElement.fileId).file.path+ref;
} else {
const text:string = (this.textMode == TextMode.parsed)
? this.excalidrawData.getRawText(selectedElement.id)
: selectedElement.text;
const text:string = (this.textMode == TextMode.parsed)
? this.excalidrawData.getRawText(selectedElement.id)
: selectedElement.text;
if(!text) return;
if(text.match(REG_LINKINDEX_HYPERLINK)) return;
if(!text) return;
if(text.match(REG_LINKINDEX_HYPERLINK)) return;
const parts = REGEX_LINK.getRes(text).next();
if(!parts.value) return;
let linktext = REGEX_LINK.getLink(parts); //parts.value[2] ? parts.value[2]:parts.value[6];
if(linktext.match(REG_LINKINDEX_HYPERLINK)) return;
const parts = REGEX_LINK.getRes(text).next();
if(!parts.value) return;
linktext = REGEX_LINK.getLink(parts); //parts.value[2] ? parts.value[2]:parts.value[6];
if(linktext.match(REG_LINKINDEX_HYPERLINK)) return;
}
this.plugin.hover.linkText = linktext;
this.plugin.hover.sourcePath = this.file.path;

View File

@@ -42,6 +42,7 @@ export class InsertImageDialog extends FuzzySuggestModal<TFile> {
const ea = this.plugin.ea;
ea.reset();
ea.setView(this.view);
ea.canvas.theme = this.view.excalidrawAPI.getAppState().theme;
(async () => {
await ea.addImage(0,0,item);
ea.addElementsToView(true,false);

54
src/InsertMDDialog.ts Normal file
View File

@@ -0,0 +1,54 @@
import {
App,
FuzzySuggestModal,
TFile
} from "obsidian";
import { IMAGE_TYPES } from "./constants";
import { ExcalidrawAutomate } from "./ExcalidrawAutomate";
import ExcalidrawView from "./ExcalidrawView";
import {t} from './lang/helpers'
import ExcalidrawPlugin from "./main";
export class InsertMDDialog extends FuzzySuggestModal<TFile> {
public app: App;
public plugin: ExcalidrawPlugin;
private view: ExcalidrawView;
constructor(plugin: ExcalidrawPlugin) {
super(plugin.app);
this.plugin = plugin;
this.app = plugin.app;
this.limit = 20;
this.setInstructions([{
command: t("SELECT_FILE"),
purpose: "",
}]);
this.setPlaceholder(t("SELECT_MD"));
this.emptyStateText = t("NO_MATCH");
}
getItems(): TFile[] {
return (this.app.vault.getFiles() || []).filter((f:TFile) => (f.extension==="md") && !this.plugin.isExcalidrawFile(f));
}
getItemText(item: TFile): string {
return item.path;
}
onChooseItem(item: TFile, _evt: MouseEvent | KeyboardEvent): void {
const ea = this.plugin.ea;
ea.reset();
ea.setView(this.view);
(async () => {
await ea.addImage(0,0,item);
ea.addElementsToView(true,false);
})();
}
public start(view: ExcalidrawView) {
this.view = view;
this.open();
}
}

View File

@@ -4,7 +4,7 @@ export class Prompt extends Modal {
private promptEl: HTMLInputElement;
private resolve: (value: string) => void;
constructor(app: App, private prompt_text: string, private default_value: string, private placeholder:string) {
constructor(app: App, private prompt_text: string, private default_value: string, private placeholder:string, private prompt_desc?:string) {
super(app);
}
@@ -18,9 +18,14 @@ export class Prompt extends Modal {
}
createForm(): void {
const div = this.contentEl.createDiv();
let div = this.contentEl.createDiv();
div.addClass("excalidraw-prompt-div");
if(this.prompt_desc) {
div = div.createDiv();
div.style.width = "100%";
const p=div.createEl("p");
p.innerHTML = this.prompt_desc;
}
const form = div.createEl("form");
form.addClass("excalidraw-prompt-form");
form.type = "submit";

View File

@@ -297,6 +297,28 @@ export function getIMGFilename(path:string,extension:string):string {
return path.substring(0,path.lastIndexOf('.')) + '.' + extension;
}
export type LinkParts = {
original: string,
path: string,
isBlockRef: boolean,
ref: string,
width: number,
height: number
}
export const getLinkParts = (fname:string):LinkParts => {
const REG = /(^[^#\|]+)#?(\^)?([^\|]*)?\|?(\d*)x?(\d*)/;
const parts = fname.match(REG)
return {
original: fname,
path: parts[1],
isBlockRef: parts[2]==="^",
ref: parts[3],
width: parts[4]?parseInt(parts[4]):undefined,
height: parts[5]?parseInt(parts[5]):undefined
}
}
export const errorlog = (data:{}) => {
console.log({plugin:"Excalidraw",...data});
}

View File

@@ -14,6 +14,8 @@ export const FRONTMATTER_KEY_CUSTOM_URL_PREFIX = "excalidraw-url-prefix";
export const FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS = "excalidraw-link-brackets";
export const FRONTMATTER_KEY_DEFAULT_MODE = "excalidraw-default-mode";
export const FRONTMATTER_KEY_FONT = "excalidraw-font";
export const FRONTMATTER_KEY_FONTCOLOR = "excalidraw-font-color";
export const FRONTMATTER_KEY_CSS = "excalidraw-css-file";
export const VIEW_TYPE_EXCALIDRAW = "excalidraw";
export const ICON_NAME = "excalidraw-icon";
export const MAX_COLORS = 5;

View File

@@ -24,6 +24,7 @@ export default {
TOGGLE_LOCK: "Toggle Text Element edit RAW/PREVIEW",
INSERT_LINK: "Insert link to file",
INSERT_IMAGE: "Insert image from vault",
INSERT_MD: "Insert markdown file from vault",
INSERT_LATEX: "Insert LaTeX formula (e.g. \\binom{n}{k} = \\frac{n!}{k!(n-k)!})",
ENTER_LATEX: "Enter a valid LaTeX expression",
@@ -91,7 +92,7 @@ export default {
ZOOM_TO_FIT_MAX_LEVEL_NAME: "Zoom to fit max ZOOM level",
ZOOM_TO_FIT_MAX_LEVEL_DESC: "Set the maximum level to which zoom to fit will enlarge the drawing. Minimum is 0.5 (50%) and maximum is 10 (1000%).",
LINKS_HEAD: "Links and transclusion",
LINKS_DESC: "CTRL/CMD + CLICK on Text Elements to open them as links. " +
LINKS_DESC: "CTRL/CMD + CLICK on [[Text Elements]] to open them as links. " +
"If the selected text has more than one [[valid Obsidian links]], only the first will be opened. " +
"If the text starts as a valid web link (i.e. https:// or http://), then " +
"the plugin will open it in a browser. " +
@@ -114,7 +115,7 @@ export default {
URL_PREFIX_DESC:"In PREVIEW mode, if the Text Element contains a URL link, precede the text with these characters. " +
"You can override this setting for a specific drawing by adding \'" + FRONTMATTER_KEY_CUSTOM_URL_PREFIX +
': "🌐 "\' to the file\'s frontmatter.',
LINK_CTRL_CLICK_NAME: "CTRL/CMD + CLICK on text to open them as links",
LINK_CTRL_CLICK_NAME: "CTRL/CMD + CLICK on text with [[links]] or [](links) to open them",
LINK_CTRL_CLICK_DESC: "You can turn this feature off if it interferes with default Excalidraw features you want to use. If " +
"this is turned off, only the link button in the title bar of the drawing pane will open links.",
TRANSCLUSION_WRAP_NAME: "Overflow wrap behavior of transcluded text",
@@ -126,6 +127,22 @@ export default {
"![[markdown page]] format.",
GET_URL_TITLE_NAME: "Use iframely to resolve page title",
GET_URL_TITLE_DESC: "Use the http://iframely.server.crestify.com/iframely?url= to get title of page when dropping a link into Excalidraw",
MD_HEAD: "Markdown-embed settings",
MD_HEAD_DESC: "You can transclude formatted markdown documents into drawings as images CTRL/CMD drop from the file explorer or using "+
"the command palette action.",
MD_TRANSCLUDE_WIDTH_NAME: "Default width of a transcluded markdown document",
MD_TRANSCLUDE_WIDTH_DESC: "The width of the markdown page. This effects the word wrapping when transcluding longer paragraphs, and the width of " +
"the image element. You can override the default width of " +
"an embedded file using the [[filename#heading|WIDTHxMAXHEIGHT]] syntax in markdown view mode under embedded files.",
MD_TRANSCLUDE_HEIGHT_NAME: "Default maximum height of a transcluded markdown document",
MD_TRANSCLUDE_HEIGHT_DESC: "The embedded image will be as high as the markdown text requries, but not higher than this value. " +
"You can override this value by editing the embedded image link in markdown view mode with the following syntax [[filename#^blockref|WIDTHxMAXHEIGHT]].",
MD_DEFAULT_FONT_NAME: "The default font typeface to use for embedded markdown files.",
MD_DEFAULT_FONT_DESC: 'Set this value to "Virgil" or "Cascadia" or the filename of a valid .ttf, .woff, or .woff2 font e.g. "MyFont.woff2" ' +
'You can override this setting by adding the following frontmatter-key to the embedded markdown file: "excalidraw-font: font_or_filename"',
MD_DEFAULT_COLOR_NAME: "The default font color to use for embedded markdown files.",
MD_DEFAULT_COLOR_DESC: 'Set this to allowed css color names e.g. "steelblue" (https://www.w3schools.com/colors/colors_names.asp), or a valid hexadecimal color e.g. "#e67700". ' +
'You can override this setting by adding the following frontmatter-key to the embedded markdown file: "excalidraw-font-color: color_name_or_rgbhex"',
EMBED_HEAD: "Embed & Export",
EMBED_PREVIEW_SVG_NAME: "Display SVG in markdown preview",
EMBED_PREVIEW_SVG_DESC: "The default is to display drawings as SVG images in the markdown preview. Turning this feature off, the markdown preview will display the drawing as an embedded PNG image.",
@@ -186,6 +203,7 @@ export default {
TYPE_FILENAME: "Type name of drawing to select.",
SELECT_FILE_OR_TYPE_NEW: "Select existing drawing or type name of a new drawing then press Enter.",
SELECT_TO_EMBED: "Select the drawing to insert into active document.",
SELECT_MD: "Select the markdown document you want to insert",
//EmbeddedFileLoader.ts
INFINITE_LOOP_WARNING: "EXCALIDRAW WARNING\nAborted loading embedded images due to infinite loop in file:\n",

View File

@@ -53,10 +53,15 @@ import {
import {
InsertImageDialog
} from "./InsertImageDialog";
import {
InsertMDDialog
} from "./InsertMDDialog";
import {
initExcalidrawAutomate,
destroyExcalidrawAutomate,
ExcalidrawAutomate
ExcalidrawAutomate,
createSVG,
createPNG
} from "./ExcalidrawAutomate";
import { Prompt } from "./Prompt";
import { around } from "monkey-around";
@@ -84,6 +89,7 @@ export default class ExcalidrawPlugin extends Plugin {
private openDialog: OpenFileDialog;
private insertLinkDialog: InsertLinkDialog;
private insertImageDialog: InsertImageDialog;
private insertMDDialog: InsertMDDialog;
private activeExcalidrawView: ExcalidrawView = null;
public lastActiveExcalidrawFilePath: string = null;
public hover: {linkText: string, sourcePath: string} = {linkText: null, sourcePath: null};
@@ -238,7 +244,6 @@ export default class ExcalidrawPlugin extends Plugin {
img.addClass(imgAttributes.style);
const [scene,pos] = getJSON(content);
this.ea.reset();
const theme = this.settings.previewMatchObsidianTheme
? (isObsidianThemeDark() ? "dark" : "light")
@@ -255,13 +260,34 @@ export default class ExcalidrawPlugin extends Plugin {
if(width>=1200) scale = 3;
if(width>=1800) scale = 4;
if(width>=2400) scale = 5;
const png = await this.ea.createPNG(file.path,scale,exportSettings,loader,theme);
const png = await createPNG(
file.path,
scale,
exportSettings,
loader,
theme,
null,
null,
[],
this
);
//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,loader,theme)).outerHTML;
const svgSnapshot = (
await createSVG(
file.path,
true,
exportSettings,
loader,
theme,
null,
null,
[],
this
)).outerHTML;
let svg:SVGSVGElement = null;
const el = document.createElement('div');
el.innerHTML = svgSnapshot;
@@ -490,6 +516,7 @@ export default class ExcalidrawPlugin extends Plugin {
this.openDialog = new OpenFileDialog(this.app, this);
this.insertLinkDialog = new InsertLinkDialog(this.app);
this.insertImageDialog = new InsertImageDialog(this);
this.insertMDDialog = new InsertMDDialog(this);
this.addRibbonIcon(ICON_NAME, t("CREATE_NEW"), async (e) => {
this.createDrawing(this.getNextDefaultFilename(), e[CTRL_OR_CMD]); //.ctrlKey||e.metaKey);
@@ -750,6 +777,24 @@ export default class ExcalidrawPlugin extends Plugin {
},
});
this.addCommand({
id: "insert-md",
name: t("INSERT_MD"),
checkCallback: (checking: boolean) => {
if (checking) {
const view = this.app.workspace.activeLeaf.view;
return (view instanceof ExcalidrawView);
} else {
const view = this.app.workspace.activeLeaf.view;
if (view instanceof ExcalidrawView) {
this.insertMDDialog.start(view);
return true;
}
else return false;
}
},
});
this.addCommand({
id: "insert-LaTeX-symbol",
name: t("INSERT_LATEX"),
@@ -1043,23 +1088,29 @@ export default class ExcalidrawPlugin extends Plugin {
//save Excalidraw leaf and update embeds when switching to another leaf
const activeLeafChangeEventHandler = async (leaf:WorkspaceLeaf) => {
const activeExcalidrawView = self.activeExcalidrawView;
const newActiveview:ExcalidrawView = (leaf.view instanceof ExcalidrawView) ? leaf.view : null;
self.activeExcalidrawView = newActiveview;
if(newActiveview) {
self.lastActiveExcalidrawFilePath = newActiveview.file?.path;
const previouslyActiveEV = self.activeExcalidrawView;
const newActiveviewEV:ExcalidrawView = (leaf.view instanceof ExcalidrawView) ? leaf.view : null;
self.activeExcalidrawView = newActiveviewEV;
if(newActiveviewEV) {
self.lastActiveExcalidrawFilePath = newActiveviewEV.file?.path;
}
if(activeExcalidrawView && activeExcalidrawView != newActiveview) {
if(activeExcalidrawView.leaf != leaf) {
if(previouslyActiveEV && previouslyActiveEV != newActiveviewEV) {
if(previouslyActiveEV.leaf != leaf) {
//if loading new view to same leaf then don't save. Excalidarw view will take care of saving anyway.
//avoid double saving
await activeExcalidrawView.save(true); //this will update transclusions in the drawing
await previouslyActiveEV.save(true); //this will update transclusions in the drawing
}
if(activeExcalidrawView.file) {
self.triggerEmbedUpdates(activeExcalidrawView.file.path);
if(previouslyActiveEV.file) {
self.triggerEmbedUpdates(previouslyActiveEV.file.path);
}
}
if(newActiveviewEV && (!previouslyActiveEV || previouslyActiveEV.leaf != leaf)) {
//the user switched to a new leaf
//timeout gives time to the view being exited to finish saving
if(newActiveviewEV.file) setTimeout(()=>newActiveviewEV.loadSceneFiles(),1000); //refresh embedded files
}
};
self.registerEvent(
self.app.workspace.on("active-leaf-change",activeLeafChangeEventHandler)

View File

@@ -52,6 +52,10 @@ export interface ExcalidrawSettings {
imageElementNotice: boolean, //1.4.0
runWYSIWYGpatch: boolean, //1.4.9
fixInfinitePreviewLoop: boolean, //1.4.10
mdSVGwidth: number,
mdSVGmaxHeight: number,
mdFont: string,
mdFontColor: string,
}
export const DEFAULT_SETTINGS: ExcalidrawSettings = {
@@ -100,7 +104,11 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
patchCommentBlock: true,
imageElementNotice: true,
runWYSIWYGpatch: true,
fixInfinitePreviewLoop: true
fixInfinitePreviewLoop: true,
mdSVGwidth: 500,
mdSVGmaxHeight: 800,
mdFont: "Virgil",
mdFontColor: "Black",
}
export class ExcalidrawSettingTab extends PluginSettingTab {
@@ -297,8 +305,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
this.containerEl.createEl('h1', {text: t("LINKS_HEAD")});
this.containerEl.createEl('p',{
text: t("LINKS_DESC")});
this.containerEl.createEl('p',{text: t("LINKS_DESC")});
new Setting(containerEl)
.setName(t("ADJACENT_PANE_NAME"))
@@ -396,6 +403,81 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
this.applySettingsUpdate();
}));
this.containerEl.createEl('h1', {text: t("MD_HEAD")});
this.containerEl.createEl('p',{text: t("MD_HEAD_DESC")});
new Setting(containerEl)
.setName(t("MD_TRANSCLUDE_WIDTH_NAME"))
.setDesc(t("MD_TRANSCLUDE_WIDTH_DESC"))
.addText(text => text
.setPlaceholder('Enter a number e.g. 500')
.setValue(this.plugin.settings.mdSVGwidth.toString())
.onChange(async (value) => {
const intVal = parseInt(value);
if(isNaN(intVal) && value!=="") {
text.setValue(this.plugin.settings.mdSVGwidth.toString());
return;
}
this.requestEmbedUpdate = true;
if(value === "") {
this.plugin.settings.mdSVGwidth = 500;
this.applySettingsUpdate(true);
return;
}
this.plugin.settings.mdSVGwidth = intVal;
this.requestReloadDrawings=true;
text.setValue(this.plugin.settings.mdSVGwidth.toString());
this.applySettingsUpdate(true);
}));
new Setting(containerEl)
.setName(t("MD_TRANSCLUDE_HEIGHT_NAME"))
.setDesc(t("MD_TRANSCLUDE_HEIGHT_DESC"))
.addText(text => text
.setPlaceholder('Enter a number e.g. 800')
.setValue(this.plugin.settings.mdSVGmaxHeight.toString())
.onChange(async (value) => {
const intVal = parseInt(value);
if(isNaN(intVal) && value!=="") {
text.setValue(this.plugin.settings.mdSVGmaxHeight.toString());
return;
}
this.requestEmbedUpdate = true;
if(value === "") {
this.plugin.settings.mdSVGmaxHeight = 800;
this.applySettingsUpdate(true);
return;
}
this.plugin.settings.mdSVGmaxHeight = intVal;
this.requestReloadDrawings=true;
text.setValue(this.plugin.settings.mdSVGmaxHeight.toString());
this.applySettingsUpdate(true);
}));
new Setting(containerEl)
.setName(t("MD_DEFAULT_FONT_NAME"))
.setDesc(t("MD_DEFAULT_FONT_DESC"))
.addText(text => text
.setPlaceholder("Virgil|Cascadia|Filename")
.setValue(this.plugin.settings.mdFont)
.onChange((value) => {
this.requestReloadDrawings=true;
this.plugin.settings.mdFont = value;
this.applySettingsUpdate(true);
}));
new Setting(containerEl)
.setName(t("MD_DEFAULT_COLOR_NAME"))
.setDesc(t("MD_DEFAULT_COLOR_DESC"))
.addText(text => text
.setPlaceholder("CSS Color-name|RGB-HEX")
.setValue(this.plugin.settings.mdFontColor)
.onChange((value) => {
this.requestReloadDrawings=true;
this.plugin.settings.mdFontColor = value;
this.applySettingsUpdate(true);
}));
this.containerEl.createEl('h1', {text: t("EMBED_HEAD")});