From f4a458061ab628d884fb39c98253a978b07fa0f1 Mon Sep 17 00:00:00 2001 From: Zsolt Viczian Date: Sat, 23 Oct 2021 14:32:05 +0200 Subject: [PATCH] Image click navigation. Image embeds finalized. --- TODO.md | 6 +- src/ExcalidrawAutomate.ts | 72 +++++++----- src/ExcalidrawData.ts | 19 ++-- src/ExcalidrawView.ts | 224 ++++++++++++++++++++++---------------- src/Utils.ts | 14 ++- src/lang/locale/en.ts | 6 +- src/main.ts | 6 +- 7 files changed, 211 insertions(+), 136 deletions(-) diff --git a/TODO.md b/TODO.md index 29fafe7..33fccff 100644 --- a/TODO.md +++ b/TODO.md @@ -4,9 +4,9 @@ [x] update code to adopt change files moving from AppState to App - Add "files" to legacy excalidraw export -[ ] PNG preview -[ ] markdown embed SVG 190 -[ ] markdown embed PNG +[x] PNG preview +[x] markdown embed SVG 190 +[x] markdown embed PNG [ ] embed Excalidraw into other Excalidraw diff --git a/src/ExcalidrawAutomate.ts b/src/ExcalidrawAutomate.ts index 92343c6..e463970 100644 --- a/src/ExcalidrawAutomate.ts +++ b/src/ExcalidrawAutomate.ts @@ -9,8 +9,8 @@ import { normalizePath, TFile } from "obsidian" -import ExcalidrawView from "./ExcalidrawView"; -import { getJSON, getSVGString } from "./ExcalidrawData"; +import ExcalidrawView, { TextMode } from "./ExcalidrawView"; +import { ExcalidrawData, getJSON, getSVGString } from "./ExcalidrawData"; import { FRONTMATTER, nanoid, @@ -18,7 +18,7 @@ import { VIEW_TYPE_EXCALIDRAW, MAX_IMAGE_SIZE } from "./constants"; -import { generateSVGString, getObsidianImage, getPNG, getSVG, wrapText } from "./Utils"; +import { embedFontsInSVG, generateSVGString, getObsidianImage, getPNG, getSVG, loadSceneFiles, svgToBase64, wrapText } from "./Utils"; import { AppState } from "@zsviczian/excalidraw/types/types"; declare type ConnectionPoint = "top"|"bottom"|"left"|"right"; @@ -283,7 +283,7 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) { } } ):Promise { - const template = params?.templatePath ? (await getTemplate(params.templatePath)) : null; + const template = params?.templatePath ? (await getTemplate(params.templatePath,true)) : null; let elements = template ? template.elements : []; elements = elements.concat(this.getElements()); let frontmatter:string; @@ -325,22 +325,24 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) { currentItemEndArrowhead: template?.appState?.currentItemEndArrowhead ?? this.style.endArrowHead, currentItemLinearStrokeSharpness: template?.appState?.currentItemLinearStrokeSharpness ?? this.style.strokeSharpness, gridSize: template?.appState?.gridSize ?? this.canvas.gridSize, - files: template?.appState?.files ?? {}, - } + }, + files: template?.files ?? {}, }; return plugin.createDrawing( params?.filename ? params.filename + '.excalidraw.md' : this.plugin.getNextDefaultFilename(), params?.onNewPane ? params.onNewPane : false, params?.foldername ? params.foldername : this.plugin.settings.folder, - frontmatter + await plugin.exportSceneToMD( - JSON.stringify(scene,null,"\t")) + this.plugin.settings.compatibilityMode + ? JSON.stringify(scene,null,"\t") + : frontmatter + await plugin.exportSceneToMD(JSON.stringify(scene,null,"\t")) ); }, async createSVG(templatePath?:string,embedFont:boolean = false):Promise { - const template = templatePath ? (await getTemplate(templatePath)) : null; + const automateElements = this.getElements(); + const template = templatePath ? (await getTemplate(templatePath,true)) : null; let elements = template ? template.elements : []; - elements = elements.concat(this.getElements()); + elements = elements.concat(automateElements); const svg = await getSVG( {//createDrawing type: "excalidraw", @@ -350,20 +352,21 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) { appState: { theme: template?.appState?.theme ?? this.canvas.theme, viewBackgroundColor: template?.appState?.viewBackgroundColor ?? this.canvas.viewBackgroundColor, - files: template?.appState?.files ?? {} - } + }, + files: template?.files ?? {} }, { withBackground: plugin.settings.exportWithBackground, withTheme: plugin.settings.exportWithTheme } ) - return embedFont ? ExcalidrawView.embedFontsInSVG(svg) : svg; + return embedFont ? embedFontsInSVG(svg) : svg; }, async createPNG(templatePath?:string, scale:number=1) { - const template = templatePath ? (await getTemplate(templatePath)) : null; + const automateElements = this.getElements(); + const template = templatePath ? (await getTemplate(templatePath,true)) : null; let elements = template ? template.elements : []; - elements = elements.concat(this.getElements()); + elements = elements.concat(automateElements); return getPNG( { type: "excalidraw", @@ -373,8 +376,8 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) { appState: { theme: template?.appState?.theme ?? this.canvas.theme, viewBackgroundColor: template?.appState?.viewBackgroundColor ?? this.canvas.viewBackgroundColor, - files: template?.appState?.files ?? {} - } + }, + files: template?.files ?? {} }, { withBackground: plugin.settings.exportWithBackground, @@ -824,21 +827,40 @@ async function getTemplate(fileWithPath:string, loadFiles:boolean = false):Promi const vault = app.vault; const file = app.metadataCache.getFirstLinkpathDest(normalizePath(fileWithPath),''); if(file && file instanceof TFile) { - const data = await vault.read(file); + const data = (await vault.read(file)).replaceAll("\r\n","\n").replaceAll("\r","\n"); + const excalidrawData:ExcalidrawData = new ExcalidrawData(window.ExcalidrawAutomate.plugin); + + if(file.extension === "excalidraw") { + await excalidrawData.loadLegacyData(data,file); + return { + elements: excalidrawData.scene.elements, + appState: excalidrawData.scene.appState, + frontmatter: "", + files: excalidrawData.scene.files, + svgSnapshot: null, + }; + } + + const parsed = data.search("excalidraw-plugin: parsed\n")>-1 || data.search("excalidraw-plugin: locked\n")>-1; //locked for backward compatibility + await excalidrawData.loadData(data,file,parsed ? TextMode.parsed : TextMode.raw) let trimLocation = data.search("# Text Elements\n"); if(trimLocation == -1) trimLocation = data.search("# Drawing\n"); - const [scene,pos] = getJSON(data); - const svgSnapshot = getSVGString(data.substr(pos+scene.length)); + if(loadFiles) { + await loadSceneFiles(app,excalidrawData.files,(fileArray:any)=>{ + for(const f of fileArray) { + excalidrawData.scene.files[f.id] = f; + } + }); + } - const excalidrawData = JSON_parse(scene); return { - elements: excalidrawData.elements, - appState: excalidrawData.appState, + elements: excalidrawData.scene.elements, + appState: excalidrawData.scene.appState, frontmatter: data.substring(0,trimLocation), - files: null, - svgSnapshot: svgSnapshot + files: excalidrawData.scene.files, + svgSnapshot: excalidrawData.svgSnapshot }; }; return { diff --git a/src/ExcalidrawData.ts b/src/ExcalidrawData.ts index c1b0dd9..42cf1cf 100644 --- a/src/ExcalidrawData.ts +++ b/src/ExcalidrawData.ts @@ -87,7 +87,7 @@ export function getSVGString(data:string):string { } export class ExcalidrawData { - public svgString: string = null; + public svgSnapshot: string = null; private textElements:Map = null; public scene:any = null; private file:TFile = null; @@ -99,6 +99,7 @@ export class ExcalidrawData { private plugin: ExcalidrawPlugin; public loaded: boolean = false; public files:Map = null; //fileId, path + private compatibilityMode:boolean = false; constructor(plugin: ExcalidrawPlugin) { this.plugin = plugin; @@ -116,6 +117,7 @@ export class ExcalidrawData { this.file = file; this.textElements = new Map(); this.files.clear(); + this.compatibilityMode = false; //I am storing these because if the settings change while a drawing is open parsing will run into errors during save //The drawing will use these values until next drawing is loaded or this drawing is re-loaded @@ -151,7 +153,7 @@ export class ExcalidrawData { this.scene.files = {}; //loading legacy scenes that do not yet have the files attribute. } - this.svgString = getSVGString(data.substr(pos+scene.length)); + this.svgSnapshot = getSVGString(data.substr(pos+scene.length)); data = data.substring(0,pos); @@ -203,6 +205,7 @@ export class ExcalidrawData { } public async loadLegacyData(data: string,file: TFile):Promise { + this.compatibilityMode = true; this.file = file; this.textElements = new Map(); this.setShowLinkBrackets(); @@ -485,7 +488,7 @@ export class ExcalidrawData { } outString += '\n'; } - return outString + this.plugin.getMarkdownDrawingSection(JSON.stringify(this.scene,null,"\t"),this.svgString); + return outString + this.plugin.getMarkdownDrawingSection(JSON.stringify(this.scene,null,"\t"),this.svgSnapshot); } private async syncFiles(scene:SceneDataWithFiles):Promise { @@ -523,10 +526,12 @@ export class ExcalidrawData { } public async syncElements(newScene:any):Promise { - //console.log("Excalidraw.Data.syncElements()"); - let result = await this.syncFiles(newScene); - this.scene = newScene;//JSON_parse(newScene); - this.scene.files = {}; + this.scene = newScene; + let result = false; + if(!this.compatibilityMode) { + result = await this.syncFiles(newScene); + this.scene.files = {}; + } result = result || this.setLinkPrefix() || this.setUrlPrefix() || this.setShowLinkBrackets(); await this.updateTextElementsFromScene(); return result || this.findNewTextElementsInScene(); diff --git a/src/ExcalidrawView.ts b/src/ExcalidrawView.ts index 9b7c1ca..0a7a660 100644 --- a/src/ExcalidrawView.ts +++ b/src/ExcalidrawView.ts @@ -36,7 +36,7 @@ import ExcalidrawPlugin from './main'; import {ExcalidrawAutomate, repositionElementsToCursor} from './ExcalidrawAutomate'; import { t } from "./lang/helpers"; import { ExcalidrawData, REG_LINKINDEX_HYPERLINK, REGEX_LINK } from "./ExcalidrawData"; -import { checkAndCreateFolder, download, generateSVGString, getNewOrAdjacentLeaf, getNewUniqueFilepath, getObsidianImage, getPNG, getSVG, loadSceneFiles, rotatedDimensions, splitFolderAndFilename, svgToBase64, viewportCoordsToSceneCoords } from "./Utils"; +import { checkAndCreateFolder, download, embedFontsInSVG, generateSVGString, getNewOrAdjacentLeaf, getNewUniqueFilepath, getObsidianImage, getPNG, getSVG, loadSceneFiles, rotatedDimensions, splitFolderAndFilename, svgToBase64, viewportCoordsToSceneCoords } from "./Utils"; import { Prompt } from "./Prompt"; import { ClipboardData } from "@zsviczian/excalidraw/types/clipboard"; @@ -63,6 +63,7 @@ export default class ExcalidrawView extends TextFileView { private getScene: Function = null; public addElements: Function = null; //add elements to the active Excalidraw drawing private getSelectedTextElement: Function = null; + private getSelectedImageElement: Function = null; public addText:Function = null; private refresh: Function = null; public excalidrawRef: React.MutableRefObject = null; @@ -117,23 +118,12 @@ export default class ExcalidrawView extends TextFileView { const svg = await getSVG(scene,exportSettings); if(!svg) return; let serializer =new XMLSerializer(); - const svgString = serializer.serializeToString(ExcalidrawView.embedFontsInSVG(svg)); + const svgString = serializer.serializeToString(embedFontsInSVG(svg)); if(file && file instanceof TFile) await this.app.vault.modify(file,svgString); else await this.app.vault.create(filepath,svgString); })(); } - public static embedFontsInSVG(svg:SVGSVGElement):SVGSVGElement { - //replace font references with base64 fonts - const includesVirgil = svg.querySelector("text[font-family^='Virgil']") != null; - const includesCascadia = svg.querySelector("text[font-family^='Cascadia']") != null; - const defs = svg.querySelector("defs"); - if (defs && (includesCascadia || includesVirgil)) { - defs.innerHTML = ""; - } - return svg; - } - public savePNG(scene?: any) { if(!scene) { if (!this.getScene) return false; @@ -168,7 +158,7 @@ export default class ExcalidrawView extends TextFileView { await this.loadDrawing(false); } //generate SVG preview snapshot - this.excalidrawData.svgString = await generateSVGString(this.getScene(),this.plugin.settings); + this.excalidrawData.svgSnapshot = await generateSVGString(this.getScene(),this.plugin.settings); } await super.save(); } @@ -180,12 +170,12 @@ export default class ExcalidrawView extends TextFileView { //console.log("ExcalidrawView.getViewData()"); if(!this.getScene) return this.data; if(!this.excalidrawData.loaded) return this.data; + const scene = this.getScene(); if(!this.compatibilityMode) { let trimLocation = this.data.search(/(^%%\n)?# Text Elements\n/m); if(trimLocation == -1) trimLocation = this.data.search(/(%%\n)?# Drawing\n/); if(trimLocation == -1) return this.data; - const scene = this.excalidrawData.scene; if(!this.autosaving) { if(this.plugin.settings.autoexportSVG) this.saveSVG(scene); if(this.plugin.settings.autoexportPNG) this.savePNG(scene); @@ -197,7 +187,6 @@ export default class ExcalidrawView extends TextFileView { return header + this.excalidrawData.generateMD(); } if(this.compatibilityMode) { - const scene = this.excalidrawData.scene; if(!this.autosaving) { if(this.plugin.settings.autoexportSVG) this.saveSVG(scene); if(this.plugin.settings.autoexportPNG) this.savePNG(scene); @@ -208,62 +197,79 @@ export default class ExcalidrawView extends TextFileView { } async handleLinkClick(view: ExcalidrawView, ev:MouseEvent) { - let text:string = (this.textMode == TextMode.parsed) - ? this.excalidrawData.getRawText(this.getSelectedTextElement().id) - : this.getSelectedTextElement().text; - if(!text) { + const selectedText = this.getSelectedTextElement(); + let file = null; + let lineNum = 0; + let linkText:string = null; + + if(selectedText?.id) { + linkText = (this.textMode == TextMode.parsed) + ? this.excalidrawData.getRawText(selectedText.id) + : selectedText.text; + + linkText = linkText.replaceAll("\n",""); //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/187 + if(linkText.match(REG_LINKINDEX_HYPERLINK)) { + window.open(linkText,"_blank"); + return; + } + + const parts = REGEX_LINK.getRes(linkText).next(); + if(!parts.value) { + const tags = linkText.matchAll(/#([\p{Letter}\p{Emoji_Presentation}\p{Number}\/_-]+)/ug).next(); + if(!tags.value || tags.value.length<2) { + new Notice(t("TEXT_ELEMENT_EMPTY"),4000); + return; + } + const search=this.app.workspace.getLeavesOfType("search"); + if(search.length==0) return; + //@ts-ignore + search[0].view.setQuery("tag:"+tags.value[1]); + this.app.workspace.revealLeaf(search[0]); + + if(document.fullscreenElement === this.contentEl) { + document.exitFullscreen(); + this.zoomToFit(); + } + return; + } + + linkText = REGEX_LINK.getLink(parts); + + if(linkText.match(REG_LINKINDEX_HYPERLINK)) { + window.open(linkText,"_blank"); + return; + } + + if(linkText.search("#")>-1) { + let t; + [t,lineNum] = await this.excalidrawData.getTransclusion(linkText); + linkText = linkText.substring(0,linkText.search("#")); + } + if(linkText.match(REG_LINKINDEX_INVALIDCHARS)) { + new Notice(t("FILENAME_INVALID_CHARS"),4000); + return; + } + file = view.app.metadataCache.getFirstLinkpathDest(linkText,view.file.path); + if (!ev.altKey && !file) { + new Notice(t("FILE_DOES_NOT_EXIST"), 4000); + return; + } + } else { + const selectedImage = this.getSelectedImageElement(); + if(selectedImage?.id) { + await this.save(true); //in case pasted images haven't been saved yet + if(this.excalidrawData.files.has(selectedImage.fileId)) { + linkText = this.excalidrawData.files.get(selectedImage.fileId); + } + } + } + + if(!linkText) { new Notice(t("LINK_BUTTON_CLICK_NO_TEXT"),20000); return; } - text = text.replaceAll("\n",""); //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/187 - if(text.match(REG_LINKINDEX_HYPERLINK)) { - window.open(text,"_blank"); - return; - } - const parts = REGEX_LINK.getRes(text).next(); - if(!parts.value) { - const tags = text.matchAll(/#([\p{Letter}\p{Emoji_Presentation}\p{Number}\/_-]+)/ug).next(); - if(!tags.value || tags.value.length<2) { - new Notice(t("TEXT_ELEMENT_EMPTY"),4000); - return; - } - const search=this.app.workspace.getLeavesOfType("search"); - if(search.length==0) return; - //@ts-ignore - search[0].view.setQuery("tag:"+tags.value[1]); - this.app.workspace.revealLeaf(search[0]); - if(document.fullscreenElement === this.contentEl) { - document.exitFullscreen(); - this.zoomToFit(); - } - return; - } - - text = REGEX_LINK.getLink(parts); - - if(text.match(REG_LINKINDEX_HYPERLINK)) { - window.open(text,"_blank"); - return; - } - - let lineNum = null; - if(text.search("#")>-1) { - let t; - [t,lineNum] = await this.excalidrawData.getTransclusion(text); - text = text.substring(0,text.search("#")); - } - if(text.match(REG_LINKINDEX_INVALIDCHARS)) { - new Notice(t("FILENAME_INVALID_CHARS"),4000); - return; - } - const file = view.app.metadataCache.getFirstLinkpathDest(text,view.file.path); - if (!ev.altKey && !file) { - new Notice(t("FILE_DOES_NOT_EXIST"), 4000); - return; - } - try { const f = view.file; if(ev.shiftKey && document.fullscreenElement === this.contentEl) { @@ -275,7 +281,7 @@ export default class ExcalidrawView extends TextFileView { if(file) { leaf.openFile(file,{eState: {line: lineNum-1}}); //if file exists open file and jump to reference } else { - leaf.view.app.workspace.openLinkText(text,view.file.path); + leaf.view.app.workspace.openLinkText(linkText,view.file.path); } } catch (e) { new Notice(e,4000); @@ -362,6 +368,10 @@ export default class ExcalidrawView extends TextFileView { this.preventReload = false; return; } + if(this.compatibilityMode) { + this.dirty = null; + return; + } if(!this.excalidrawRef) return; if(!this.file) return; if(file) this.data = await this.app.vault.cachedRead(file); @@ -383,7 +393,7 @@ export default class ExcalidrawView extends TextFileView { data = this.data = data.replaceAll("\r\n","\n").replaceAll("\r","\n"); this.app.workspace.onLayoutReady(async ()=>{ this.dirty = null; - this.compatibilityMode = this.file.extension == "excalidraw"; + this.compatibilityMode = this.file.extension === "excalidraw"; await this.plugin.loadSettings(); this.plugin.opencount++; if(this.compatibilityMode) { @@ -430,6 +440,7 @@ export default class ExcalidrawView extends TextFileView { viewModeEnabled: viewModeEnabled, ... excalidrawData.appState, }, + files: excalidrawData.files, commitToHistory: true, }); if((this.app.workspace.activeLeaf === this.leaf) && this.excalidrawWrapperRef) { @@ -440,30 +451,11 @@ export default class ExcalidrawView extends TextFileView { this.instantiateExcalidraw({ elements: excalidrawData.elements, appState: excalidrawData.appState, + files: excalidrawData.files, libraryItems: await this.getLibrary(), }); //files are loaded on excalidrawRef readyPromise - } - - /* - //load files - this.excalidrawData.files.forEach((value,key)=> { - const file = this.app.vault.getAbstractFileByPath(value); - if(file && file instanceof TFile) { - getObsidianImage(this.plugin.app,file).then(async (data)=>{ - if(!this.excalidrawData) return; - let files:BinaryFileData[] = []; - files.push({ - mimeType : data.mimeType, - id: key as FileId, - dataURL: data.dataURL, - created: data.created - }); - this.excalidrawAPI.addFiles(files); - }); - } - });*/ - + } } //Compatibility mode with .excalidraw files @@ -583,7 +575,7 @@ export default class ExcalidrawView extends TextFileView { } let svg = await getSVG(this.getScene(),exportSettings); if(!svg) return null; - svg = ExcalidrawView.embedFontsInSVG(svg); + svg = embedFontsInSVG(svg); download(null,svgToBase64(svg.outerHTML),this.file.basename+'.svg'); return; } @@ -671,7 +663,7 @@ export default class ExcalidrawView extends TextFileView { if(this.excalidrawAPI.getAppState().viewModeEnabled) { if(selectedTextElement) { const retval = selectedTextElement; - selectedTextElement == null; + selectedTextElement = null; return retval; } return {id:null,text:null}; @@ -690,6 +682,30 @@ export default class ExcalidrawView extends TextFileView { return {id:selectedElement[0].id, text:selectedElement[0].text}; //return text element text }; + this.getSelectedImageElement = ():{id: string, fileId:string} => { + if(!excalidrawRef?.current) return {id:null,fileId:null}; + if(this.excalidrawAPI.getAppState().viewModeEnabled) { + if(selectedImageElement) { + const retval = selectedImageElement; + selectedImageElement = null; + return retval; + } + return {id:null,fileId:null}; + } + const selectedElement = this.excalidrawAPI.getSceneElements().filter((el:any)=>el.id==Object.keys(this.excalidrawAPI.getAppState().selectedElementIds)[0]); + if(selectedElement.length===0) return {id:null,fileId:null}; + if(selectedElement[0].type == "image") return {id:selectedElement[0].id, fileId:selectedElement[0].fileId}; //an image element was selected. Return fileId + if(selectedElement[0].groupIds.length === 0) return {id:null,fileId:null}; //is the selected element part of a group? + const group = selectedElement[0].groupIds[0]; //if yes, take the first group it is part of + const imageElement = this + .excalidrawAPI + .getSceneElements() + .filter((el:any)=>el.groupIds?.includes(group)) + .filter((el:any)=>el.type=="image"); //filter for Image elements of the group + if(imageElement.length===0) return {id:null,fileId:null}; //the group had no image element member + return {id:selectedElement[0].id, fileId:selectedElement[0].fileId}; //return image element fileId + }; + this.addText = (text:string, fontFamily?:1|2|3) => { if(!excalidrawRef?.current) { return; @@ -793,6 +809,7 @@ export default class ExcalidrawView extends TextFileView { //variables used to handle click events in view mode let selectedTextElement:{id:string,text:string} = null; + let selectedImageElement:{id:string,fileId:string} = null; let timestamp = 0; let blockOnMouseButtonDown = false; @@ -822,6 +839,19 @@ export default class ExcalidrawView extends TextFileView { //if there are still multiple text elements with links on top of each other, return the first return {id:elementsWithLinks[0].id,text:elementsWithLinks[0].text}; } + + const getImageElementAtPointer = (pointer:any) => { + const elements = this.excalidrawAPI.getSceneElements() + .filter((e:ExcalidrawElement)=>{ + if (e.type !== "image") return false; + const [x,y,w,h] = rotatedDimensions(e); + return x<=pointer.x && x+w>=pointer.x + && y<=pointer.y && y+h>=pointer.y; + }); + if(elements.length==0) return null; + if(elements.length>1) return {id:elements[0].id,fileId:elements[0].fileId}; + //if more than 1 image elements are at the location, return the first + } let hoverPoint = {x:0,y:0}; let hoverPreviewTarget:EventTarget = null; @@ -859,7 +889,13 @@ export default class ExcalidrawView extends TextFileView { const event = new MouseEvent("click", {ctrlKey: true, shiftKey: this.shiftKeyDown, altKey:this.altKeyDown}); this.handleLinkClick(this,event); selectedTextElement = null; - } + } + selectedImageElement = getImageElementAtPointer(currentPosition); + if(selectedImageElement) { + const event = new MouseEvent("click", {ctrlKey: true, shiftKey: this.shiftKeyDown, altKey:this.altKeyDown}); + this.handleLinkClick(this,event); + selectedImageElement = null; + } } let mouseEvent:any = null; @@ -930,7 +966,7 @@ export default class ExcalidrawView extends TextFileView { //@ts-ignore if(!(e.ctrlKey||e.metaKey)) return; if(!(this.plugin.settings.allowCtrlClick)) return; - if(!this.getSelectedTextElement().id) return; + if(!(this.getSelectedTextElement().id || this.getSelectedImageElement().id)) return; this.handleLinkClick(this,e); }, onMouseMove: (e:MouseEvent) => { diff --git a/src/Utils.ts b/src/Utils.ts index 0941f9e..262c9c2 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -3,7 +3,7 @@ import { App, normalizePath, TAbstractFile, TFile, TFolder, Vault, WorkspaceLea import { Random } from "roughjs/bin/math"; import { BinaryFileData, DataURL, Zoom } from "@zsviczian/excalidraw/types/types"; import { nanoid } from "nanoid"; -import { IMAGE_TYPES } from "./constants"; +import { CASCADIA_FONT, IMAGE_TYPES, VIRGIL_FONT } from "./constants"; import {ExcalidrawAutomate} from './ExcalidrawAutomate'; import ExcalidrawPlugin from "./main"; import { ExcalidrawElement, FileId } from "@zsviczian/excalidraw/types/element/types"; @@ -352,6 +352,18 @@ export const getPNG = async (scene:any, exportSettings:ExportSettings, scale:num } } +export const embedFontsInSVG = (svg:SVGSVGElement):SVGSVGElement => { + //replace font references with base64 fonts + const includesVirgil = svg.querySelector("text[font-family^='Virgil']") != null; + const includesCascadia = svg.querySelector("text[font-family^='Cascadia']") != null; + const defs = svg.querySelector("defs"); + if (defs && (includesCascadia || includesVirgil)) { + defs.innerHTML = ""; + } + return svg; +} + + export const loadSceneFiles = async (app:App, filesMap: Map,addFiles:Function) => { const entries = filesMap.entries(); let entry; diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts index 867e571..3958de5 100644 --- a/src/lang/locale/en.ts +++ b/src/lang/locale/en.ts @@ -32,10 +32,10 @@ export default { SAVE_AS_SVG: "Save as SVG into Vault (CTRL/META+CLICK to export)", OPEN_LINK: "Open selected text as link\n(SHIFT+CLICK to open in a new pane)", EXPORT_EXCALIDRAW: "Export to an .Excalidraw file", - LINK_BUTTON_CLICK_NO_TEXT: 'Select a Text Element containing an internal or external link.\n'+ + LINK_BUTTON_CLICK_NO_TEXT: 'Select a an ImageElement, or select a TextElement that contains an internal or external link.\n'+ 'SHIFT CLICK this button to open the link in a new pane.\n'+ - 'CTRL/META CLICK the Text Element on the canvas has the same effect!', - TEXT_ELEMENT_EMPTY: "Text Element is empty, or [[valid-link|alias]] or [alias](valid-link) is not found", + 'CTRL/META CLICK the Image or TextElement on the canvas has the same effect!', + TEXT_ELEMENT_EMPTY: "No ImageElement is selected or TextElement is empty, or [[valid-link|alias]] or [alias](valid-link) is not found", FILENAME_INVALID_CHARS: 'File name cannot contain any of the following characters: * " \\  < > : | ?', FILE_DOES_NOT_EXIST: "File does not exist. Hold down ALT (or ALT+SHIFT) and CLICK link button to create a new file.", FORCE_SAVE: "Force-save to update transclusions in adjacent panes.\n(Please note, that autosave is always on)", diff --git a/src/main.ts b/src/main.ts index 0b01117..ff4c69f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -56,7 +56,7 @@ import { Prompt } from "./Prompt"; import { around } from "monkey-around"; import { t } from "./lang/helpers"; import { MigrationPrompt } from "./MigrationPrompt"; -import { checkAndCreateFolder, download, generateSVGString, getAttachmentsFolderAndFilePath, getIMGPathFromExcalidrawFile, getNewUniqueFilepath, getPNG, getSVG, splitFolderAndFilename, svgToBase64 } from "./Utils"; +import { checkAndCreateFolder, download, embedFontsInSVG, generateSVGString, getAttachmentsFolderAndFilePath, getIMGPathFromExcalidrawFile, getNewUniqueFilepath, getPNG, getSVG, splitFolderAndFilename, svgToBase64 } from "./Utils"; declare module "obsidian" { interface App { @@ -250,7 +250,7 @@ export default class ExcalidrawPlugin extends Plugin { svg = await getSVG(JSON_parse(scene),exportSettings); } if(!svg) return null; - svg = ExcalidrawView.embedFontsInSVG(svg); + svg = embedFontsInSVG(svg); svg.removeAttribute('width'); svg.removeAttribute('height'); img.setAttribute("src",svgToBase64(svg.outerHTML)); @@ -789,7 +789,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 + this.exportSceneToMD(data)); + const result = await this.app.vault.create(fname,FRONTMATTER + await this.exportSceneToMD(data)); if (this.settings.keepInSync) { ['.svg','.png'].forEach( (ext:string)=>{ const oldIMGpath = file.path.substring(0,file.path.lastIndexOf(".excalidraw")) + ext;