diff --git a/ea-scripts/Change shape of selected elements.md b/ea-scripts/Change shape of selected elements.md index fc647fc..5cbea9b 100644 --- a/ea-scripts/Change shape of selected elements.md +++ b/ea-scripts/Change shape of selected elements.md @@ -5,12 +5,36 @@ The script allows you to change the shape of selected Rectangles, Diamonds and E ```javascript */ -const shapesDispaly=["○ ellipse","□ rectangle","◇ diamond"]; -const shapes=["ellipse","rectangle","diamond"]; -elements = ea.getViewSelectedElements().filter(el=>shapes.contains(el.type)); -newShape = await utils.suggester(shapesDispaly, shapes); -if(!newShape) return; +const boxShapesDispaly=["○ ellipse","□ rectangle","◇ diamond"]; +const boxShapes=["ellipse","rectangle","diamond"]; +const lineShapesDispaly=["- line","⭢ arrow"]; +const lineShapes=["line","arrow"]; + +let editedElements = []; + +let elements = ea.getViewSelectedElements().filter(el=>boxShapes.contains(el.type)); +if (elements.length>0) { + newShape = await utils.suggester(boxShapesDispaly, boxShapes, "Change shape of 'box' type elements in selection"); + if(newShape) { + editedElements = elements; + elements.forEach(el=>el.type = newShape); + } +} + +elements = ea.getViewSelectedElements().filter(el=>lineShapes.contains(el.type)); +if (elements.length>0) { + newShape = await utils.suggester(lineShapesDispaly, lineShapes, "Change shape of 'line' type elements in selection"); + if(newShape) { + editedElements = editedElements.concat(elements); + elements.forEach((el)=>{ + el.type = newShape; + if(newShape === "arrow") { + el.endArrowhead = "triangle"; + } + }); + } +} + +ea.copyViewElementsToEAforEditing(editedElements); -elements.forEach(el=>el.type = newShape); -ea.copyViewElementsToEAforEditing(elements); ea.addElementsToView(false,false); \ No newline at end of file diff --git a/manifest.json b/manifest.json index f052933..bc1db45 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-excalidraw-plugin", "name": "Excalidraw", - "version": "1.6.13", + "version": "1.6.14", "minAppVersion": "0.12.16", "description": "An Obsidian plugin to edit and view Excalidraw drawings", "author": "Zsolt Viczian", diff --git a/src/ExcalidrawData.ts b/src/ExcalidrawData.ts index b596d1a..f528d53 100644 --- a/src/ExcalidrawData.ts +++ b/src/ExcalidrawData.ts @@ -83,28 +83,58 @@ export const REGEX_LINK = { }, }; -export const REG_LINKINDEX_HYPERLINK = /^\w+:\/\//; - //added \n at and of DRAWING_REG: https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/357 const DRAWING_REG = /\n# Drawing\n[^`]*(```json\n)([\s\S]*?)```\n/gm; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/182 const DRAWING_REG_FALLBACK = /\n# Drawing\n(```json\n)?(.*)(```)?(%%)?/gm; -const DRAWING_COMPRESSED_REG = /\n# Drawing\n[^`]*(```compressed\-json\n)([\s\S]*?)```\n/gm; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/182 -const DRAWING_COMPRESSED_REG_FALLBACK = /\n# Drawing\n(```compressed\-json\n)?(.*)(```)?(%%)?/gm; +const DRAWING_COMPRESSED_REG = /(\n# Drawing\n[^`]*(?:```compressed\-json\n))([\s\S]*?)(```\n)/gm; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/182 +const DRAWING_COMPRESSED_REG_FALLBACK = /(\n# Drawing\n(?:```compressed\-json\n)?)(.*)((```)?(%%)?)/gm; +export const REG_LINKINDEX_HYPERLINK = /^\w+:\/\//; + +const isCompressedMD = (data:string):boolean => { + return data.match(/```compressed\-json\n/gm) !== null; +} + +const getDecompressedScene = (data:string):[string,IteratorResult] => { + let res = data.matchAll(DRAWING_COMPRESSED_REG); + + //In case the user adds a text element with the contents "# Drawing\n" + let parts; + parts = res.next(); + if (parts.done) { + //did not find a match + res = data.matchAll(DRAWING_COMPRESSED_REG_FALLBACK); + parts = res.next(); + } + if (parts.value && parts.value.length > 1) { + return [decompress(parts.value[2]),parts]; + } + return [null,parts]; +} + +export const changeThemeOfExcalidrawMD = (data:string):string => { + const compressed = isCompressedMD(data); + let scene = compressed ? getDecompressedScene(data)[0] : data; + if(!scene) return data; + if(isObsidianThemeDark) { + if((scene.match(/"theme"\s*:\s*"light"\s*,/g)||[]).length === 1) { + scene = scene.replace(/"theme"\s*:\s*"light"\s*,/,`"theme": "dark",`); + } + } else { + if((scene.match(/"theme"\s*:\s*"dark"\s*,/g)||[]).length === 1) { + scene = scene.replace(/"theme"\s*:\s*"dark"\s*,/,`"theme": "light",`); + } + } + if(compressed) { + return data.replace(DRAWING_COMPRESSED_REG,`$1${compress(scene)}$3`); + } + return scene; +} + export function getJSON(data: string): { scene: string; pos: number } { let res; - if(data.match(/```compressed\-json\n/gm)) { - res = data.matchAll(DRAWING_COMPRESSED_REG); - - //In case the user adds a text element with the contents "# Drawing\n" - let parts; - parts = res.next(); - if (parts.done) { - //did not find a match - res = data.matchAll(DRAWING_COMPRESSED_REG_FALLBACK); - parts = res.next(); - } - if (parts.value && parts.value.length > 1) { - const result = decompress(parts.value[2]); + if(isCompressedMD(data)) { + const [result,parts] = getDecompressedScene(data); + if(result) { return { scene: result.substring(0, result.lastIndexOf("}") + 1), pos: parts.value.index, diff --git a/src/ExcalidrawView.ts b/src/ExcalidrawView.ts index a41e52b..3140c79 100644 --- a/src/ExcalidrawView.ts +++ b/src/ExcalidrawView.ts @@ -146,6 +146,7 @@ export default class ExcalidrawView extends TextFileView { public excalidrawAPI: any = null; public excalidrawWrapperRef: React.MutableRefObject = null; private justLoaded: boolean = false; + private preventAutozoomOnLoad: boolean = false; private plugin: ExcalidrawPlugin; private dirty: string = null; public autosaveTimer: any = null; @@ -693,7 +694,7 @@ export default class ExcalidrawView extends TextFileView { this.textIsParsed_Element.hide(); } if (reload) { - await this.save(false); + await this.save(false,true); this.updateContainerSize(); this.excalidrawAPI.history.clear(); //to avoid undo replacing links with parsed text } @@ -758,6 +759,7 @@ export default class ExcalidrawView extends TextFileView { const loadOnModifyTrigger = (file && file === this.file); if (loadOnModifyTrigger) { this.data = await this.app.vault.cachedRead(file); + this.preventAutozoomOnLoad = true; } if (fullreload) { await this.excalidrawData.loadData(this.data, this.file, this.textMode); @@ -1837,7 +1839,8 @@ export default class ExcalidrawView extends TextFileView { viewModeEnabled = st.viewModeEnabled; if (this.justLoaded) { this.justLoaded = false; - this.zoomToFit(false); + if(!this.preventAutozoomOnLoad) this.zoomToFit(false); + this.preventAutozoomOnLoad = false; this.previousSceneVersion = getSceneVersion(et); return; } diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts index ce2bad0..59582f6 100644 --- a/src/lang/locale/en.ts +++ b/src/lang/locale/en.ts @@ -45,6 +45,7 @@ export default { "Insert LaTeX formula (e.g. \\binom{n}{k} = \\frac{n!}{k!(n-k)!})", ENTER_LATEX: "Enter a valid LaTeX expression", TRAY_MODE: "Toggle property-panel tray-mode", + SEARCH: "Search for text in drawing", //ExcalidrawView.ts INSTALL_SCRIPT_BUTTON: "Install or update Excalidraw Scripts", @@ -256,6 +257,8 @@ export default { "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_WIKILINK_NAME: "Embed SVG or PNG as Wiki link", + EMBED_WIKILINK_DESC: "Toggle ON: Excalidraw will embed a [[wiki link]]. Toggle OFF: Excalidraw will embed a [markdown](link).", EXPORT_PNG_SCALE_NAME: "PNG export image scale", EXPORT_PNG_SCALE_DESC: "The size-scale of the exported PNG image", EXPORT_BACKGROUND_NAME: "Export image with background", diff --git a/src/main.ts b/src/main.ts index c1035a8..64f6b94 100644 --- a/src/main.ts +++ b/src/main.ts @@ -15,6 +15,8 @@ import { loadMathJax, Scope, request, + MetadataCache, + FrontMatterCache, } from "obsidian"; import { BLANK_DRAWING, @@ -42,7 +44,7 @@ import { VIRGIL_DATAURL, } from "./constants"; import ExcalidrawView, { TextMode } from "./ExcalidrawView"; -import { getMarkdownDrawingSection } from "./ExcalidrawData"; +import { changeThemeOfExcalidrawMD, getMarkdownDrawingSection } from "./ExcalidrawData"; import { ExcalidrawSettings, DEFAULT_SETTINGS, @@ -97,6 +99,7 @@ declare module "obsidian" { } export default class ExcalidrawPlugin extends Plugin { + private excalidrawFiles: Set = new Set(); public excalidrawFileModes: { [file: string]: string } = {}; private _loaded: boolean = false; public settings: ExcalidrawSettings; @@ -757,6 +760,55 @@ export default class ExcalidrawPlugin extends Plugin { }, }); + this.addCommand({ + id: "search-text", + name: t("SEARCH"), + checkCallback: (checking: boolean) => { + if (checking) { + return ( + this.app.workspace.activeLeaf.view.getViewType() === + VIEW_TYPE_EXCALIDRAW + ); + } + const view = this.app.workspace.activeLeaf.view; + if (view instanceof ExcalidrawView) { + (async ()=>{ + const ea = this.ea; + ea.reset(); + ea.setView(view); + const elements = ea.getViewElements().filter(el=>el.type==="text"); + if(elements.length === 0) return; + let text = await ScriptEngine.inputPrompt(this.app,"Search for","use quotation marks for exact match",""); + if(!text) return; + const res = text.matchAll(/"(.*?)"/g) + let query:string[] = []; + let parts; + while (!(parts = res.next()).done) { + query.push(parts.value[1]); + } + text = text.replaceAll(/"(.*?)"/g, ""); + query = query.concat(text.split(" ").filter(s=>s.length!==0)); + const match = elements + .filter((el:any)=>query + .some(q=>el + .rawText + .toLowerCase() + .replaceAll("\n"," ") + .match(q.toLowerCase()) + )); + if(match.length === 0) { + new Notice("I could not find a matching text element"); + return; + } + ea.selectElementsInView(match); + ea.getExcalidrawAPI().zoomToFit(match,this.settings.zoomToFitMaxLevel,0.05); + })(); + return true; + } + return false; + }, + }); + this.addCommand({ id: "export-png", name: t("EXPORT_PNG"), @@ -1253,14 +1305,9 @@ export default class ExcalidrawPlugin extends Plugin { if (!(file instanceof TFile)) { return; } - const isExcalidarwFile = - //@ts-ignore - (file.unsafeCachedData && - //@ts-ignore - file.unsafeCachedData.search( - /---[\r\n]+[\s\S]*excalidraw-plugin:\s*\w+[\r\n]+[\s\S]*---/gm, - ) > -1) || - file.extension == "excalidraw"; + + const isExcalidarwFile = this.excalidrawFiles.has(file); + this.updateFileCache(file,undefined,true); if (!isExcalidarwFile) { return; } @@ -1373,9 +1420,36 @@ export default class ExcalidrawPlugin extends Plugin { activeLeafChangeEventHandler, ), ); + + const metaCache:MetadataCache = self.app.metadataCache; + //@ts-ignore + metaCache.getCachedFiles().forEach((filename:string) => { + const fm = metaCache.getCache(filename)?.frontmatter; + if ((fm && Object.keys(fm).contains(FRONTMATTER_KEY)) || + filename.match(/\.excalidraw$/) + ) { + self.updateFileCache( + self.app.vault.getAbstractFileByPath(filename) as TFile, fm + ); + } + }); + this.registerEvent(metaCache.on("changed", (file, data, cache) => this.updateFileCache(file, cache?.frontmatter))); }); } + updateFileCache(file: TFile, frontmatter?: FrontMatterCache, deleted: boolean = false) { + if(frontmatter) { + const isExcalidrawFile = Object.keys(frontmatter).contains(FRONTMATTER_KEY); + this.excalidrawFiles.add(file); + return; + } + if(!deleted && file.extension==="excalidraw") { + this.excalidrawFiles.add(file); + return; + } + this.excalidrawFiles.delete(file); + } + onunload() { window.removeEventListener("keydown", this.onKeyDown, false); window.removeEventListener("keyup", this.onKeyUp, false); @@ -1413,7 +1487,8 @@ export default class ExcalidrawPlugin extends Plugin { ) const editor = activeView.editor; if (this.settings.embedType === "excalidraw") { - editor.replaceSelection(`![[${data}]]`); + editor.replaceSelection(this.settings.embedWikiLink + ? `![[${data}]]` : `![](${encodeURI(data)})`); editor.focus(); return; } @@ -1425,11 +1500,15 @@ export default class ExcalidrawPlugin extends Plugin { file.path,"."+this.settings.embedType.toLowerCase() ); - await this.app.vault.create(filepath, ""); - //await sleep(200); - + const imgFile = this.app.vault.getAbstractFileByPath(filepath); + if(!imgFile) { + await this.app.vault.create(filepath, ""); + await sleep(200); + } editor.replaceSelection( - `![[${filename}]]\n%%[[${data}|🖋 Edit in Excalidraw]]%%`, + this.settings.embedWikiLink + ? `![[${filename}]]\n%%[[${data}|🖋 Edit in Excalidraw]]%%` + : `![](${encodeURI(filename)})\n%%[🖋 Edit in Excalidraw](${encodeURI(data)})%%`, ); editor.focus(); } @@ -1515,18 +1594,7 @@ export default class ExcalidrawPlugin extends Plugin { ) { let data = await this.app.vault.read(template); if (data) { - if(this.settings.matchTheme) { - if(isObsidianThemeDark) { - if((data.match(/"theme"\s*:\s*"light"\s*,/g)||[]).length === 1) { - data = data.replace(/"theme"\s*:\s*"light"\s*,/,`"theme": "dark",`); - } - } else { - if((data.match(/"theme"\s*:\s*"dark"\s*,/g)||[]).length === 1) { - data = data.replace(/"theme"\s*:\s*"dark"\s*,/,`"theme": "light",`); - } - } - } - return data; + return this.settings.matchTheme ? changeThemeOfExcalidrawMD(data) : data; } } } @@ -1634,4 +1702,5 @@ export default class ExcalidrawPlugin extends Plugin { const fileCache = f ? this.app.metadataCache.getFileCache(f) : null; return !!fileCache?.frontmatter && !!fileCache.frontmatter[FRONTMATTER_KEY]; } + } diff --git a/src/settings.ts b/src/settings.ts index aad1a6c..487faca 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -50,6 +50,7 @@ export interface ExcalidrawSettings { autoexportPNG: boolean; autoexportExcalidraw: boolean; embedType: "excalidraw" | "PNG" | "SVG"; + embedWikiLink: boolean, syncExcalidraw: boolean; compatibilityMode: boolean; experimentalFileType: boolean; @@ -114,6 +115,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = { autoexportPNG: false, autoexportExcalidraw: false, embedType: "excalidraw", + embedWikiLink: true, syncExcalidraw: false, experimentalFileType: false, experimentalFileTag: "✏️", @@ -789,6 +791,18 @@ export class ExcalidrawSettingTab extends PluginSettingTab { }); }); + new Setting(containerEl) + .setName(t("EMBED_WIKILINK_NAME")) + .setDesc(fragWithHTML(t("EMBED_WIKILINK_DESC"))) + .addToggle((toggle) => + toggle + .setValue(this.plugin.settings.embedWikiLink) + .onChange(async (value) => { + this.plugin.settings.embedWikiLink = value; + this.applySettingsUpdate(); + }), + ); + let scaleText: HTMLDivElement; new Setting(containerEl) diff --git a/versions.json b/versions.json index 4bf956e..1f77985 100644 --- a/versions.json +++ b/versions.json @@ -1,4 +1,4 @@ { - "1.6.13": "0.12.16", + "1.6.14": "0.12.16", "1.4.2": "0.11.13" }