diff --git a/src/EmbeddedFileLoader.ts b/src/EmbeddedFileLoader.ts index b253605..5bc4196 100644 --- a/src/EmbeddedFileLoader.ts +++ b/src/EmbeddedFileLoader.ts @@ -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 = new Map(); 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 => { return svgToBase64(svg) as DataURL; } -const mdSVGwidth = 640; -const mdSVGmaxHeight = 800; -const convertMarkdownToSVG = async (plugin: ExcalidrawPlugin, file: TFile): Promise => { - const text = await plugin.app.vault.cachedRead(file); +const convertMarkdownToSVG = async (plugin: ExcalidrawPlugin, file: TFile, linkParts: LinkParts): Promise => { + + //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= '' + - xml+''; - 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= '' + - xml+''; - 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 to because links anyway don't work when the foreignObject is + //encapsulated in an img element. does not render with an underline, will. + const xml = (new XMLSerializer().serializeToString(div)).replaceAll("",""); + let svgStyle = ' width="'+linkParts.width+'px" height="100%"'; + let foreignObjectStyle = ' width="'+linkParts.width+'px" height="100%"'; + + const svg = () => '' + + '' + + xml + + ''; + + //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 => { @@ -307,7 +373,7 @@ const getDataURL = async (file: ArrayBuffer,mimeType: string): Promise 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})); }); }; diff --git a/src/ExcalidrawAutomate.ts b/src/ExcalidrawAutomate.ts index 04ce0b9..1326eff 100644 --- a/src/ExcalidrawAutomate.ts +++ b/src/ExcalidrawAutomate.ts @@ -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 { - 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{ + 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 { + 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; diff --git a/src/ExcalidrawData.ts b/src/ExcalidrawData.ts index 48baf08..58e5b9d 100644 --- a/src/ExcalidrawData.ts +++ b/src/ExcalidrawData.ts @@ -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 { + 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:"\\|?*]/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!
Follow this format when editing your link:
filename#^blockref|WIDTHxMAXHEIGHT" + ); + 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; diff --git a/src/InsertImageDialog.ts b/src/InsertImageDialog.ts index 72e184b..0743ab6 100644 --- a/src/InsertImageDialog.ts +++ b/src/InsertImageDialog.ts @@ -42,6 +42,7 @@ export class InsertImageDialog extends FuzzySuggestModal { 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); diff --git a/src/InsertMDDialog.ts b/src/InsertMDDialog.ts new file mode 100644 index 0000000..ea79d60 --- /dev/null +++ b/src/InsertMDDialog.ts @@ -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 { + 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(); + } +} diff --git a/src/Prompt.ts b/src/Prompt.ts index 7edf95f..ded2a43 100644 --- a/src/Prompt.ts +++ b/src/Prompt.ts @@ -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"; diff --git a/src/Utils.ts b/src/Utils.ts index 68a9b74..7a8bd9a 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -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}); } diff --git a/src/constants.ts b/src/constants.ts index 78cb503..8547949 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -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; diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts index 23bd5b5..fe78483 100644 --- a/src/lang/locale/en.ts +++ b/src/lang/locale/en.ts @@ -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", diff --git a/src/main.ts b/src/main.ts index 6c3463a..c75c623 100644 --- a/src/main.ts +++ b/src/main.ts @@ -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) diff --git a/src/settings.ts b/src/settings.ts index 68362a7..7726063 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -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")});