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.
+
+
`,
"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]]":""}%%`
- : `})\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]]"
+ : ""
+ }%%`
+ : "")
+ : `})\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);