From ec5a13f9e45a6b57fc56c5151fb58b475dd1e9d5 Mon Sep 17 00:00:00 2001 From: Zsolt Viczian Date: Tue, 10 Aug 2021 20:57:12 +0200 Subject: [PATCH] 1.2.14 --- manifest.json | 2 +- src/ExcalidrawAutomate.ts | 29 +---------- src/ExcalidrawData.ts | 9 ++-- src/Utils.ts | 20 ++++---- src/lang/locale/en.ts | 2 + src/main.ts | 101 ++++++++++++++++++++++++++++---------- src/settings.ts | 14 ++++++ versions.json | 2 +- 8 files changed, 107 insertions(+), 72 deletions(-) diff --git a/manifest.json b/manifest.json index 67189b4..d22db25 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-excalidraw-plugin", "name": "Excalidraw", - "version": "1.2.13", + "version": "1.2.14", "minAppVersion": "0.12.0", "description": "An Obsidian plugin to edit and view Excalidraw drawings", "author": "Zsolt Viczian", diff --git a/src/ExcalidrawAutomate.ts b/src/ExcalidrawAutomate.ts index dc8dfd8..4edc9d4 100644 --- a/src/ExcalidrawAutomate.ts +++ b/src/ExcalidrawAutomate.ts @@ -177,7 +177,7 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) { params?.filename ? params.filename + '.excalidraw.md' : this.plugin.getNextDefaultFilename(), params?.onNewPane ? params.onNewPane : false, params?.foldername ? params.foldername : this.plugin.settings.folder, - FRONTMATTER + exportSceneToMD( + FRONTMATTER + plugin.exportSceneToMD( JSON.stringify({ type: "excalidraw", version: 2, @@ -492,30 +492,3 @@ async function getTemplate(fileWithPath: string):Promise<{elements: any,appState } } -/** - * Extracts the text elements from an Excalidraw scene into a string of ids as headers followed by the text contents - * @param {string} data - Excalidraw scene JSON string - * @returns {string} - Text starting with the "# Text Elements" header and followed by each "## id-value" and text - */ - export function exportSceneToMD(data:string): string { - if(!data) return ""; - const excalidrawData = JSON_parse(data); - const textElements = excalidrawData.elements?.filter((el:any)=> el.type=="text") - let outString = '# Text Elements\n'; - let id:string; - for (const te of textElements) { - id = te.id; - //replacing Excalidraw text IDs with my own, because default IDs may contain - //characters not recognized by Obsidian block references - //also Excalidraw IDs are inconveniently long - if(te.id.length>8) { - id=nanoid(); - data = data.replaceAll(te.id,id); //brute force approach to replace all occurances. - } - outString += te.text+' ^'+id+'\n\n'; - } - return outString + '# Drawing\n' - + String.fromCharCode(96)+String.fromCharCode(96)+String.fromCharCode(96)+'json\n' - + data + '\n' - + String.fromCharCode(96)+String.fromCharCode(96)+String.fromCharCode(96); -} \ No newline at end of file diff --git a/src/ExcalidrawData.ts b/src/ExcalidrawData.ts index 2e0ac49..0cf820a 100644 --- a/src/ExcalidrawData.ts +++ b/src/ExcalidrawData.ts @@ -12,7 +12,7 @@ import { } from "./constants"; import { TextMode } from "./ExcalidrawView"; -const DRAWING_REG = /[\r\n]# Drawing[\r\n](```json[\r\n])?(.*)(```)?/gm; +const DRAWING_REG = /[\r\n]# Drawing[\r\n](```json[\r\n])?(.*)(```)?(%%)?/gm; //![[link|alias]]![alias](link) //1 2 3 4 5 6 @@ -37,8 +37,10 @@ export class ExcalidrawData { private showLinkBrackets: boolean; private linkPrefix: string; private textMode: TextMode = TextMode.raw; + private plugin: ExcalidrawPlugin; constructor(plugin: ExcalidrawPlugin) { + this.plugin = plugin; this.settings = plugin.settings; this.app = plugin.app; } @@ -369,10 +371,7 @@ export class ExcalidrawData { for(const key of this.textElements.keys()){ outString += this.textElements.get(key).raw+' ^'+key+'\n\n'; } - return outString + '# Drawing\n' - + String.fromCharCode(96)+String.fromCharCode(96)+String.fromCharCode(96)+'json\n' - + JSON.stringify(this.scene) + '\n' - + String.fromCharCode(96)+String.fromCharCode(96)+String.fromCharCode(96); + return outString + this.plugin.getMarkdownDrawingSection(JSON.stringify(this.scene)); } public syncElements(newScene:any):boolean { diff --git a/src/Utils.ts b/src/Utils.ts index 5bcf925..0da5105 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -60,15 +60,15 @@ export function getIMGPathFromExcalidrawFile (excalidrawPath:string,newExtension return fname; } - /** - * Open or create a folderpath if it does not exist - * @param folderpath - */ - export async function checkAndCreateFolder(vault:Vault,folderpath:string) { - let folder = vault.getAbstractFileByPath(folderpath); - if(folder && folder instanceof TFolder) return; - await vault.createFolder(folderpath); - } +/** +* Open or create a folderpath if it does not exist +* @param folderpath +*/ +export async function checkAndCreateFolder(vault:Vault,folderpath:string) { + let folder = vault.getAbstractFileByPath(folderpath); + if(folder && folder instanceof TFolder) return; + await vault.createFolder(folderpath); +} let random = new Random(Date.now()); -export const randomInteger = () => Math.floor(random.next() * 2 ** 31); \ No newline at end of file +export const randomInteger = () => Math.floor(random.next() * 2 ** 31); diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts index f43a1af..1fb92e3 100644 --- a/src/lang/locale/en.ts +++ b/src/lang/locale/en.ts @@ -88,6 +88,8 @@ export default { 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.", 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.", EMBED_WIDTH_NAME: "Default width of embedded (transcluded) image", EMBED_WIDTH_DESC: "The default width of an embedded drawing. You can specify a custom " + "width when embedding an image using the ![[drawing.excalidraw|100]] or " + diff --git a/src/main.ts b/src/main.ts index f369b22..3c0b991 100644 --- a/src/main.ts +++ b/src/main.ts @@ -37,7 +37,8 @@ import { TEXT_DISPLAY_PARSED_ICON_NAME, TEXT_DISPLAY_RAW_ICON_NAME, //UNLOCK_ICON, - JSON_parse + JSON_parse, + nanoid } from "./constants"; import ExcalidrawView, {ExportSettings, TextMode} from "./ExcalidrawView"; import {getJSON} from "./ExcalidrawData"; @@ -52,8 +53,7 @@ import { } from "./openDrawing"; import { initExcalidrawAutomate, - destroyExcalidrawAutomate, - exportSceneToMD, + destroyExcalidrawAutomate } from "./ExcalidrawAutomate"; import { Prompt } from "./Prompt"; import { around } from "monkey-around"; @@ -128,6 +128,7 @@ export default class ExcalidrawPlugin extends Plugin { private addMarkdownPostProcessor() { interface imgElementAttributes { + file?: TFile, fname: string, //Excalidraw filename fwidth: string, //Display width of image fheight: string, //Display height of image @@ -140,9 +141,13 @@ export default class ExcalidrawPlugin extends Plugin { * @returns {Promise} - the IMG HTML element containing the encoded SVG image */ const getIMG = async (parts:imgElementAttributes):Promise => { - const file = this.app.vault.getAbstractFileByPath(parts.fname); - if(!(file && file instanceof TFile)) { - return null; + let file = parts.file; + if(!parts.file) { + const f = this.app.vault.getAbstractFileByPath(parts.fname); + if(!(f && f instanceof TFile)) { + return null; + } + file = f; } const content = await this.app.vault.read(file); @@ -150,16 +155,28 @@ export default class ExcalidrawPlugin extends Plugin { withBackground: this.settings.exportWithBackground, withTheme: this.settings.exportWithTheme } + const img = createEl("img"); + img.setAttribute("width",parts.fwidth); + if(parts.fheight) img.setAttribute("height",parts.fheight); + img.addClass(parts.style); + + + if(!this.settings.displaySVGInPreview) { + const width = parseInt(parts.fwidth); + let scale = 1; + if(width>=800) scale = 2; + if(width>=1600) scale = 3; + if(width>=2400) scale = 4; + const png = await ExcalidrawView.getPNG(JSON_parse(getJSON(content)),exportSettings, scale); + if(!png) return null; + img.src = URL.createObjectURL(png); + return img; + } let svg = await ExcalidrawView.getSVG(JSON_parse(getJSON(content)),exportSettings); if(!svg) return null; svg = ExcalidrawView.embedFontsInSVG(svg); - const img = createEl("img"); svg.removeAttribute('width'); svg.removeAttribute('height'); - img.setAttribute("width",parts.fwidth); - - if(parts.fheight) img.setAttribute("height",parts.fheight); - img.addClass(parts.style); img.setAttribute("src","data:image/svg+xml;base64,"+btoa(unescape(encodeURIComponent(svg.outerHTML.replaceAll(" "," "))))); return img; } @@ -174,7 +191,7 @@ export default class ExcalidrawPlugin extends Plugin { if(drawings.length==0) return; let attr:imgElementAttributes={fname:"",fheight:"",fwidth:"",style:""}; - let alt:string, img:any, parts, div, file:TFile; + let alt:string, parts, div, file:TFile; for (const drawing of drawings) { attr.fname = drawing.getAttribute("src"); file = this.app.metadataCache.getFirstLinkpathDest(attr.fname, ctx.sourcePath); @@ -196,8 +213,8 @@ export default class ExcalidrawPlugin extends Plugin { } attr.fname = file?.path; - img = await getIMG(attr); - div = createDiv(attr.style, (el)=>{ + div = createDiv(attr.style, async (el)=>{ + const img = await getIMG(attr); el.append(img); el.setAttribute("src",file.path); if(attr.fwidth) el.setAttribute("w",attr.fwidth); @@ -245,13 +262,13 @@ export default class ExcalidrawPlugin extends Plugin { ); //monitoring for div.popover.hover-popover.file-embed.is-loaded to be added to the DOM tree - this.observer = new MutationObserver((m)=>{ + this.observer = new MutationObserver(async (m)=>{ if(m.length == 0) return; if(!this.hover.linkText) return; const file = this.app.metadataCache.getFirstLinkpathDest(this.hover.linkText, this.hover.sourcePath?this.hover.sourcePath:""); if(!file || !(file instanceof TFile) || !this.isExcalidrawFile(file)) return - if((file as TFile).extension == "excalidraw") { + if(file.extension == "excalidraw") { observerForLegacyFileFormat(m,file); return; } @@ -271,10 +288,12 @@ export default class ExcalidrawPlugin extends Plugin { //@ts-ignore if(!m[i].addedNodes[0].matchParent(".hover-popover")) return; + const node = m[i].addedNodes[0]; + //this div will be on top of original DIV. By stopping the propagation of the click //I prevent the default Obsidian feature of openning the link in the native app - const div = createDiv("",async (el)=>{ - const img = await getIMG({fname:file.path,fwidth:"300",fheight:null,style:"excalidraw-svg"}); + const div = createDiv("", async (el)=>{ + const img = await getIMG({file:file,fname:file.path,fwidth:"300",fheight:null,style:"excalidraw-svg"}); el.appendChild(img); el.setAttribute("src",file.path); el.onClickEvent((ev)=>{ @@ -283,12 +302,11 @@ export default class ExcalidrawPlugin extends Plugin { if(src) this.openDrawing(this.app.vault.getAbstractFileByPath(src) as TFile,ev.ctrlKey||ev.metaKey); }); }); - m[i].addedNodes[0].insertBefore(div,m[i].addedNodes[0].firstChild) - + node.insertBefore(div,node.firstChild) }); //compatibility: .excalidraw file observer - let observerForLegacyFileFormat = (m:MutationRecord[], file:TFile) => { + let observerForLegacyFileFormat = async (m:MutationRecord[], file:TFile) => { if(!this.hover.linkText) return; if(m.length!=1) return; if(m[0].addedNodes.length != 1) return; @@ -299,8 +317,8 @@ export default class ExcalidrawPlugin extends Plugin { //this div will be on top of original DIV. By stopping the propagation of the click //I prevent the default Obsidian feature of openning the link in the native app + const img = await getIMG({file:file,fname:file.path,fwidth:"300",fheight:null,style:"excalidraw-svg"}); const div = createDiv("",async (el)=>{ - const img = await getIMG({fname:file.path,fwidth:"300",fheight:null,style:"excalidraw-svg"}); el.appendChild(img); el.setAttribute("src",file.path); el.onClickEvent((ev)=>{ @@ -692,7 +710,7 @@ export default class ExcalidrawPlugin extends Plugin { const filename = file.name.substr(0,file.name.lastIndexOf(".excalidraw")) + (replaceExtension ? ".md" : ".excalidraw.md"); const fname = getNewUniqueFilepath(this.app.vault,filename,normalizePath(file.path.substr(0,file.path.lastIndexOf(file.name)))); console.log(fname); - const result = await this.app.vault.create(fname,FRONTMATTER + exportSceneToMD(data)); + const result = await this.app.vault.create(fname,FRONTMATTER + this.exportSceneToMD(data)); if (this.settings.keepInSync) { ['.svg','.png'].forEach( (ext:string)=>{ const oldIMGpath = file.path.substring(0,file.path.lastIndexOf(".excalidraw")) + ext; @@ -1002,10 +1020,39 @@ export default class ExcalidrawPlugin extends Plugin { if (this.settings.compatibilityMode) { return BLANK_DRAWING; } - return FRONTMATTER + '\n# Drawing\n' - + String.fromCharCode(96)+String.fromCharCode(96)+String.fromCharCode(96)+'json\n' - + BLANK_DRAWING + '\n' - + String.fromCharCode(96)+String.fromCharCode(96)+String.fromCharCode(96); + return FRONTMATTER + '\n' + this.getMarkdownDrawingSection(BLANK_DRAWING); + } + + public getMarkdownDrawingSection(jsonString: string) { + return '%%\n# Drawing\n' + + String.fromCharCode(96)+String.fromCharCode(96)+String.fromCharCode(96)+'json\n' + + jsonString + '\n' + + String.fromCharCode(96)+String.fromCharCode(96)+String.fromCharCode(96) + '%%'; + } + + /** + * Extracts the text elements from an Excalidraw scene into a string of ids as headers followed by the text contents + * @param {string} data - Excalidraw scene JSON string + * @returns {string} - Text starting with the "# Text Elements" header and followed by each "## id-value" and text + */ + public exportSceneToMD(data:string): string { + if(!data) return ""; + const excalidrawData = JSON_parse(data); + const textElements = excalidrawData.elements?.filter((el:any)=> el.type=="text") + let outString = '# Text Elements\n'; + let id:string; + for (const te of textElements) { + id = te.id; + //replacing Excalidraw text IDs with my own, because default IDs may contain + //characters not recognized by Obsidian block references + //also Excalidraw IDs are inconveniently long + if(te.id.length>8) { + id=nanoid(); + data = data.replaceAll(te.id,id); //brute force approach to replace all occurances. + } + outString += te.text+' ^'+id+'\n\n'; + } + return outString + this.getMarkdownDrawingSection(data); } public async createDrawing(filename: string, onNewPane: boolean, foldername?: string, initData?:string) { diff --git a/src/settings.ts b/src/settings.ts index 9275969..ca46a83 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -14,6 +14,7 @@ export interface ExcalidrawSettings { templateFilePath: string, drawingFilenamePrefix: string, drawingFilenameDateTime: string, + displaySVGInPreview: boolean, width: string, showLinkBrackets: boolean, linkPrefix: string, @@ -40,6 +41,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = { templateFilePath: 'Excalidraw/Template.excalidraw', drawingFilenamePrefix: 'Drawing ', drawingFilenameDateTime: 'YYYY-MM-DD HH.mm.ss', + displaySVGInPreview: true, width: '400', linkPrefix: "📍", showLinkBrackets: true, @@ -208,6 +210,18 @@ export class ExcalidrawSettingTab extends PluginSettingTab { this.containerEl.createEl('h1', {text: t("EMBED_HEAD")}); + + new Setting(containerEl) + .setName(t("EMBED_PREVIEW_SVG_NAME")) + .setDesc(t("EMBED_PREVIEW_SVG_DESC")) + .addToggle(toggle => toggle + .setValue(this.plugin.settings.displaySVGInPreview) + .onChange(async (value) => { + this.plugin.settings.displaySVGInPreview = value; + await this.plugin.saveSettings(); + })); + + new Setting(containerEl) .setName(t("EMBED_WIDTH_NAME")) .setDesc(t("EMBED_WIDTH_DESC")) diff --git a/versions.json b/versions.json index 4c0b748..29f60f0 100644 --- a/versions.json +++ b/versions.json @@ -1,3 +1,3 @@ { - "1.2.13": "0.11.13" + "1.2.14": "0.11.13" }