diff --git a/manifest.json b/manifest.json index d29bed4..51b59dd 100644 --- a/manifest.json +++ b/manifest.json @@ -1,11 +1,12 @@ { "id": "obsidian-excalidraw-plugin", "name": "Excalidraw", - "version": "1.9.28", + "version": "2.0.0", "minAppVersion": "1.1.6", "description": "An Obsidian plugin to edit and view Excalidraw drawings", "author": "Zsolt Viczian", "authorUrl": "https://zsolt.blog", "fundingUrl": "https://ko-fi.com/zsolt", + "helpUrl": "https://github.com/zsviczian/obsidian-excalidraw-plugin#readme", "isDesktopOnly": false } diff --git a/src/MarkdownPostProcessor.ts b/src/MarkdownPostProcessor.ts index 960ad4b..b3af7ff 100644 --- a/src/MarkdownPostProcessor.ts +++ b/src/MarkdownPostProcessor.ts @@ -19,13 +19,11 @@ import { getWithBackground, hasExportTheme, convertSVGStringToElement, - getFileCSSClasses, } from "./utils/Utils"; -import { getParentOfClass, isObsidianThemeDark } from "./utils/ObsidianUtils"; +import { getParentOfClass, isObsidianThemeDark, getFileCSSClasses } from "./utils/ObsidianUtils"; import { linkClickModifierType } from "./utils/ModifierkeyHelper"; import { ImageKey, imageCache } from "./utils/ImageCache"; import { FILENAMEPARTS, PreviewImageType } from "./utils/UtilTypes"; -import { css } from "chroma-js"; interface imgElementAttributes { file?: TFile; @@ -401,7 +399,7 @@ const createImgElement = async ( newImg.setAttribute("fileSource",fileSource); parent.append(newImg); }); - const cssClasses = getFileCSSClasses(plugin, attr.file); + const cssClasses = getFileCSSClasses(attr.file); cssClasses.forEach((cssClass) => imgOrDiv.addClass(cssClass)); return imgOrDiv; } diff --git a/src/dialogs/Messages.ts b/src/dialogs/Messages.ts index 7b2da8a..f940616 100644 --- a/src/dialogs/Messages.ts +++ b/src/dialogs/Messages.ts @@ -16,6 +16,30 @@ export const RELEASE_NOTES: { [k: string]: string } = { I develop this plugin as a hobby, spending my free time doing this. If you find it valuable, then please say THANK YOU or...
+`, +"2.0.0":` +
+ +
+ +## New +- Added support for applying CSS classes in frontmatter. Now, when embedding Excalidraw drawings into Obsidian Canvas, you can use [Canvas Candy](https://tfthacker.com/canvas-candy) classes. For instance, ${String.fromCharCode(96)}cssclasses: cc-border-none${String.fromCharCode(96)} removes the canvas node border around the drawing. +- Introduced new context menu actions: + - Navigate to link or embedded image. + - Add any file from the vault to the canvas. + - Convert the selected text element or sticky note to an embedded markdown file. + - Add a link from the Vault to the selected element. +- Frames are now rendered in exported images. +- SVG Export includes the ${String.fromCharCode(96)}.excalidraw-svg${String.fromCharCode(96)} class, enabling post-processing of SVGs using publish.js when using custom domains with Obsidian Publish. Also, added a command palette action ${String.fromCharCode(96)}Obsidian Publish: Find SVG and PNG exports that are out of date${String.fromCharCode(96)}. +- Added a new Command palette action to open the corresponding Excalidraw file based on the embedded SVG or PNG file. ${String.fromCharCode(96)}Open Excalidraw Drawing${String.fromCharCode(96)} [Issue #1411](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1411) + +## Fixed and Improved +- Resolved issue with the Mermaid Timeline graph displaying all black. [Issue #1424](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1424) +- Enabled toggling pen mode off after activation by a pen touch. +- Now you are able to unlock elements on mobile; previously, locked elements couldn't be selected. +- Fixed the disabled ${String.fromCharCode(96)}complete line button${String.fromCharCode(96)} for multipoint lines on mobile. +![Mobile Editing Image](https://github.com/zsviczian/obsidian-excalidraw-plugin/assets/14358394/e7051c75-818f-4800-ba16-ac276e229184) + `, "1.9.28":` ## Fixed & Improved diff --git a/src/dialogs/PublishOutOfDateFiles.ts b/src/dialogs/PublishOutOfDateFiles.ts index c0b0e64..17940a6 100644 --- a/src/dialogs/PublishOutOfDateFiles.ts +++ b/src/dialogs/PublishOutOfDateFiles.ts @@ -67,8 +67,7 @@ export class PublishOutOfDateFilesDialog extends Modal { text: "Video about Obsidian Publish support", }); detailsEl.createEl("br"); - addIframe(detailsEl, "OX5_UYjXEvc", undefined, ""); - + addIframe(detailsEl, "JC1E-jeiWhI"); const p = this.contentEl.createEl("p",{text: "Collecting data..."}); const statusEl = this.contentEl.createEl("p", {text: "Status: "}); const files = await listOfOutOfSyncImgExports(this.plugin, recursive, statusEl); diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts index d7bee70..6925f63 100644 --- a/src/lang/locale/en.ts +++ b/src/lang/locale/en.ts @@ -10,6 +10,7 @@ import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/Modifierke export default { // main.ts PUBLISH_SVG_CHECK: "Obsidian Publish: Find SVG and PNG exports that are out of date", + OPEN_IMAGE_SOURCE: "Open Excalidraw drawing", INSTALL_SCRIPT: "Install the script", UPDATE_SCRIPT: "Update available - Click to install", CHECKING_SCRIPT: @@ -405,6 +406,11 @@ FILENAME_HEAD: "Filename", "or a PNG or an SVG copy. You need to enable auto-export PNG / SVG (see below under Export Settings) for those image types to be available in the dropdown. For drawings that do not have a " + "a corresponding PNG or SVG readily available the command palette action will insert a broken link. You need to open the original drawing and initiate export manually. " + "This option will not autogenerate PNG/SVG files, but will simply reference the already existing files.", + EMBED_MARKDOWN_COMMENT_NAME: "Embed link to drawing as comment", + EMBED_MARKDOWN_COMMENT_DESC: + "Embed the link to the original Excalidraw file as a markdown link under the image, e.g.:%%[[drawing.excalidraw]]%%.
" + + "Instead of adding a markdown comment you may also select the embedded SVG or PNG line and use the command palette action: " + + "'Excalidraw: Open Excalidraw drawing' to open the drawing.", EMBED_WIKILINK_NAME: "Embed Drawing using Wiki link", EMBED_WIKILINK_DESC: "Toggle ON: Excalidraw will embed a [[wiki link]].
Toggle OFF: Excalidraw will embed a [markdown](link).", diff --git a/src/main.ts b/src/main.ts index 419a5df..abcf850 100644 --- a/src/main.ts +++ b/src/main.ts @@ -21,7 +21,6 @@ import { Editor, MarkdownFileInfo, loadMermaid, - moment, } from "obsidian"; import { BLANK_DRAWING, @@ -92,7 +91,7 @@ import { getExportTheme, isCallerFromTemplaterPlugin, } from "./utils/Utils"; -import { getAttachmentsFolderAndFilePath, getNewOrAdjacentLeaf, getParentOfClass, isObsidianThemeDark } from "./utils/ObsidianUtils"; +import { extractSVGPNGFileName, getAttachmentsFolderAndFilePath, getNewOrAdjacentLeaf, getParentOfClass, isObsidianThemeDark } from "./utils/ObsidianUtils"; //import { OneOffs } from "./OneOffs"; import { ExcalidrawElement, ExcalidrawImageElement, ExcalidrawTextElement, FileId } from "@zsviczian/excalidraw/types/element/types"; import { ScriptEngine } from "./Scripts"; @@ -117,7 +116,6 @@ import { UniversalInsertFileModal } from "./dialogs/UniversalInsertFileModal"; import { imageCache } from "./utils/ImageCache"; import { StylesManager } from "./utils/StylesManager"; import { MATHJAX_SOURCE_LZCOMPRESSED } from "./constMathJaxSource"; -import { getEA } from "src"; import { PublishOutOfDateFilesDialog } from "./dialogs/PublishOutOfDateFiles"; declare const EXCALIDRAW_PACKAGES:string; @@ -855,6 +853,37 @@ export default class ExcalidrawPlugin extends Plugin { } }) + this.addCommand({ + id: "open-image-excalidraw-source", + name: t("OPEN_IMAGE_SOURCE"), + checkCallback: (checking: boolean) => { + const view = this.app.workspace.getActiveViewOfType(MarkdownView); + if(!view) return false; + if(view.leaf !== this.app.workspace.activeLeaf) return false; + const editor = view.editor; + if(!editor) return false; + const cursor = editor.getCursor(); + const line = editor.getLine(cursor.line); + const fname = extractSVGPNGFileName(line); + if(!fname) return false; + const imgFile = this.app.metadataCache.getFirstLinkpathDest(fname, view.file.path); + if(!imgFile) return false; + const excalidrawFname = getIMGFilename(imgFile.path, "md"); + let excalidrawFile = this.app.metadataCache.getFirstLinkpathDest(excalidrawFname, view.file.path); + if(!excalidrawFile) { + if(excalidrawFname.endsWith(".dark.md")) { + excalidrawFile = this.app.metadataCache.getFirstLinkpathDest(excalidrawFname.replace(/\.dark\.md$/,".md"), view.file.path); + } + if(excalidrawFname.endsWith(".light.md")) { + excalidrawFile = this.app.metadataCache.getFirstLinkpathDest(excalidrawFname.replace(/\.light\.md$/,".md"), view.file.path); + } + if(!excalidrawFile) return false; + } + if(checking) return true; + this.openDrawing(excalidrawFile, "new-tab", true); + } + }); + this.addCommand({ id: "excalidraw-disable-autosave", name: t("TEMPORARY_DISABLE_AUTOSAVE"), @@ -2362,7 +2391,9 @@ export default class ExcalidrawPlugin extends Plugin { ) : ""; - theme = theme===""?"":theme+"."; + theme = (theme === "") + ? "" + : theme + "."; const imageRelativePath = getIMGFilename( excalidrawRelativePath, @@ -2374,12 +2405,13 @@ export default class ExcalidrawPlugin extends Plugin { ); //will hold incorrect value if theme==="", however in that case it won't be used - const otherTheme = theme === "dark." ? "light.":"dark."; - const otherImageRelativePath = getIMGFilename( - excalidrawRelativePath, - otherTheme+this.settings.embedType.toLowerCase(), - ); - + const otherTheme = theme === "dark." ? "light." : "dark."; + const otherImageRelativePath = theme === "" + ? null + : getIMGFilename( + excalidrawRelativePath, + otherTheme+this.settings.embedType.toLowerCase(), + ); const imgFile = this.app.vault.getAbstractFileByPath(imageFullpath); if (!imgFile) { @@ -2387,13 +2419,21 @@ export default class ExcalidrawPlugin extends Plugin { await sleep(200); //wait for metadata cache to update } + const inclCom = this.settings.embedMarkdownCommentLinks; + editor.replaceSelection( this.settings.embedWikiLink - ? `![[${imageRelativePath}]]\n%%[[${excalidrawRelativePath}|🖋 Edit in Excalidraw]]${ - otherImageRelativePath ? ", and the [["+otherImageRelativePath+"|"+otherTheme.split(".")[0]+" exported image]]":""}%%` - : `![](${encodeURI(imageRelativePath)})\n%%[🖋 Edit in Excalidraw](${encodeURI( - excalidrawRelativePath, - )})${otherImageRelativePath?", and the ["+otherTheme.split(".")[0]+" exported image]("+encodeURI(otherImageRelativePath)+")":""}%%`, + ? `![[${imageRelativePath}]]\n` + + (inclCom + ? `%%[[${excalidrawRelativePath}|🖋 Edit in Excalidraw]]${ + otherImageRelativePath + ? ", and the [["+otherImageRelativePath+"|"+otherTheme.split(".")[0]+" exported image]]" + : "" + }%%` + : "") + : `![](${encodeURI(imageRelativePath)})\n` + + (inclCom ? `%%[🖋 Edit in Excalidraw](${encodeURI(excalidrawRelativePath, + )})${otherImageRelativePath?", and the ["+otherTheme.split(".")[0]+" exported image]("+encodeURI(otherImageRelativePath)+")":""}%%` : ""), ); editor.focus(); } diff --git a/src/settings.ts b/src/settings.ts index 6ba1866..e9fc71e 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -89,6 +89,7 @@ export interface ExcalidrawSettings { autoExportLightAndDark: boolean; autoexportExcalidraw: boolean; embedType: "excalidraw" | "PNG" | "SVG"; + embedMarkdownCommentLinks: boolean; embedWikiLink: boolean; syncExcalidraw: boolean; compatibilityMode: boolean; @@ -212,6 +213,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = { autoExportLightAndDark: false, autoexportExcalidraw: false, embedType: "excalidraw", + embedMarkdownCommentLinks: true, embedWikiLink: true, syncExcalidraw: false, experimentalFileType: false, @@ -1200,7 +1202,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab { addIframe(detailsEl, "opLd1SqaH_I",8); let dropdown: DropdownComponent; - + let embedComment: Setting; new Setting(detailsEl) .setName(t("EMBED_TYPE_NAME")) .setDesc(fragWithHTML(t("EMBED_TYPE_DESC"))) @@ -1224,9 +1226,24 @@ export class ExcalidrawSettingTab extends PluginSettingTab { .onChange(async (value) => { //@ts-ignore this.plugin.settings.embedType = value; + embedComment.settingEl.style.display = value === "excalidraw" ? "none":""; this.applySettingsUpdate(); }); }); + + embedComment = new Setting(detailsEl) + .setName(t("EMBED_MARKDOWN_COMMENT_NAME")) + .setDesc(fragWithHTML(t("EMBED_MARKDOWN_COMMENT_DESC"))) + .addToggle((toggle) => + toggle + .setValue(this.plugin.settings.embedMarkdownCommentLinks) + .onChange(async (value) => { + this.plugin.settings.embedMarkdownCommentLinks = value; + this.applySettingsUpdate(); + }), + ); + + embedComment.settingEl.style.display = this.plugin.settings.embedType === "excalidraw" ? "none":""; new Setting(detailsEl) .setName(t("EMBED_WIKILINK_NAME")) diff --git a/src/utils/ObsidianUtils.ts b/src/utils/ObsidianUtils.ts index f3dbc3c..dc63a2f 100644 --- a/src/utils/ObsidianUtils.ts +++ b/src/utils/ObsidianUtils.ts @@ -1,6 +1,6 @@ import { App, - normalizePath, Workspace, WorkspaceLeaf, WorkspaceSplit + normalizePath, parseFrontMatterEntry, TFile, Workspace, WorkspaceLeaf, WorkspaceSplit } from "obsidian"; import ExcalidrawPlugin from "../main"; import { checkAndCreateFolder, splitFolderAndFilename } from "./FileUtils"; @@ -233,3 +233,24 @@ export const obsidianPDFQuoteWithRef = (text:string):{quote: string, link: strin } return null; } + +export const extractSVGPNGFileName = (text:string) => { + const regex = /\[\[([^\]|#^]+\.(?:svg|png))(?:[^\]]+)?\]\]|\[[^\]]+\]\(([^\)]+\.(?:svg|png))\)/; + const match = text.match(regex); + return match ? (match[1] || match[2]) : null; +} + +export const getFileCSSClasses = ( + file: TFile, +): string[] => { + if (file) { + const plugin = window.ExcalidrawAutomate.plugin; + const fileCache = plugin.app.metadataCache.getFileCache(file); + if(!fileCache?.frontmatter) return []; + const x = parseFrontMatterEntry(fileCache.frontmatter, "cssclasses"); + if (Array.isArray(x)) return x + if (typeof x === "string") return Array.from(new Set(x.split(/[, ]+/).filter(Boolean))); + return []; + } + return []; +} \ No newline at end of file diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 6da9ba5..89c6004 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -33,7 +33,7 @@ import { generateEmbeddableLink } from "./CustomEmbeddableUtils"; import ExcalidrawScene from "src/svgToExcalidraw/elements/ExcalidrawScene"; import { FILENAMEPARTS } from "./UtilTypes"; import { Mutable } from "@zsviczian/excalidraw/types/utility-types"; -import { cleanBlockRef, cleanSectionHeading } from "./ObsidianUtils"; +import { cleanBlockRef, cleanSectionHeading, getFileCSSClasses } from "./ObsidianUtils"; import { updateElementLinksToObsidianLinks } from "src/ExcalidrawAutomate"; @@ -295,6 +295,10 @@ export const getSVG = async ( }); if(svg) { svg.addClass("excalidraw-svg"); + if(srcFile instanceof TFile) { + const cssClasses = getFileCSSClasses(srcFile); + cssClasses.forEach((cssClass) => svg.addClass(cssClass)); + } } return svg; } catch (error) { @@ -616,21 +620,6 @@ export const getExportPadding = ( return plugin.settings.exportPaddingSVG; }; -export const getFileCSSClasses = ( - plugin: ExcalidrawPlugin, - file: TFile, -): string[] => { - if (file) { - const fileCache = plugin.app.metadataCache.getFileCache(file); - if(!fileCache?.frontmatter) return []; - const x = parseFrontMatterEntry(fileCache.frontmatter, "cssclasses"); - if (Array.isArray(x)) return x - if (typeof x === "string") return Array.from(new Set(x.split(/[, ]+/).filter(Boolean))); - return []; - } - return []; -} - export const getPNGScale = (plugin: ExcalidrawPlugin, file: TFile): number => { if (file) { const fileCache = plugin.app.metadataCache.getFileCache(file);