mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc7227d164 | ||
|
|
c75e7fb76c | ||
|
|
6d3eb20ff1 | ||
|
|
a603e4eeac | ||
|
|
ac260925dd | ||
|
|
1a2e7ac23f | ||
|
|
1eb9b88f4b | ||
|
|
a7814f383a | ||
|
|
54e6d47df0 | ||
|
|
55ea1cf121 | ||
|
|
a2982f3406 | ||
|
|
633ff1fea8 | ||
|
|
3312df0743 | ||
|
|
0722bb8133 | ||
|
|
c9be4d95d7 | ||
|
|
023ddcec39 | ||
|
|
9255643646 | ||
|
|
36ead43102 | ||
|
|
f61d000326 | ||
|
|
9bb254dc48 |
13
README.md
13
README.md
@@ -11,6 +11,8 @@ Please upgrade to Obsidian v0.12.19 or higher to get the latest release.
|
||||
|[](https://youtu.be/QOL1KF7-kdc)|[](https://youtu.be/aSgcbfspvfo)|[](https://youtu.be/MaJ5jJwBRWs)|
|
||||
|[](https://youtu.be/MXzeCOEExNo)|[](https://youtu.be/R0IAg0s-wQE)|[](https://youtu.be/ibdS7ykwpW4)|
|
||||
|[](https://youtu.be/VRZVujfVab0)|[](https://youtu.be/D1iBYo1_jjc)|[](https://www.youtube.com/watch?v=_c_0zpBJ4Xc&)|
|
||||
|[](https://youtu.be/r08wk-58DPk)|[](https://youtu.be/tsecSfnTMow)|[](https://youtu.be/K6qZkTz8GHs)|
|
||||
|
||||
|
||||
# Key features
|
||||
- The plugin aims to integrate Excalidraw seamlessly into Obsidian including Command Palette actions, File Explorer features, Option Menu commands, and the Ribbon Button.
|
||||
@@ -58,6 +60,17 @@ Please upgrade to Obsidian v0.12.19 or higher to get the latest release.
|
||||
- `excalidraw-link-prefix: "📍"` preview prefix for internal links
|
||||
- `excalidraw-url-prefix: "🌐"` preview prefix for external links
|
||||
- `excalidraw-link-brackets: true|false` whether or not to display brackets around links in preview
|
||||
- Embed complete markdown files into your drawings
|
||||
- Drag from the desired file from the Obsidian file explorer and hold down CTRL/CMD while dropping the file onto the canvas.
|
||||
- Use the command palette action: `Insert markdown file from vault`
|
||||
- Use custom woff, woff2 or TTF font to display the document, you can set the default font to use under Excalidraw Settings.
|
||||
- You can set a custom css for rendering the snapshot image of your markdown document. Only operating system standard fonts are supported as the font-family ([Win10](https://docs.microsoft.com/en-us/typography/fonts/windows_10_font_list), [Mac & iOS](https://developer.apple.com/fonts/system-fonts/)), plus you can set one additional custom font using the setting explained above. (for a demonstration watch this [video](https://youtu.be/K6qZkTz8GHs) and check out this [sample css](https://github.com/zsviczian/obsidian-excalidraw-plugin/discussions/281)).
|
||||
- To help with styling you can observe the SVG snapshot of the markdown document created by Excalidraw. Open Obsidian Developer Console (CTRL+Shift+i) and execute the following command: `ExcalidrawAutomate.mostRecentMarkdownSVG`
|
||||
- You can control appearance of the embedded markdown file on a file by file bases by adding the following front matter keys to your markdown document:
|
||||
- `excalidraw-font: Virgil|Cascadia|font_file_name.extension`
|
||||
- `excalidraw-font-color: css-color-name|#HEXcolor|any-other-html-standard-format`, you can find css color names [here](https://www.w3schools.com/colors/colors_names.asp).
|
||||
- `excalidraw-css: "css-filename|css snippet"`
|
||||
- Switch to markdown view or use CTRL/CMD+ALT/OPT click on the image to edit properties of the embed: `[[filename#^blockref|WIDTHxMAXHEIGHT]]`
|
||||
- Includes full [QuickAdd](https://github.com/chhoumann/quickadd), [Templater](https://silentvoid13.github.io/Templater/) and [Dataview](https://blacksmithgu.github.io/obsidian-dataview/docs/api/intro/) support through ExcalidrawAutomate. Check out the [detailed help + examples](https://zsviczian.github.io/obsidian-excalidraw-plugin/). I also have a [YouTube ExcalidrawAutomate Playlist](https://www.youtube.com/playlist?list=PL6mqgtMZ4NP1IR4nXxSlMA4PA5E-qpyHZ) with lots of examples.
|
||||
- REQUIRES AN OBSIDIAN SYNC SUBSCRIPTION: Full drawing file history and synchronization between devices
|
||||
- Multilanguage support: if you'd like to help out by translating the plugin, please get in contact with me.
|
||||
|
||||
@@ -52,8 +52,20 @@ export interface ExcalidrawAutomate {
|
||||
}
|
||||
}
|
||||
):Promise<string>;
|
||||
createSVG (templatePath?:string, embedFont?:boolean):Promise<SVGSVGElement>;
|
||||
createPNG (templatePath?:string):Promise<any>;
|
||||
createSVG (
|
||||
templatePath?:string,
|
||||
embedFont?:boolean,
|
||||
exportSettings?:ExportSettings, //see ExcalidrawAutomate.getExportSettings(boolean,boolean)
|
||||
loader?:EmbeddedFilesLoader, //see ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?)
|
||||
theme?:string
|
||||
):Promise<SVGSVGElement>;
|
||||
createPNG (
|
||||
templatePath?:string,
|
||||
scale?:number,
|
||||
exportSettings?:ExportSettings, //see ExcalidrawAutomate.getExportSettings(boolean,boolean)
|
||||
loader?:EmbeddedFilesLoader, //see ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?)
|
||||
theme?:string
|
||||
):Promise<any>;
|
||||
wrapText (text:string, lineLen:number):string;
|
||||
addRect (topX:number, topY:number, width:number, height:number):string;
|
||||
addDiamond (topX:number, topY:number, width:number, height:number):string;
|
||||
@@ -84,7 +96,7 @@ export interface ExcalidrawAutomate {
|
||||
}
|
||||
):string ;
|
||||
addImage(topX:number, topY:number, imageFile: TFile):Promise<string>;
|
||||
addLaTex(topX:number, topY:number, tex: string, color?:string):Promise<string>;
|
||||
addLaTex(topX:number, topY:number, tex: string):Promise<string>;
|
||||
connectObjects (
|
||||
objectA: string,
|
||||
connectionA: ConnectionPoint,
|
||||
@@ -134,6 +146,10 @@ export interface ExcalidrawAutomate {
|
||||
view: ExcalidrawView, //the excalidraw view receiving the drop
|
||||
pointerPosition: {x:number, y:number} //the pointer position on canvas at the time of drop
|
||||
}):boolean;
|
||||
mostRecentMarkdownSVG:SVGSVGElement; //Markdown renderer will drop a copy of the most recent SVG here for debugging purposes
|
||||
//utility functions to generate EmbeddedFilesLoaderand ExportSettings objects
|
||||
getEmbeddedFilesLoader(isDark?:boolean):EmbeddedFilesLoader;
|
||||
getExportSettings(withBackground:boolean,withTheme:boolean):ExportSettings;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "1.4.13",
|
||||
"version": "1.4.18",
|
||||
"minAppVersion": "0.12.16",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import { FileId } from "@zsviczian/excalidraw/types/element/types";
|
||||
import { BinaryFileData, DataURL } from "@zsviczian/excalidraw/types/types";
|
||||
import { link } from "fs";
|
||||
import { App, MarkdownRenderer, Notice, TFile } from "obsidian";
|
||||
import { CASCADIA_FONT, fileid, FRONTMATTER_KEY_CSS, FRONTMATTER_KEY_FONT, FRONTMATTER_KEY_FONTCOLOR, IMAGE_TYPES, nanoid, VIRGIL_FONT } from "./constants";
|
||||
import { CASCADIA_FONT, fileid, 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 ExcalidrawView, { ExportSettings } from "./ExcalidrawView";
|
||||
import { ExportSettings } from "./ExcalidrawView";
|
||||
import { t } from "./lang/helpers";
|
||||
import de from "./lang/locale/de";
|
||||
import { tex2dataURL } from "./LaTeX";
|
||||
import ExcalidrawPlugin from "./main";
|
||||
import {debug, errorlog, getImageSize, getLinkParts, LinkParts, svgToBase64 } from "./Utils";
|
||||
import {errorlog, getImageSize, getLinkParts, LinkParts, svgToBase64 } from "./Utils";
|
||||
|
||||
export declare type MimeType = "image/svg+xml" | "image/png" | "image/jpeg" | "image/gif" | "application/octet-stream";
|
||||
export type FileData = BinaryFileData & {
|
||||
@@ -50,9 +48,13 @@ export class EmbeddedFile {
|
||||
if(!this.linkParts.width) this.linkParts.width = this.plugin.settings.mdSVGwidth;
|
||||
if(!this.linkParts.height) this.linkParts.height = this.plugin.settings.mdSVGmaxHeight;
|
||||
this.file = this.plugin.app.metadataCache.getFirstLinkpathDest(this.linkParts.path,hostPath);
|
||||
if(!this.file) {
|
||||
new Notice("Excalidraw Warning: could not find image file: "+imgPath,5000);
|
||||
}
|
||||
}
|
||||
|
||||
private fileChanged():boolean {
|
||||
if(!this.file) return false;
|
||||
return this.mtime !=this.file.stat.mtime;
|
||||
}
|
||||
|
||||
@@ -282,24 +284,25 @@ const getSVGData = async (app: App, file: TFile): Promise<DataURL> => {
|
||||
}
|
||||
|
||||
const convertMarkdownToSVG = async (plugin: ExcalidrawPlugin, file: TFile, linkParts: LinkParts): Promise<DataURL> => {
|
||||
|
||||
//const text = await plugin.app.vault.cachedRead(file);
|
||||
//1.
|
||||
//get the markdown text
|
||||
const [text,line] = await getTransclusion(linkParts,plugin.app,file);
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
|
||||
|
||||
//2.
|
||||
//get styles
|
||||
let fontName:string;
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
let fontDef:string;
|
||||
let font = plugin.settings.mdFont;
|
||||
let fontName = plugin.settings.mdFont;
|
||||
if (fileCache?.frontmatter && fileCache.frontmatter[FRONTMATTER_KEY_FONT]!=null) {
|
||||
font = fileCache.frontmatter[FRONTMATTER_KEY_FONT];
|
||||
fontName = fileCache.frontmatter[FRONTMATTER_KEY_FONT];
|
||||
}
|
||||
switch(font){
|
||||
case "Virgil":
|
||||
case "": fontName = "Virgil";fontDef = VIRGIL_FONT; break;
|
||||
case "Cascadia": fontName = "Cascadia";fontDef = CASCADIA_FONT; break;
|
||||
switch(fontName){
|
||||
case "Virgil": fontDef = VIRGIL_FONT; break;
|
||||
case "Cascadia": fontDef = CASCADIA_FONT; break;
|
||||
case "": fontDef = ""; break;
|
||||
default:
|
||||
const f = plugin.app.metadataCache.getFirstLinkpathDest(font,file.path);
|
||||
const f = plugin.app.metadataCache.getFirstLinkpathDest(fontName,file.path);
|
||||
if(f) {
|
||||
const ab = await plugin.app.vault.readBinary(f);
|
||||
const mimeType=f.extension.startsWith("woff")?"application/font-woff":"font/truetype";
|
||||
@@ -308,48 +311,120 @@ const convertMarkdownToSVG = async (plugin: ExcalidrawPlugin, file: TFile, linkP
|
||||
const split = fontDef.split(";base64,",2);
|
||||
fontDef = split[0]+";charset=utf-8;base64,"+split[1];
|
||||
} else {
|
||||
fontName = "Virgil";fontDef = VIRGIL_FONT;
|
||||
fontDef = "";
|
||||
}
|
||||
}
|
||||
|
||||
const fontColor = fileCache?.frontmatter ? fileCache.frontmatter[FRONTMATTER_KEY_FONTCOLOR] : plugin.settings.mdFontColor;
|
||||
|
||||
//construct SVG
|
||||
const div = createDiv();
|
||||
div.setAttribute("xmlns","http://www.w3.org/1999/xhtml");
|
||||
div.style.fontFamily = fontName;
|
||||
if(fontColor) div.style.color = fontColor;
|
||||
div.style.fontSize = "initial";
|
||||
await MarkdownRenderer.renderMarkdown(text,div,file.path,plugin);
|
||||
div.querySelectorAll(":scope > *[class^='frontmatter']").forEach((el)=>div.removeChild(el));
|
||||
//brute force to swap <a> to <u> because links anyway don't work when the foreignObject is
|
||||
//encapsulated in an img element. <a> does not render with an underline, <u> will.
|
||||
const xml = (new XMLSerializer().serializeToString(div)).replaceAll("<a ","<u ").replaceAll("</a>","</u>");
|
||||
let svgStyle = ' width="'+linkParts.width+'px" height="100%"';
|
||||
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 && plugin.settings.mdCSS && plugin.settings.mdCSS!="") {
|
||||
const f = plugin.app.metadataCache.getFirstLinkpathDest(plugin.settings.mdCSS,file.path);
|
||||
if(f) {
|
||||
style += "\n"+await plugin.app.vault.read(f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//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 = () => '<svg xmlns="http://www.w3.org/2000/svg"'+svgStyle+'>'
|
||||
const svg = (xml:string,xmlFooter:string,style?:string) =>
|
||||
'<svg xmlns="http://www.w3.org/2000/svg"'+svgStyle+'>'
|
||||
+ (style?'<style>'+style+'</style>':'')
|
||||
+ '<foreignObject x="0" y="0"'+foreignObjectStyle+'>'
|
||||
+ xml
|
||||
+ '</foreignObject><defs><style>'
|
||||
+ fontDef
|
||||
+ '</style></defs></svg>';
|
||||
+ xml
|
||||
+ xmlFooter //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/286#issuecomment-982179639
|
||||
+ '</foreignObject>'
|
||||
+ (fontDef!==""
|
||||
? ('<defs><style>' + fontDef + '</style></defs>')
|
||||
: "")
|
||||
+ '</svg>';
|
||||
|
||||
|
||||
//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";
|
||||
if(fontColor && fontColor!="") mdDIV.style.color = fontColor;
|
||||
|
||||
await MarkdownRenderer.renderMarkdown(text,mdDIV,file.path,plugin);
|
||||
mdDIV.querySelectorAll(":scope > *[class^='frontmatter']").forEach((el)=>mdDIV.removeChild(el));
|
||||
|
||||
//5.1
|
||||
//get SVG size.
|
||||
//First I need to create a fully self contained copy of the document to convert
|
||||
//blank styles into inline styles using computedStyle
|
||||
const iframeHost = document.body.createDiv();
|
||||
iframeHost.style.display = "none";
|
||||
const iframe = iframeHost.createEl("iframe");
|
||||
const iframeDoc = iframe.contentWindow.document;
|
||||
if(style) {
|
||||
const styleEl = iframeDoc.createElement("style");
|
||||
styleEl.type = "text/css";
|
||||
styleEl.innerHTML = style;
|
||||
iframeDoc.head.appendChild(styleEl);
|
||||
}
|
||||
const stylingDIV = iframeDoc.importNode(mdDIV,true)
|
||||
iframeDoc.body.appendChild(stylingDIV);
|
||||
const footerDIV = createDiv();
|
||||
footerDIV.setAttribute("class","excalidraw-md-footer");
|
||||
iframeDoc.body.appendChild(footerDIV);
|
||||
|
||||
iframeDoc.body.querySelectorAll("*").forEach((el:HTMLElement)=>{
|
||||
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(),"image/svg+xml");
|
||||
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 height = svgEl.firstElementChild.firstElementChild.scrollHeight;
|
||||
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"';
|
||||
return svgToBase64(svg()) as DataURL;
|
||||
mdDIV.style.height = (svgHeight-footerHeight)+"px";
|
||||
mdDIV.style.overflow = "hidden";
|
||||
const xml = (new XMLSerializer().serializeToString(mdDIV))
|
||||
const finalSVG = svg(xml,'<div class="excalidraw-md-footer"></div>',style);
|
||||
plugin.ea.mostRecentMarkdownSVG = parser.parseFromString(finalSVG,"image/svg+xml").firstElementChild as SVGSVGElement;
|
||||
return svgToBase64(finalSVG) as DataURL;
|
||||
}
|
||||
|
||||
const getDataURL = async (file: ArrayBuffer,mimeType: string): Promise<DataURL> => {
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
FileId,
|
||||
} from "@zsviczian/excalidraw/types/element/types";
|
||||
import {
|
||||
MarkdownRenderer,
|
||||
normalizePath,
|
||||
TFile
|
||||
} from "obsidian"
|
||||
@@ -19,7 +18,7 @@ import {
|
||||
VIEW_TYPE_EXCALIDRAW,
|
||||
MAX_IMAGE_SIZE,
|
||||
} from "./constants";
|
||||
import { debug, embedFontsInSVG, errorlog, getPNG, getSVG, scaleLoadedImage, wrapText } from "./Utils";
|
||||
import { debug, embedFontsInSVG, errorlog, getPNG, getSVG, isObsidianThemeDark, scaleLoadedImage, wrapText } from "./Utils";
|
||||
import { AppState, DataURL } from "@zsviczian/excalidraw/types/types";
|
||||
import { EmbeddedFilesLoader, FileData, MimeType } from "./EmbeddedFileLoader";
|
||||
import { tex2dataURL } from "./LaTeX";
|
||||
@@ -78,15 +77,15 @@ export interface ExcalidrawAutomate {
|
||||
createSVG (
|
||||
templatePath?:string,
|
||||
embedFont?:boolean,
|
||||
exportSettings?:ExportSettings,
|
||||
loader?:EmbeddedFilesLoader,
|
||||
exportSettings?:ExportSettings, //see ExcalidrawAutomate.getExportSettings(boolean,boolean)
|
||||
loader?:EmbeddedFilesLoader, //see ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?)
|
||||
theme?:string
|
||||
):Promise<SVGSVGElement>;
|
||||
createPNG (
|
||||
templatePath?:string,
|
||||
scale?:number,
|
||||
exportSettings?:ExportSettings,
|
||||
loader?:EmbeddedFilesLoader,
|
||||
exportSettings?:ExportSettings, //see ExcalidrawAutomate.getExportSettings(boolean,boolean)
|
||||
loader?:EmbeddedFilesLoader, //see ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?)
|
||||
theme?:string
|
||||
):Promise<any>;
|
||||
wrapText (text:string, lineLen:number):string;
|
||||
@@ -169,7 +168,10 @@ export interface ExcalidrawAutomate {
|
||||
view: ExcalidrawView, //the excalidraw view receiving the drop
|
||||
pointerPosition: {x:number, y:number} //the pointer position on canvas at the time of drop
|
||||
}):boolean;
|
||||
renderMarkdown(text:string,el:HTMLElement):Promise<void>;
|
||||
mostRecentMarkdownSVG:SVGSVGElement; //Markdown renderer will drop a copy of the most recent SVG here for debugging purposes
|
||||
//utility functions to generate EmbeddedFilesLoaderand ExportSettings objects
|
||||
getEmbeddedFilesLoader(isDark?:boolean):EmbeddedFilesLoader;
|
||||
getExportSettings(withBackground:boolean,withTheme:boolean):ExportSettings;
|
||||
}
|
||||
|
||||
declare let window: any;
|
||||
@@ -359,9 +361,26 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin):Promise<E
|
||||
templatePath?:string,
|
||||
embedFont:boolean = false,
|
||||
exportSettings?:ExportSettings,
|
||||
loader:EmbeddedFilesLoader = new EmbeddedFilesLoader(this.plugin),
|
||||
loader?:EmbeddedFilesLoader,
|
||||
theme?:string,
|
||||
):Promise<SVGSVGElement> {
|
||||
if(!theme) {
|
||||
theme = this.plugin.settings.previewMatchObsidianTheme
|
||||
? (isObsidianThemeDark() ? "dark" : "light")
|
||||
: (!this.plugin.settings.exportWithTheme
|
||||
? "light"
|
||||
: undefined);
|
||||
}
|
||||
if(theme && !exportSettings) {
|
||||
exportSettings = {
|
||||
withBackground: this.plugin.settings.exportBackground,
|
||||
withTheme: true
|
||||
}
|
||||
}
|
||||
if(!loader) {
|
||||
const loader = new EmbeddedFilesLoader(this.plugin,theme?(theme==="dark"):undefined);
|
||||
}
|
||||
|
||||
return await createSVG(
|
||||
templatePath,
|
||||
embedFont,
|
||||
@@ -378,9 +397,26 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin):Promise<E
|
||||
templatePath?:string,
|
||||
scale:number=1,
|
||||
exportSettings?:ExportSettings,
|
||||
loader:EmbeddedFilesLoader = new EmbeddedFilesLoader(this.plugin),
|
||||
loader?:EmbeddedFilesLoader,
|
||||
theme?:string
|
||||
) {
|
||||
if(!theme) {
|
||||
theme = this.plugin.settings.previewMatchObsidianTheme
|
||||
? (isObsidianThemeDark() ? "dark" : "light")
|
||||
: (!this.plugin.settings.exportWithTheme
|
||||
? "light"
|
||||
: undefined);
|
||||
}
|
||||
if(theme && !exportSettings) {
|
||||
exportSettings = {
|
||||
withBackground: this.plugin.settings.exportBackground,
|
||||
withTheme: true
|
||||
}
|
||||
}
|
||||
if(!loader) {
|
||||
const loader = new EmbeddedFilesLoader(this.plugin,theme?(theme==="dark"):undefined);
|
||||
}
|
||||
|
||||
return await createPNG(
|
||||
templatePath,
|
||||
scale,
|
||||
@@ -750,9 +786,13 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin):Promise<E
|
||||
return await this.targetView.addElements(elements,repositionToCursor,save,this.imagesDict);
|
||||
},
|
||||
onDropHook:null,
|
||||
async renderMarkdown(text:string,el:HTMLElement):Promise<void> {
|
||||
await MarkdownRenderer.renderMarkdown(text,el,'',this.plugin);
|
||||
}
|
||||
mostRecentMarkdownSVG:null,
|
||||
getEmbeddedFilesLoader(isDark?:boolean):EmbeddedFilesLoader {
|
||||
return new EmbeddedFilesLoader(this.plugin,isDark);
|
||||
},
|
||||
getExportSettings(withBackground:boolean,withTheme:boolean):ExportSettings{
|
||||
return {withBackground,withTheme};
|
||||
},
|
||||
};
|
||||
await initFonts();
|
||||
return window.ExcalidrawAutomate;
|
||||
@@ -928,14 +968,15 @@ export async function createPNG(
|
||||
templatePath:string = undefined,
|
||||
scale:number=1,
|
||||
exportSettings:ExportSettings,
|
||||
loader:EmbeddedFilesLoader = new EmbeddedFilesLoader(this.plugin),
|
||||
loader:EmbeddedFilesLoader,
|
||||
forceTheme:string = undefined,
|
||||
canvasTheme: string = undefined,
|
||||
canvasBackgroundColor: string = undefined,
|
||||
automateElements: ExcalidrawElement[] = [],
|
||||
plugin: ExcalidrawPlugin,
|
||||
) {
|
||||
const template = templatePath ? (await getTemplate(this.plugin,templatePath,true,loader)) : null;
|
||||
if(!loader) loader = new EmbeddedFilesLoader(plugin);
|
||||
const template = templatePath ? (await getTemplate(plugin,templatePath,true,loader)) : null;
|
||||
let elements = template?.elements ?? [];
|
||||
elements = elements.concat(automateElements);
|
||||
return await getPNG(
|
||||
@@ -962,13 +1003,14 @@ export async function createSVG(
|
||||
templatePath:string = undefined,
|
||||
embedFont:boolean = false,
|
||||
exportSettings:ExportSettings,
|
||||
loader:EmbeddedFilesLoader = new EmbeddedFilesLoader(this.plugin),
|
||||
loader:EmbeddedFilesLoader,
|
||||
forceTheme:string = undefined,
|
||||
canvasTheme: string = undefined,
|
||||
canvasBackgroundColor: string = undefined,
|
||||
automateElements: ExcalidrawElement[] = [],
|
||||
plugin: ExcalidrawPlugin,
|
||||
):Promise<SVGSVGElement> {
|
||||
if(!loader) loader = new EmbeddedFilesLoader(plugin);
|
||||
const template = templatePath ? (await getTemplate(plugin,templatePath,true,loader)) : null;
|
||||
let elements = template?.elements ?? [];
|
||||
elements = elements.concat(automateElements);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { App, TFile } from "obsidian";
|
||||
import { App, Notice, TFile } from "obsidian";
|
||||
import {
|
||||
nanoid,
|
||||
FRONTMATTER_KEY_CUSTOM_PREFIX,
|
||||
@@ -641,7 +641,10 @@ export class ExcalidrawData {
|
||||
*/
|
||||
public setFile(fileId:FileId, data:EmbeddedFile) {
|
||||
//always store absolute path because in case of paste, relative path may not resolve ok
|
||||
if(!data) return;
|
||||
this.files.set(fileId,data);
|
||||
|
||||
if(!data.file) return;
|
||||
this.plugin.filesMaster.set(
|
||||
fileId,
|
||||
{
|
||||
|
||||
@@ -58,6 +58,8 @@ const REG_LINKINDEX_INVALIDCHARS = /[<>:"\\|?*]/g;
|
||||
|
||||
export const addFiles = async (files:FileData[], view: ExcalidrawView,isDark?:boolean) => {
|
||||
if(!files || files.length === 0 || !view) return;
|
||||
files = files.filter((f)=>(f.size.height>0) && (f.size.width>0)); //height will be zero when file does not exisig in case of broken embedded file links
|
||||
if(files.length === 0) return;
|
||||
const [dirty, scene] = scaleLoadedImage(view.getScene(),files);
|
||||
if(isDark===undefined) isDark = scene.appState.theme;
|
||||
if(dirty) {
|
||||
@@ -71,6 +73,7 @@ export const addFiles = async (files:FileData[], view: ExcalidrawView,isDark?:bo
|
||||
for(const f of files) {
|
||||
if(view.excalidrawData.hasFile(f.id)) {
|
||||
const embeddedFile = view.excalidrawData.getFile(f.id);
|
||||
|
||||
embeddedFile.setImage(
|
||||
f.dataURL,
|
||||
f.mimeType,
|
||||
@@ -861,7 +864,8 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
};
|
||||
|
||||
const el: ExcalidrawElement[] = this.excalidrawAPI.getSceneElements();
|
||||
const newIds = newElements.map((e)=>e.id);
|
||||
const el: ExcalidrawElement[] = this.excalidrawAPI.getSceneElements().filter((e:ExcalidrawElement)=>!newIds.includes(e.id));
|
||||
let st: AppState = this.excalidrawAPI.getAppState();
|
||||
|
||||
if(repositionToCursor) newElements = repositionElementsToCursor(newElements,currentPosition,true);
|
||||
@@ -891,7 +895,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
);
|
||||
this.excalidrawData.setFile(images[k].id,embeddedFile);
|
||||
}
|
||||
if(images[k].tex) {
|
||||
if(images[k].latex) {
|
||||
this.excalidrawData.setEquation(
|
||||
images[k].id,
|
||||
{
|
||||
@@ -1398,4 +1402,4 @@ export default class ExcalidrawView extends TextFileView {
|
||||
export function getTextMode(data:string):TextMode {
|
||||
const parsed = data.search("excalidraw-plugin: parsed\n")>-1 || data.search("excalidraw-plugin: locked\n")>-1; //locked for backward compatibility
|
||||
return parsed ? TextMode.parsed : TextMode.raw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export const FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS = "excalidraw-link-brackets";
|
||||
export const FRONTMATTER_KEY_DEFAULT_MODE = "excalidraw-default-mode";
|
||||
export const FRONTMATTER_KEY_FONT = "excalidraw-font";
|
||||
export const FRONTMATTER_KEY_FONTCOLOR = "excalidraw-font-color";
|
||||
export const FRONTMATTER_KEY_CSS = "excalidraw-css-file";
|
||||
export const FRONTMATTER_KEY_MD_STYLE = "excalidraw-css";
|
||||
export const VIEW_TYPE_EXCALIDRAW = "excalidraw";
|
||||
export const ICON_NAME = "excalidraw-icon";
|
||||
export const MAX_COLORS = 5;
|
||||
|
||||
@@ -143,6 +143,14 @@ export default {
|
||||
MD_DEFAULT_COLOR_NAME: "The default font color to use for embedded markdown files.",
|
||||
MD_DEFAULT_COLOR_DESC: 'Set this to allowed css color names e.g. "steelblue" (https://www.w3schools.com/colors/colors_names.asp), or a valid hexadecimal color e.g. "#e67700". ' +
|
||||
'You can override this setting by adding the following frontmatter-key to the embedded markdown file: "excalidraw-font-color: color_name_or_rgbhex"',
|
||||
MD_CSS_NAME: "CSS file",
|
||||
MD_CSS_DESC: "The filename of the CSS to apply to markdown embeds. Provide the filename with extension (e.g. 'md-embed.css'). The css file may also be a plain " +
|
||||
"markdown file (e.g. 'md-embed-css.md'), just make sure the content is written using valid css syntax. " +
|
||||
"If you need to look at the HTML code you are applying the CSS to, then open Obsidian Developer Console (CTRL+SHIFT+i) and type in the follwoing command: " +
|
||||
'"ExcalidrawAutomate.mostRecentMarkdownSVG". This will display the most recent SVG generated by Excalidraw. ' +
|
||||
"Setting the font-family in the css is has limitations. By default only your operating system's standard fonts are available (see README for details). "+
|
||||
"You can add one custom font beyond that using the setting above. " +
|
||||
'You can override this css setting by adding the following frontmatter-key to the embedded markdown file: "excalidraw-css: css_file_in_valut|css-snippet".',
|
||||
EMBED_HEAD: "Embed & Export",
|
||||
EMBED_PREVIEW_SVG_NAME: "Display SVG in markdown preview",
|
||||
EMBED_PREVIEW_SVG_DESC: "The default is to display drawings as SVG images in the markdown preview. Turning this feature off, the markdown preview will display the drawing as an embedded PNG image.",
|
||||
|
||||
@@ -118,9 +118,9 @@ export default {
|
||||
SYNC_EXCALIDRAW_DESC: "如果 *.excalidraw 文件的修改比 *.md 文件的修改更新" +
|
||||
",会根据 .excalidraw 文件更新 .md 文件中的绘图",
|
||||
COMPATIBILITY_MODE_NAME: "以旧格式创建新绘图",
|
||||
COMPATIBILITY_MODE_DESC: "通过启用此功能图形,您可以使用功能区图标、命令面板操作、 "+
|
||||
"并且文件浏览器将仍旧保留 *.excalidraw 文件。 此设置还将" +
|
||||
"关闭你打开旧格式绘图时的提醒消息",
|
||||
COMPATIBILITY_MODE_DESC: "启用此功能后,你使用功能区图标、命令面板、"+
|
||||
"或文件浏览器创建的绘图都将是旧格式 *.excalidraw 文件。 此设置还将" +
|
||||
"关闭你打开并编辑旧格式绘图文件时的提醒消息",
|
||||
EXPERIMENTAL_HEAD: "实验性特性",
|
||||
EXPERIMENTAL_DESC: "这些设置不会立即生效,只有在刷新文件资源管理器或重新启动 Obsidian 时才会生效。",
|
||||
FILETYPE_NAME: "在文件浏览器中给所有的 Excalidraw 文件加上 ✏️ 标识符",
|
||||
|
||||
13
src/main.ts
13
src/main.ts
@@ -155,8 +155,8 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
const self = this;
|
||||
this.loadMathJax();
|
||||
process.env.REACT_APP_LIBRARY_URL = "https://libraries.excalidraw.com/";
|
||||
process.env.REACT_APP_LIBRARY_BACKEND = "https://us-central1-excalidraw-room-persistence.cloudfunctions.net/libraries";
|
||||
// process.env.REACT_APP_LIBRARY_URL = "https://libraries.excalidraw.com/";
|
||||
// process.env.REACT_APP_LIBRARY_BACKEND = "https://us-central1-excalidraw-room-persistence.cloudfunctions.net/libraries";
|
||||
}
|
||||
|
||||
private loadMathJax() {
|
||||
@@ -460,6 +460,8 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
if(!this.settings.matchThemeTrigger) return;
|
||||
//@ts-ignore
|
||||
if(m[0]?.oldValue === m[0]?.target?.getAttribute("class")) return;
|
||||
//@ts-ignore
|
||||
if(m[0]?.oldValue?.includes("theme-dark") === m[0]?.target?.classList?.contains("theme-dark")) return;
|
||||
const theme = isObsidianThemeDark() ? "dark":"light";
|
||||
const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
leaves.forEach((leaf:WorkspaceLeaf)=> {
|
||||
@@ -487,8 +489,11 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
private experimentalFileTypeDisplay() {
|
||||
const insertFiletype = (el: HTMLElement) => {
|
||||
if(el.childElementCount != 1) return;
|
||||
//@ts-ignore
|
||||
if(this.isExcalidrawFile(this.app.vault.getAbstractFileByPath(el.attributes["data-path"].value))) {
|
||||
const filename = el.getAttribute("data-path");
|
||||
if(!filename) return;
|
||||
const f = this.app.vault.getAbstractFileByPath(filename);
|
||||
if(!f || !(f instanceof TFile)) return;
|
||||
if(this.isExcalidrawFile(f)) {
|
||||
el.insertBefore(createDiv({cls:"nav-file-tag",text:this.settings.experimentalFileTag}),el.firstChild);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -56,6 +56,7 @@ export interface ExcalidrawSettings {
|
||||
mdSVGmaxHeight: number,
|
||||
mdFont: string,
|
||||
mdFontColor: string,
|
||||
mdCSS: string,
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
@@ -109,6 +110,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
mdSVGmaxHeight: 800,
|
||||
mdFont: "Virgil",
|
||||
mdFontColor: "Black",
|
||||
mdCSS: ""
|
||||
}
|
||||
|
||||
export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
@@ -127,7 +129,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
const plugin = this.plugin;
|
||||
this.applyDebounceTimer = window.setTimeout(() => {
|
||||
plugin.saveSettings();
|
||||
}, 200);
|
||||
}, 100);
|
||||
if(requestReloadDrawings) this.requestReloadDrawings = true;
|
||||
}
|
||||
|
||||
@@ -478,6 +480,18 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
this.applySettingsUpdate(true);
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(t("MD_CSS_NAME"))
|
||||
.setDesc(t("MD_CSS_DESC"))
|
||||
.addText(text => text
|
||||
.setPlaceholder("filename of css file in vault")
|
||||
.setValue(this.plugin.settings.mdCSS)
|
||||
.onChange((value) => {
|
||||
this.requestReloadDrawings=true;
|
||||
this.plugin.settings.mdCSS = value;
|
||||
this.applySettingsUpdate(true);
|
||||
}));
|
||||
|
||||
this.containerEl.createEl('h1', {text: t("EMBED_HEAD")});
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"1.4.13": "0.12.16",
|
||||
"1.4.18": "0.12.16",
|
||||
"1.4.2": "0.11.13"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user