From 024f7979a77a8573979562d131c154080bdbef02 Mon Sep 17 00:00:00 2001 From: Zsolt Viczian Date: Sun, 9 Oct 2022 19:57:46 +0200 Subject: [PATCH] 1.7.23 --- manifest.json | 2 +- package.json | 2 +- src/EmbeddedFileLoader.ts | 1322 ++++++++++++++++++------------------- src/ExcalidrawAutomate.ts | 33 +- src/dialogs/Messages.ts | 17 + src/lang/locale/en.ts | 1 + src/main.ts | 38 +- src/types.d.ts | 3 +- 8 files changed, 750 insertions(+), 668 deletions(-) diff --git a/manifest.json b/manifest.json index 062fa98..05f9f9c 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-excalidraw-plugin", "name": "Excalidraw", - "version": "1.7.22", + "version": "1.7.23", "minAppVersion": "0.15.6", "description": "An Obsidian plugin to edit and view Excalidraw drawings", "author": "Zsolt Viczian", diff --git a/package.json b/package.json index 4bf445b..563486c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-excalidraw-plugin", - "version": "1.7.11", + "version": "1.7.23", "description": "This is an Obsidian.md plugin that lets you view and edit Excalidraw drawings", "main": "lib/index.js", "types": "lib/index.d.ts", diff --git a/src/EmbeddedFileLoader.ts b/src/EmbeddedFileLoader.ts index 0a0aca8..6778f10 100644 --- a/src/EmbeddedFileLoader.ts +++ b/src/EmbeddedFileLoader.ts @@ -1,661 +1,661 @@ -import { FileId } from "@zsviczian/excalidraw/types/element/types"; -import { BinaryFileData, DataURL } from "@zsviczian/excalidraw/types/types"; -import { App, MarkdownRenderer, Notice, TFile } from "obsidian"; -import { - CASCADIA_FONT, - DEFAULT_MD_EMBED_CSS, - fileid, - FRONTMATTER_KEY_BORDERCOLOR, - FRONTMATTER_KEY_FONT, - FRONTMATTER_KEY_FONTCOLOR, - FRONTMATTER_KEY_MD_STYLE, - IMAGE_TYPES, - nanoid, - VIRGIL_FONT, -} from "./Constants"; -import { createSVG } from "./ExcalidrawAutomate"; -import { ExcalidrawData, getTransclusion } from "./ExcalidrawData"; -import { ExportSettings } from "./ExcalidrawView"; -import { t } from "./lang/helpers"; -import { tex2dataURL } from "./LaTeX"; -import ExcalidrawPlugin from "./main"; -import { - errorlog, - getDataURL, - getExportTheme, - getFontDataURL, - getImageSize, - getLinkParts, - getExportPadding, - getWithBackground, - hasExportBackground, - hasExportTheme, - LinkParts, - svgToBase64, -} from "./utils/Utils"; - -const THEME_FILTER = "invert(100%) hue-rotate(180deg) saturate(1.25)"; - -export declare type MimeType = - | "image/svg+xml" - | "image/png" - | "image/jpeg" - | "image/gif" - | "image/webp" - | "image/bmp" - | "image/x-icon" - | "application/octet-stream"; -export type FileData = BinaryFileData & { - size: Size; - hasSVGwithBitmap: boolean; -}; - -export type Size = { - height: number; - width: number; -}; - -export class EmbeddedFile { - public file: TFile = null; - public isSVGwithBitmap: boolean = false; - private img: string = ""; //base64 - private imgInverted: string = ""; //base64 - public mtime: number = 0; //modified time of the image - private plugin: ExcalidrawPlugin; - public mimeType: MimeType = "application/octet-stream"; - public size: Size = { height: 0, width: 0 }; - public linkParts: LinkParts; - private hostPath: string; - public attemptCounter: number = 0; - /*public isHyperlink: boolean = false;*/ - - constructor(plugin: ExcalidrawPlugin, hostPath: string, imgPath: string) { - this.plugin = plugin; - this.resetImage(hostPath, imgPath); - } - - public resetImage(hostPath: string, imgPath: string) { - /*if(imgPath.startsWith("https://") || imgPath.startsWith("http://")) { - this.img=imgPath; - this.imgInverted=imgPath; - this.isHyperlink = true; - return; - }*/ - this.imgInverted = this.img = ""; - this.mtime = 0; - this.linkParts = getLinkParts(imgPath); - this.hostPath = hostPath; - 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 = app.metadataCache.getFirstLinkpathDest( - this.linkParts.path, - hostPath, - ); - if (!this.file) { - if(this.attemptCounter++ === 0) { - new Notice( - `Excalidraw Warning: could not find image file: ${imgPath}`, - 5000, - ); - } - } - } - - private fileChanged(): boolean { - if (!this.file) { - this.file = app.metadataCache.getFirstLinkpathDest( - this.linkParts.path, - this.hostPath, - ); // maybe the file has synchronized in the mean time - if(!this.file) { - this.attemptCounter++; - return false; - } - } - return this.mtime != this.file.stat.mtime; - } - - public 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; - this.mimeType = mimeType; - switch (isDark && isSVGwithBitmap) { - case true: - this.imgInverted = imgBase64; - break; - case false: - this.img = imgBase64; - break; //bitmaps and SVGs without an embedded bitmap do not need a negative image - } - this.isSVGwithBitmap = isSVGwithBitmap; - } - - public isLoaded(isDark: boolean): boolean { - if (!this.file) { - this.file = app.metadataCache.getFirstLinkpathDest( - this.linkParts.path, - this.hostPath, - ); // maybe the file has synchronized in the mean time - if(!this.file) { - this.attemptCounter++; - return true; - } - } - if (this.fileChanged()) { - return false; - } - if (this.isSVGwithBitmap && isDark) { - return this.imgInverted !== ""; - } - return this.img !== ""; - } - - public getImage(isDark: boolean) { - /*if(this.isHyperlink) { - return this.img; - }*/ - 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 === - } -} - -export class EmbeddedFilesLoader { - private plugin: ExcalidrawPlugin; - 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(inFile: TFile | EmbeddedFile, depth: number): Promise<{ - mimeType: MimeType; - fileId: FileId; - dataURL: DataURL; - created: number; - hasSVGwithBitmap: boolean; - size: { height: number; width: number }; - }> { - 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, - }; - - let hasSVGwithBitmap = false; - const isExcalidrawFile = this.plugin.isExcalidrawFile(file); - if ( - !( - IMAGE_TYPES.contains(file.extension) || - isExcalidrawFile || - file.extension === "md" - ) - ) { - return null; - } - const ab = await app.vault.readBinary(file); - - const getExcalidrawSVG = async (isDark: boolean) => { - //debug({where:"EmbeddedFileLoader.getExcalidrawSVG",uid:this.uid,file:file.name}); - const forceTheme = hasExportTheme(this.plugin, file) - ? getExportTheme(this.plugin, file, "light") - : undefined; - const exportSettings: ExportSettings = { - withBackground: hasExportBackground(this.plugin, file) - ? getWithBackground(this.plugin, file) - : false, - withTheme: !!forceTheme, - }; - const svg = await createSVG( - file.path, - true, - exportSettings, - this, - forceTheme, - null, - null, - [], - this.plugin, - depth+1, - getExportPadding(this.plugin, file), - ); - //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; - } - if (hasSVGwithBitmap && isDark) { - imageList.forEach((i) => { - const id = i.parentElement?.id; - svg.querySelectorAll(`use[href='#${id}']`).forEach((u) => { - u.setAttribute("filter", THEME_FILTER); - }); - }); - } - if (!hasSVGwithBitmap && svg.getAttribute("hasbitmap")) { - hasSVGwithBitmap = true; - } - const dURL = svgToBase64(svg.outerHTML) as DataURL; - return dURL as DataURL; - }; - - const excalidrawSVG = isExcalidrawFile - ? await getExcalidrawSVG(this.isDark) - : null; - let mimeType: MimeType = "image/svg+xml"; - if (!isExcalidrawFile) { - switch (file.extension) { - case "png": - mimeType = "image/png"; - break; - case "jpeg": - mimeType = "image/jpeg"; - break; - case "jpg": - mimeType = "image/jpeg"; - break; - case "gif": - mimeType = "image/gif"; - break; - case "webp": - mimeType = "image/webp"; - break; - case "bmp": - mimeType = "image/bmp"; - break; - case "ico": - mimeType = "image/x-icon" - break; - case "svg": - case "md": - mimeType = "image/svg+xml"; - break; - default: - mimeType = "application/octet-stream"; - } - } - let dataURL = - excalidrawSVG ?? - (file.extension === "svg" - ? await getSVGData(app, file) - : file.extension === "md" - ? null - : await getDataURL(ab, mimeType)); - - if(!dataURL) { - const result = await this.convertMarkdownToSVG(this.plugin, file, linkParts); - dataURL = result.dataURL; - hasSVGwithBitmap = result.hasSVGwithBitmap; - } - const size = await getImageSize(dataURL); - return { - mimeType, - fileId: await generateIdFromFile(ab), - dataURL, - created: file.stat.mtime, - hasSVGwithBitmap, - size, - }; - } - - public async loadSceneFiles( - excalidrawData: ExcalidrawData, - addFiles: Function, - depth:number - ) { - if(depth > 4) { - new Notice(t("INFINITE_LOOP_WARNING")+depth.toString(), 6000); - return; - } - 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"; - } - let entry; - const files: FileData[] = []; - while (!this.terminate && !(entry = entries.next()).done) { - const embeddedFile: EmbeddedFile = entry.value[1]; - if (!embeddedFile.isLoaded(this.isDark)) { - //debug({where:"EmbeddedFileLoader.loadSceneFiles",uid:this.uid,status:"embedded Files are not loaded"}); - const data = await this.getObsidianImage(embeddedFile, depth); - if (data) { - files.push({ - mimeType: data.mimeType, - id: entry.value[0], - dataURL: data.dataURL, - created: data.created, - size: data.size, - hasSVGwithBitmap: data.hasSVGwithBitmap, - }); - } - } else if (embeddedFile.isSVGwithBitmap) { - files.push({ - mimeType: embeddedFile.mimeType, - id: entry.value[0], - dataURL: embeddedFile.getImage(this.isDark) as DataURL, - created: embeddedFile.mtime, - size: embeddedFile.size, - hasSVGwithBitmap: embeddedFile.isSVGwithBitmap, - }); - } - } - - let equation; - const equations = excalidrawData.getEquationEntries(); - while (!this.terminate && !(equation = equations.next()).done) { - if (!excalidrawData.getEquation(equation.value[0]).isLoaded) { - const latex = equation.value[1].latex; - const data = await tex2dataURL(latex, this.plugin); - if (data) { - files.push({ - mimeType: data.mimeType, - id: equation.value[0], - dataURL: data.dataURL, - created: data.created, - size: data.size, - hasSVGwithBitmap: false, - }); - } - } - } - - 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, this.isDark); - } catch (e) { - errorlog({ where: "EmbeddedFileLoader.loadSceneFiles", error: e }); - } - } - - private async convertMarkdownToSVG( - plugin: ExcalidrawPlugin, - file: TFile, - linkParts: LinkParts, - ): Promise<{dataURL: DataURL, hasSVGwithBitmap:boolean}> { - //1. - //get the markdown text - let hasSVGwithBitmap = false; - const transclusion = await getTransclusion(linkParts, plugin.app, file); - let text = (transclusion.leadingHashes??"") + transclusion.contents; - if (text === "") { - text = - "# Empty markdown file\nCTRL+Click here to open the file for editing in the current active pane, or CTRL+SHIFT+Click to open it in an adjacent pane."; - } - - //2. - //get styles - const fileCache = plugin.app.metadataCache.getFileCache(file); - let fontDef: string; - let fontName = plugin.settings.mdFont; - if ( - fileCache?.frontmatter && - Boolean(fileCache.frontmatter[FRONTMATTER_KEY_FONT]) - ) { - fontName = fileCache.frontmatter[FRONTMATTER_KEY_FONT]; - } - switch (fontName) { - case "Virgil": - fontDef = VIRGIL_FONT; - break; - case "Cascadia": - fontDef = CASCADIA_FONT; - break; - case "": - fontDef = ""; - break; - default: - const font = await getFontDataURL(plugin.app, fontName, file.path); - fontDef = font.fontDef; - fontName = font.fontName; - } - - if ( - fileCache?.frontmatter && - fileCache.frontmatter["banner"] !== null - ) { - text = text.replace(/banner:\s*.*/,""); //patch https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/814 - } - - const fontColor = fileCache?.frontmatter - ? fileCache.frontmatter[FRONTMATTER_KEY_FONTCOLOR] ?? - plugin.settings.mdFontColor - : plugin.settings.mdFontColor; - - let style = fileCache?.frontmatter - ? fileCache.frontmatter[FRONTMATTER_KEY_MD_STYLE] ?? "" - : ""; - let frontmatterCSSisAfile = false; - if (style && style != "") { - const f = plugin.app.metadataCache.getFirstLinkpathDest(style, file.path); - if (f) { - style = await plugin.app.vault.read(f); - frontmatterCSSisAfile = true; - } - } - if (!frontmatterCSSisAfile) { - if (plugin.settings.mdCSS && plugin.settings.mdCSS !== "") { - const f = plugin.app.metadataCache.getFirstLinkpathDest( - plugin.settings.mdCSS, - file.path, - ); - style += f ? `\n${await plugin.app.vault.read(f)}` : DEFAULT_MD_EMBED_CSS; - } else { - style += DEFAULT_MD_EMBED_CSS; - } - } - - const borderColor = fileCache?.frontmatter - ? fileCache.frontmatter[FRONTMATTER_KEY_BORDERCOLOR] ?? - plugin.settings.mdBorderColor - : plugin.settings.mdBorderColor; - - if (borderColor && borderColor !== "" && !style.match(/svg/i)) { - style += `svg{border:2px solid;color:${borderColor};transform:scale(.95)}`; - } - - //3. - //SVG helper functions - //the SVG will first have ~infinite height. After sizing this will be reduced - let svgStyle = ` width="${linkParts.width}px" height="100000"`; - let foreignObjectStyle = ` width="${linkParts.width}px" height="100%"`; - - const svg = (xml: string, xmlFooter: string, style?: string) => - `${ - style ? `` : "" - }${xml}${ - xmlFooter //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/286#issuecomment-982179639 - }${ - fontDef !== "" ? `` : "" - }`; - - //4. - //create document div - this will be the contents of the foreign object - const mdDIV = createDiv(); - mdDIV.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); - mdDIV.setAttribute("class", "excalidraw-md-host"); - // mdDIV.setAttribute("style",style); - if (fontName !== "") { - mdDIV.style.fontFamily = fontName; - } - mdDIV.style.overflow = "auto"; - mdDIV.style.display = "block"; - mdDIV.style.color = fontColor && fontColor !== "" ? fontColor : "initial"; - - await MarkdownRenderer.renderMarkdown(text, mdDIV, file.path, plugin); - - mdDIV - .querySelectorAll(":scope > *[class^='frontmatter']") - .forEach((el) => mdDIV.removeChild(el)); - - const internalEmbeds = Array.from(mdDIV.querySelectorAll("span[class='internal-embed']")) - for(let i=0;i { - const elementStyle = el.style; - const computedStyle = window.getComputedStyle(el); - let style = ""; - for (const prop in elementStyle) { - if (elementStyle.hasOwnProperty(prop)) { - style += `${prop}: ${computedStyle[prop]};`; - } - } - el.setAttribute("style", style); - }); - - const xmlINiframe = new XMLSerializer().serializeToString(stylingDIV); - const xmlFooter = new XMLSerializer().serializeToString(footerDIV); - document.body.removeChild(iframeHost); - - //5.2 - //get SVG size - const parser = new DOMParser(); - const doc = parser.parseFromString( - svg(xmlINiframe, xmlFooter), - "image/svg+xml", - ); - const svgEl = doc.firstElementChild; - const host = createDiv(); - host.appendChild(svgEl); - document.body.appendChild(host); - const footerHeight = svgEl.querySelector( - ".excalidraw-md-footer", - ).scrollHeight; - const height = - svgEl.querySelector(".excalidraw-md-host").scrollHeight + footerHeight; - 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"`; - mdDIV.style.height = `${svgHeight - footerHeight}px`; - mdDIV.style.overflow = "hidden"; - - const imageList = mdDIV.querySelectorAll( - "img:not([src^='data:image/svg+xml'])", - ); - if (imageList.length > 0) { - hasSVGwithBitmap = true; - } - if (hasSVGwithBitmap && this.isDark) { - imageList.forEach(img => { - if(img instanceof HTMLImageElement) { - img.style.filter = THEME_FILTER; - } - }); - } - - const xml = new XMLSerializer().serializeToString(mdDIV); - const finalSVG = svg(xml, '', style); - plugin.ea.mostRecentMarkdownSVG = parser.parseFromString( - finalSVG, - "image/svg+xml", - ).firstElementChild as SVGSVGElement; - return { - dataURL: svgToBase64(finalSVG) as DataURL, - hasSVGwithBitmap - }; - }; -} - -const getSVGData = async (app: App, file: TFile): Promise => { - const svg = await app.vault.read(file); - return svgToBase64(svg) as DataURL; -}; - -const generateIdFromFile = async (file: ArrayBuffer): Promise => { - let id: FileId; - try { - const hashBuffer = await window.crypto.subtle.digest("SHA-1", file); - id = - // convert buffer to byte array - Array.from(new Uint8Array(hashBuffer)) - // convert to hex string - .map((byte) => byte.toString(16).padStart(2, "0")) - .join("") as FileId; - } catch (error) { - errorlog({ where: "EmbeddedFileLoader.generateIdFromFile", error }); - id = fileid() as FileId; - } - return id; -}; +import { FileId } from "@zsviczian/excalidraw/types/element/types"; +import { BinaryFileData, DataURL } from "@zsviczian/excalidraw/types/types"; +import { App, MarkdownRenderer, Notice, TFile } from "obsidian"; +import { + CASCADIA_FONT, + DEFAULT_MD_EMBED_CSS, + fileid, + FRONTMATTER_KEY_BORDERCOLOR, + FRONTMATTER_KEY_FONT, + FRONTMATTER_KEY_FONTCOLOR, + FRONTMATTER_KEY_MD_STYLE, + IMAGE_TYPES, + nanoid, + VIRGIL_FONT, +} from "./Constants"; +import { createSVG } from "./ExcalidrawAutomate"; +import { ExcalidrawData, getTransclusion } from "./ExcalidrawData"; +import { ExportSettings } from "./ExcalidrawView"; +import { t } from "./lang/helpers"; +import { tex2dataURL } from "./LaTeX"; +import ExcalidrawPlugin from "./main"; +import { + errorlog, + getDataURL, + getExportTheme, + getFontDataURL, + getImageSize, + getLinkParts, + getExportPadding, + getWithBackground, + hasExportBackground, + hasExportTheme, + LinkParts, + svgToBase64, +} from "./utils/Utils"; + +const THEME_FILTER = "invert(100%) hue-rotate(180deg) saturate(1.25)"; + +export declare type MimeType = + | "image/svg+xml" + | "image/png" + | "image/jpeg" + | "image/gif" + | "image/webp" + | "image/bmp" + | "image/x-icon" + | "application/octet-stream"; +export type FileData = BinaryFileData & { + size: Size; + hasSVGwithBitmap: boolean; +}; + +export type Size = { + height: number; + width: number; +}; + +export class EmbeddedFile { + public file: TFile = null; + public isSVGwithBitmap: boolean = false; + private img: string = ""; //base64 + private imgInverted: string = ""; //base64 + public mtime: number = 0; //modified time of the image + private plugin: ExcalidrawPlugin; + public mimeType: MimeType = "application/octet-stream"; + public size: Size = { height: 0, width: 0 }; + public linkParts: LinkParts; + private hostPath: string; + public attemptCounter: number = 0; + /*public isHyperlink: boolean = false;*/ + + constructor(plugin: ExcalidrawPlugin, hostPath: string, imgPath: string) { + this.plugin = plugin; + this.resetImage(hostPath, imgPath); + } + + public resetImage(hostPath: string, imgPath: string) { + /*if(imgPath.startsWith("https://") || imgPath.startsWith("http://")) { + this.img=imgPath; + this.imgInverted=imgPath; + this.isHyperlink = true; + return; + }*/ + this.imgInverted = this.img = ""; + this.mtime = 0; + this.linkParts = getLinkParts(imgPath); + this.hostPath = hostPath; + 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 = app.metadataCache.getFirstLinkpathDest( + this.linkParts.path, + hostPath, + ); + if (!this.file) { + if(this.attemptCounter++ === 0) { + new Notice( + `Excalidraw Warning: could not find image file: ${imgPath}`, + 5000, + ); + } + } + } + + private fileChanged(): boolean { + if (!this.file) { + this.file = app.metadataCache.getFirstLinkpathDest( + this.linkParts.path, + this.hostPath, + ); // maybe the file has synchronized in the mean time + if(!this.file) { + this.attemptCounter++; + return false; + } + } + return this.mtime != this.file.stat.mtime; + } + + public 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; + this.mimeType = mimeType; + switch (isDark && isSVGwithBitmap) { + case true: + this.imgInverted = imgBase64; + break; + case false: + this.img = imgBase64; + break; //bitmaps and SVGs without an embedded bitmap do not need a negative image + } + this.isSVGwithBitmap = isSVGwithBitmap; + } + + public isLoaded(isDark: boolean): boolean { + if (!this.file) { + this.file = app.metadataCache.getFirstLinkpathDest( + this.linkParts.path, + this.hostPath, + ); // maybe the file has synchronized in the mean time + if(!this.file) { + this.attemptCounter++; + return true; + } + } + if (this.fileChanged()) { + return false; + } + if (this.isSVGwithBitmap && isDark) { + return this.imgInverted !== ""; + } + return this.img !== ""; + } + + public getImage(isDark: boolean) { + /*if(this.isHyperlink) { + return this.img; + }*/ + 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 === + } +} + +export class EmbeddedFilesLoader { + private plugin: ExcalidrawPlugin; + 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(inFile: TFile | EmbeddedFile, depth: number): Promise<{ + mimeType: MimeType; + fileId: FileId; + dataURL: DataURL; + created: number; + hasSVGwithBitmap: boolean; + size: { height: number; width: number }; + }> { + 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, + }; + + let hasSVGwithBitmap = false; + const isExcalidrawFile = this.plugin.isExcalidrawFile(file); + if ( + !( + IMAGE_TYPES.contains(file.extension) || + isExcalidrawFile || + file.extension === "md" + ) + ) { + return null; + } + const ab = await app.vault.readBinary(file); + + const getExcalidrawSVG = async (isDark: boolean) => { + //debug({where:"EmbeddedFileLoader.getExcalidrawSVG",uid:this.uid,file:file.name}); + const forceTheme = hasExportTheme(this.plugin, file) + ? getExportTheme(this.plugin, file, "light") + : undefined; + const exportSettings: ExportSettings = { + withBackground: hasExportBackground(this.plugin, file) + ? getWithBackground(this.plugin, file) + : false, + withTheme: !!forceTheme, + }; + const svg = await createSVG( + file.path, + true, + exportSettings, + this, + forceTheme, + null, + null, + [], + this.plugin, + depth+1, + getExportPadding(this.plugin, file), + ); + //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; + } + if (hasSVGwithBitmap && isDark) { + imageList.forEach((i) => { + const id = i.parentElement?.id; + svg.querySelectorAll(`use[href='#${id}']`).forEach((u) => { + u.setAttribute("filter", THEME_FILTER); + }); + }); + } + if (!hasSVGwithBitmap && svg.getAttribute("hasbitmap")) { + hasSVGwithBitmap = true; + } + const dURL = svgToBase64(svg.outerHTML) as DataURL; + return dURL as DataURL; + }; + + const excalidrawSVG = isExcalidrawFile + ? await getExcalidrawSVG(this.isDark) + : null; + let mimeType: MimeType = "image/svg+xml"; + if (!isExcalidrawFile) { + switch (file.extension) { + case "png": + mimeType = "image/png"; + break; + case "jpeg": + mimeType = "image/jpeg"; + break; + case "jpg": + mimeType = "image/jpeg"; + break; + case "gif": + mimeType = "image/gif"; + break; + case "webp": + mimeType = "image/webp"; + break; + case "bmp": + mimeType = "image/bmp"; + break; + case "ico": + mimeType = "image/x-icon" + break; + case "svg": + case "md": + mimeType = "image/svg+xml"; + break; + default: + mimeType = "application/octet-stream"; + } + } + let dataURL = + excalidrawSVG ?? + (file.extension === "svg" + ? await getSVGData(app, file) + : file.extension === "md" + ? null + : await getDataURL(ab, mimeType)); + + if(!dataURL) { + const result = await this.convertMarkdownToSVG(this.plugin, file, linkParts); + dataURL = result.dataURL; + hasSVGwithBitmap = result.hasSVGwithBitmap; + } + const size = await getImageSize(dataURL); + return { + mimeType, + fileId: await generateIdFromFile(ab), + dataURL, + created: file.stat.mtime, + hasSVGwithBitmap, + size, + }; + } + + public async loadSceneFiles( + excalidrawData: ExcalidrawData, + addFiles: Function, + depth:number + ) { + if(depth > 4) { + new Notice(t("INFINITE_LOOP_WARNING")+depth.toString(), 6000); + return; + } + 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"; + } + let entry; + const files: FileData[] = []; + while (!this.terminate && !(entry = entries.next()).done) { + const embeddedFile: EmbeddedFile = entry.value[1]; + if (!embeddedFile.isLoaded(this.isDark)) { + //debug({where:"EmbeddedFileLoader.loadSceneFiles",uid:this.uid,status:"embedded Files are not loaded"}); + const data = await this.getObsidianImage(embeddedFile, depth); + if (data) { + files.push({ + mimeType: data.mimeType, + id: entry.value[0], + dataURL: data.dataURL, + created: data.created, + size: data.size, + hasSVGwithBitmap: data.hasSVGwithBitmap, + }); + } + } else if (embeddedFile.isSVGwithBitmap) { + files.push({ + mimeType: embeddedFile.mimeType, + id: entry.value[0], + dataURL: embeddedFile.getImage(this.isDark) as DataURL, + created: embeddedFile.mtime, + size: embeddedFile.size, + hasSVGwithBitmap: embeddedFile.isSVGwithBitmap, + }); + } + } + + let equation; + const equations = excalidrawData.getEquationEntries(); + while (!this.terminate && !(equation = equations.next()).done) { + if (!excalidrawData.getEquation(equation.value[0]).isLoaded) { + const latex = equation.value[1].latex; + const data = await tex2dataURL(latex, this.plugin); + if (data) { + files.push({ + mimeType: data.mimeType, + id: equation.value[0], + dataURL: data.dataURL, + created: data.created, + size: data.size, + hasSVGwithBitmap: false, + }); + } + } + } + + 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, this.isDark); + } catch (e) { + errorlog({ where: "EmbeddedFileLoader.loadSceneFiles", error: e }); + } + } + + private async convertMarkdownToSVG( + plugin: ExcalidrawPlugin, + file: TFile, + linkParts: LinkParts, + ): Promise<{dataURL: DataURL, hasSVGwithBitmap:boolean}> { + //1. + //get the markdown text + let hasSVGwithBitmap = false; + const transclusion = await getTransclusion(linkParts, plugin.app, file); + let text = (transclusion.leadingHashes??"") + transclusion.contents; + if (text === "") { + text = + "# Empty markdown file\nCTRL+Click here to open the file for editing in the current active pane, or CTRL+SHIFT+Click to open it in an adjacent pane."; + } + + //2. + //get styles + const fileCache = plugin.app.metadataCache.getFileCache(file); + let fontDef: string; + let fontName = plugin.settings.mdFont; + if ( + fileCache?.frontmatter && + Boolean(fileCache.frontmatter[FRONTMATTER_KEY_FONT]) + ) { + fontName = fileCache.frontmatter[FRONTMATTER_KEY_FONT]; + } + switch (fontName) { + case "Virgil": + fontDef = VIRGIL_FONT; + break; + case "Cascadia": + fontDef = CASCADIA_FONT; + break; + case "": + fontDef = ""; + break; + default: + const font = await getFontDataURL(plugin.app, fontName, file.path); + fontDef = font.fontDef; + fontName = font.fontName; + } + + if ( + fileCache?.frontmatter && + fileCache.frontmatter["banner"] !== null + ) { + text = text.replace(/banner:\s*.*/,""); //patch https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/814 + } + + const fontColor = fileCache?.frontmatter + ? fileCache.frontmatter[FRONTMATTER_KEY_FONTCOLOR] ?? + plugin.settings.mdFontColor + : plugin.settings.mdFontColor; + + let style = fileCache?.frontmatter + ? fileCache.frontmatter[FRONTMATTER_KEY_MD_STYLE] ?? "" + : ""; + let frontmatterCSSisAfile = false; + if (style && style != "") { + const f = plugin.app.metadataCache.getFirstLinkpathDest(style, file.path); + if (f) { + style = await plugin.app.vault.read(f); + frontmatterCSSisAfile = true; + } + } + if (!frontmatterCSSisAfile) { + if (plugin.settings.mdCSS && plugin.settings.mdCSS !== "") { + const f = plugin.app.metadataCache.getFirstLinkpathDest( + plugin.settings.mdCSS, + file.path, + ); + style += f ? `\n${await plugin.app.vault.read(f)}` : DEFAULT_MD_EMBED_CSS; + } else { + style += DEFAULT_MD_EMBED_CSS; + } + } + + const borderColor = fileCache?.frontmatter + ? fileCache.frontmatter[FRONTMATTER_KEY_BORDERCOLOR] ?? + plugin.settings.mdBorderColor + : plugin.settings.mdBorderColor; + + if (borderColor && borderColor !== "" && !style.match(/svg/i)) { + style += `svg{border:2px solid;color:${borderColor};transform:scale(.95)}`; + } + + //3. + //SVG helper functions + //the SVG will first have ~infinite height. After sizing this will be reduced + let svgStyle = ` width="${linkParts.width}px" height="100000"`; + let foreignObjectStyle = ` width="${linkParts.width}px" height="100%"`; + + const svg = (xml: string, xmlFooter: string, style?: string) => + `${ + style ? `` : "" + }${xml}${ + xmlFooter //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/286#issuecomment-982179639 + }${ + fontDef !== "" ? `` : "" + }`; + + //4. + //create document div - this will be the contents of the foreign object + const mdDIV = createDiv(); + mdDIV.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); + mdDIV.setAttribute("class", "excalidraw-md-host"); + // mdDIV.setAttribute("style",style); + if (fontName !== "") { + mdDIV.style.fontFamily = fontName; + } + mdDIV.style.overflow = "auto"; + mdDIV.style.display = "block"; + mdDIV.style.color = fontColor && fontColor !== "" ? fontColor : "initial"; + + await MarkdownRenderer.renderMarkdown(text, mdDIV, file.path, plugin); + + mdDIV + .querySelectorAll(":scope > *[class^='frontmatter']") + .forEach((el) => mdDIV.removeChild(el)); + + const internalEmbeds = Array.from(mdDIV.querySelectorAll("span[class='internal-embed']")) + for(let i=0;i { + const elementStyle = el.style; + const computedStyle = window.getComputedStyle(el); + let style = ""; + for (const prop in elementStyle) { + if (elementStyle.hasOwnProperty(prop)) { + style += `${prop}: ${computedStyle[prop]};`; + } + } + el.setAttribute("style", style); + }); + + const xmlINiframe = new XMLSerializer().serializeToString(stylingDIV); + const xmlFooter = new XMLSerializer().serializeToString(footerDIV); + document.body.removeChild(iframeHost); + + //5.2 + //get SVG size + const parser = new DOMParser(); + const doc = parser.parseFromString( + svg(xmlINiframe, xmlFooter), + "image/svg+xml", + ); + const svgEl = doc.firstElementChild; + const host = createDiv(); + host.appendChild(svgEl); + document.body.appendChild(host); + const footerHeight = svgEl.querySelector( + ".excalidraw-md-footer", + ).scrollHeight; + const height = + svgEl.querySelector(".excalidraw-md-host").scrollHeight + footerHeight; + 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"`; + mdDIV.style.height = `${svgHeight - footerHeight}px`; + mdDIV.style.overflow = "hidden"; + + const imageList = mdDIV.querySelectorAll( + "img:not([src^='data:image/svg+xml'])", + ); + if (imageList.length > 0) { + hasSVGwithBitmap = true; + } + if (hasSVGwithBitmap && this.isDark) { + imageList.forEach(img => { + if(img instanceof HTMLImageElement) { + img.style.filter = THEME_FILTER; + } + }); + } + + const xml = new XMLSerializer().serializeToString(mdDIV); + const finalSVG = svg(xml, '', style); + plugin.ea.mostRecentMarkdownSVG = parser.parseFromString( + finalSVG, + "image/svg+xml", + ).firstElementChild as SVGSVGElement; + return { + dataURL: svgToBase64(finalSVG) as DataURL, + hasSVGwithBitmap + }; + }; +} + +const getSVGData = async (app: App, file: TFile): Promise => { + const svg = await app.vault.read(file); + return svgToBase64(svg) as DataURL; +}; + +const generateIdFromFile = async (file: ArrayBuffer): Promise => { + let id: FileId; + try { + const hashBuffer = await window.crypto.subtle.digest("SHA-1", file); + id = + // convert buffer to byte array + Array.from(new Uint8Array(hashBuffer)) + // convert to hex string + .map((byte) => byte.toString(16).padStart(2, "0")) + .join("") as FileId; + } catch (error) { + errorlog({ where: "EmbeddedFileLoader.generateIdFromFile", error }); + id = fileid() as FileId; + } + return id; +}; diff --git a/src/ExcalidrawAutomate.ts b/src/ExcalidrawAutomate.ts index 7a199ed..68f986a 100644 --- a/src/ExcalidrawAutomate.ts +++ b/src/ExcalidrawAutomate.ts @@ -7,6 +7,7 @@ import { ExcalidrawBindableElement, FileId, NonDeletedExcalidrawElement, + ExcalidrawImageElement, } from "@zsviczian/excalidraw/types/element/types"; import { normalizePath, TFile, WorkspaceLeaf } from "obsidian"; import ExcalidrawView, { ExportSettings, TextMode } from "./ExcalidrawView"; @@ -24,7 +25,9 @@ import { //debug, embedFontsInSVG, errorlog, + getDataURL, getEmbeddedFilenameParts, + getImageSize, getPNG, getSVG, isVersionNewerThanOther, @@ -1214,7 +1217,7 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface { getViewElements(): ExcalidrawElement[] { //@ts-ignore if (!this.targetView || !this.targetView?._loaded) { - errorMessage("targetView not set", "getViewSelectedElements()"); + errorMessage("targetView not set", "getViewElements()"); return []; } const current = this.targetView?.excalidrawRef?.current; @@ -1232,7 +1235,7 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface { deleteViewElements(elToDelete: ExcalidrawElement[]): boolean { //@ts-ignore if (!this.targetView || !this.targetView?._loaded) { - errorMessage("targetView not set", "getViewSelectedElements()"); + errorMessage("targetView not set", "deleteViewElements()"); return false; } const current = this.targetView?.excalidrawRef?.current; @@ -1280,7 +1283,7 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface { getViewFileForImageElement(el: ExcalidrawElement): TFile | null { //@ts-ignore if (!this.targetView || !this.targetView?._loaded) { - errorMessage("targetView not set", "getViewSelectedElements()"); + errorMessage("targetView not set", "getViewFileForImageElement()"); return null; } if (!el || el.type !== "image") { @@ -1678,6 +1681,30 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface { return { width: size.w ?? 0, height: size.h ?? 0 }; }; + /** + * Returns the size of the image element at 100% (i.e. the original size) + * @param imageElement an image element from the active scene on targetView + */ + async getOriginalImageSize(imageElement: ExcalidrawImageElement): Promise<{width: number; height: number}> { + //@ts-ignore + if (!this.targetView || !this.targetView?._loaded) { + errorMessage("targetView not set", "getOriginalImageSize()"); + return null; + } + if(!imageElement || imageElement.type !== "image") { + errorMessage("Please provide a single image element as input", "getOriginalImageSize()"); + return null; + } + const ef = this.targetView.excalidrawData.getFile(imageElement.fileId); + if(!ef) { + errorMessage("Please provide a single image element as input", "getOriginalImageSize()"); + return null; + } + const isDark = this.getExcalidrawAPI().getAppState().theme === "dark"; + const dataURL = ef.getImage(isDark); + return await getImageSize(dataURL); + } + /** * verifyMinimumPluginVersion returns true if plugin version is >= than required * recommended use: diff --git a/src/dialogs/Messages.ts b/src/dialogs/Messages.ts index 81abfb6..f033979 100644 --- a/src/dialogs/Messages.ts +++ b/src/dialogs/Messages.ts @@ -17,6 +17,23 @@ I develop this plugin as a hobby, spending most of my free time doing this. If y
`, +"1.7.23":` +# New and improved +- **Updated Chinese translation**. Thanks, @tswwe! +- **Improved update for TextElement links**: Until now, when you attached a link to a file to a TextElement using the "Create Link" command, this link did not get updated when the file was renamed or moved. Only links created as markdown links in the TextElement text were updated. Now both approaches work. Keep in mind however, that if you have a link in the TextElemenet text, it will override the link attached to the text element using the create link command. [#566](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/566) +- **Transclusion filters markdown comments**: Text transclusion in a TextElement using the ${String.fromCharCode(96)}![[file]]${String.fromCharCode(96)} or ${String.fromCharCode(96)}![[file#section]]${String.fromCharCode(96)} format did not filter out markdown comments in the file placed ${String.fromCharCode(96)}%% inside a comment block %%${String.fromCharCode(96)}. Now they do. +- **Remove leading '>' from trancluded quotes**: Added a new option in settings under **Links and Transclusion** to remove the leading ${String.fromCharCode(96)}> ${String.fromCharCode(96)} characters from quotes you transclude as a text element in your drawing. +![image](https://user-images.githubusercontent.com/14358394/194755306-6e7bf5f3-4228-44a1-9363-c3241b34865e.png) +- **Added support for ${String.fromCharCode(96)}webp${String.fromCharCode(96)}, ${String.fromCharCode(96)}bmp${String.fromCharCode(96)}, and ${String.fromCharCode(96)}ico${String.fromCharCode(96)} images**. This extends the already supported formats (${String.fromCharCode(96)}jpg${String.fromCharCode(96)}, ${String.fromCharCode(96)}gif${String.fromCharCode(96)}, ${String.fromCharCode(96)}png${String.fromCharCode(96)}, ${String.fromCharCode(96)}svg${String.fromCharCode(96)}). +- **Added command palette action to reset images to original size**. Select a single image or embedded Excalidraw drawing on your canvas and choose ${String.fromCharCode(96)}Set selected image element size to 100% of original${String.fromCharCode(96)} from the command palette. This function is especially helpful when you combine atomic drawings on a single canvas, keeping each atomic piece in its original excalidraw file (i.e. the way I create [book on a page summaries](https://www.youtube.com/playlist?list=PL6mqgtMZ4NP1-mbCYc3T7mr-unmsIXpEG)) +- The ${String.fromCharCode(96)}async getOriginalImageSize(imageElement: ExcalidrawImageElement): Promise<{width: number; height: number}>${String.fromCharCode(96)} function is also avaiable via ExcalidrawAutomate. You may use this function to resize images to custom scales (e.g. 50% size, or to fit a certain bounding rectangle). + +# Fixed +- **Upgraded perfect freehand package to resolve unwanted dots on end of lines** [#5727](https://github.com/excalidraw/excalidraw/pull/5727) +- **Pinch zoom in View mode opens images** resulting in a very annoying behavior [#837](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/837) +- **Embedded files** such as transcluded markdown documents and images **did not honor the Obsidian "New Link Format" setting** (shortest path, relative path, absolute path). [#829](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/829) +- **Fixed error with dataview queries involving Excalidraw files**: In case you created a task on an Excalidraw canvas (${String.fromCharCode(96)}docA.md${String.fromCharCode(96)}) by typing ${String.fromCharCode(96)}- [ ] Task [[owner]] #tag${String.fromCharCode(96)}, and then you created a Dataview tasklist in another document (${String.fromCharCode(96)}docB.md${String.fromCharCode(96)}) such that the query criteria matched the task in ${String.fromCharCode(96)}docA.md${String.fromCharCode(96)}, then the task from ${String.fromCharCode(96)}docA.md${String.fromCharCode(96)} only appeared as an empty line when viewing ${String.fromCharCode(96)}docB.md${String.fromCharCode(96)}. If you now embedded ${String.fromCharCode(96)}docB.md${String.fromCharCode(96)} into a third markdown document (${String.fromCharCode(96)}docC.md${String.fromCharCode(96)}), then instead of the contents of ${String.fromCharCode(96)}docB.md${String.fromCharCode(96)} Obsidian rendered ${String.fromCharCode(96)}docA.md${String.fromCharCode(96)}. [#835](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/835) +`, "1.7.22":` # Fixed - Text size in sticky notes increased when opening the drawing and when editing a sticky note [#824](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/824) diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts index eba612f..a2e0ebd 100644 --- a/src/lang/locale/en.ts +++ b/src/lang/locale/en.ts @@ -60,6 +60,7 @@ export default { READ_RELEASE_NOTES: "Read latest release notes", TRAY_MODE: "Toggle property-panel tray-mode", SEARCH: "Search for text in drawing", + RESET_IMG_TO_100: "Set selected image element size to 100% of original", //ExcalidrawView.ts INSTALL_SCRIPT_BUTTON: "Install or update Excalidraw Scripts", diff --git a/src/main.ts b/src/main.ts index 7d1dabe..424b564 100644 --- a/src/main.ts +++ b/src/main.ts @@ -89,7 +89,7 @@ import { } from "./utils/Utils"; import { getAttachmentsFolderAndFilePath, getNewOrAdjacentLeaf, getParentOfClass, isObsidianThemeDark } from "./utils/ObsidianUtils"; //import { OneOffs } from "./OneOffs"; -import { FileId } from "@zsviczian/excalidraw/types/element/types"; +import { ExcalidrawImageElement, FileId } from "@zsviczian/excalidraw/types/element/types"; import { ScriptEngine } from "./Scripts"; import { hoverEvent, @@ -1153,6 +1153,42 @@ export default class ExcalidrawPlugin extends Plugin { }, }); + this.addCommand({ + id: "reset-image-to-100", + name: t("RESET_IMG_TO_100"), + checkCallback: (checking:boolean) => { + const view = this.app.workspace.getActiveViewOfType(ExcalidrawView); + if(!view) return false; + if(!view.excalidrawAPI) return false; + const els = view.getViewSelectedElements().filter(el=>el.type==="image"); + if(els.length !== 1) { + if(checking) return false; + new Notice("Select a single image element and try again"); + return false; + } + const el = els[0] as ExcalidrawImageElement; + const ef = view.excalidrawData.getFile(el.fileId); + if(!ef) { + if(checking) return false; + new Notice("Select a single image element and try again"); + return false; + } + if(checking) return true; + + (async () => { + const ea = new ExcalidrawAutomate(this,view); + const size = await ea.getOriginalImageSize(el); + if(size) { + ea.copyViewElementsToEAforEditing(els); + const eaEl = ea.getElement(el.id); + //@ts-ignore + eaEl.width = size.width; eaEl.height = size.height; + ea.addElementsToView(false,false,false); + } + })() + } + }) + this.addCommand({ id: "insert-image", name: t("INSERT_IMAGE"), diff --git a/src/types.d.ts b/src/types.d.ts index 38fa15e..f836e64 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1,4 +1,4 @@ -import { ExcalidrawBindableElement, ExcalidrawElement, FileId, FillStyle, NonDeletedExcalidrawElement, StrokeSharpness, StrokeStyle } from "@zsviczian/excalidraw/types/element/types"; +import { ExcalidrawBindableElement, ExcalidrawElement, ExcalidrawImageElement, FileId, FillStyle, NonDeletedExcalidrawElement, StrokeSharpness, StrokeStyle } from "@zsviczian/excalidraw/types/element/types"; import { Point } from "@zsviczian/excalidraw/types/types"; import { TFile, WorkspaceLeaf } from "obsidian"; import { EmbeddedFilesLoader } from "./EmbeddedFileLoader"; @@ -223,6 +223,7 @@ export interface ExcalidrawAutomateInterface { //verifyMinimumPluginVersion returns true if plugin version is >= than required //recommended use: //if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.20")) {new Notice("message");return;} + getOriginalImageSize(imageElement: ExcalidrawImageElement): Promise<{width: number; height: number}>; verifyMinimumPluginVersion(requiredVersion: string): boolean; isExcalidrawView(view: any): boolean; selectElementsInView(elements: ExcalidrawElement[]): void; //sets selection in view