From 71d00e0c8d37e27608d950c66b3ca8200ceea079 Mon Sep 17 00:00:00 2001 From: Zsolt Viczian Date: Sun, 25 Sep 2022 17:29:30 +0200 Subject: [PATCH] 1.7.21 --- manifest.json | 2 +- src/EmbeddedFileLoader.ts | 9 +++++- src/ExcalidrawData.ts | 18 ++++++++++-- src/ExcalidrawView.ts | 22 ++++++++++++-- src/dialogs/Messages.ts | 25 ++++++++++++++++ src/lang/locale/en.ts | 8 ++++- src/main.ts | 48 ++++++++++++++++++++++++++++-- src/menu/ActionButton.tsx | 2 +- src/settings.ts | 62 +++++++++++++++++++++++++++++++++++++-- 9 files changed, 182 insertions(+), 14 deletions(-) diff --git a/manifest.json b/manifest.json index 67fbdc6..753e421 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-excalidraw-plugin", "name": "Excalidraw", - "version": "1.7.20", + "version": "1.7.21", "minAppVersion": "0.15.6", "description": "An Obsidian plugin to edit and view Excalidraw drawings", "author": "Zsolt Viczian", diff --git a/src/EmbeddedFileLoader.ts b/src/EmbeddedFileLoader.ts index 6de6f44..4999725 100644 --- a/src/EmbeddedFileLoader.ts +++ b/src/EmbeddedFileLoader.ts @@ -422,7 +422,7 @@ export class EmbeddedFilesLoader { let fontName = plugin.settings.mdFont; if ( fileCache?.frontmatter && - fileCache.frontmatter[FRONTMATTER_KEY_FONT] != null + Boolean(fileCache.frontmatter[FRONTMATTER_KEY_FONT]) ) { fontName = fileCache.frontmatter[FRONTMATTER_KEY_FONT]; } @@ -442,6 +442,13 @@ export class EmbeddedFilesLoader { 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 diff --git a/src/ExcalidrawData.ts b/src/ExcalidrawData.ts index ed951cf..95fa84d 100644 --- a/src/ExcalidrawData.ts +++ b/src/ExcalidrawData.ts @@ -854,6 +854,7 @@ export class ExcalidrawData { let linkIcon = false; let urlIcon = false; let parts; + text = this.parseCheckbox(text); if (text.match(REG_LINKINDEX_HYPERLINK)) { link = text; urlIcon = true; @@ -869,8 +870,8 @@ export class ExcalidrawData { } if (REGEX_LINK.isTransclusion(parts)) { //transclusion //parts.value[1] || parts.value[4] - const contents = (await this.getTransclusion(REGEX_LINK.getLink(parts))) - .contents; + const contents = this.parseCheckbox((await this.getTransclusion(REGEX_LINK.getLink(parts))) + .contents); outString += text.substring(position, parts.value.index) + wrapText( @@ -907,6 +908,16 @@ export class ExcalidrawData { return { parsed: outString, link }; } + private parseCheckbox(text:string):string { + return this.plugin.settings.parseTODO + ? text + .replaceAll(/^- \[\s] /g,`${this.plugin.settings.todo} `) + .replaceAll(/\n- \[\s] /g,`\n${this.plugin.settings.todo} `) + .replaceAll(/^- \[[^\s]] /g,`${this.plugin.settings.done} `) + .replaceAll(/\n- \[[^\s]] /g,`\n${this.plugin.settings.done} `) + : text; + } + /** * Does a quick parse of the raw text. Returns the parsed string if raw text does not include a transclusion. * Return null if raw text includes a transclusion. @@ -936,6 +947,7 @@ export class ExcalidrawData { let linkIcon = false; let urlIcon = false; let parts; + text = this.parseCheckbox(text); if (text.match(REG_LINKINDEX_HYPERLINK)) { link = text; urlIcon = true; @@ -1065,7 +1077,7 @@ export class ExcalidrawData { const equation = this.getEquation(fileId); //const equation = this.equations.get(fileId as FileId); //images should have a single reference, but equations and markdown embeds should have as many as instances of the file in the scene - if(file && (file.file.extension !== "md" || this.plugin.isExcalidrawFile(file.file))) { + if(file && file.file && (file.file.extension !== "md" || this.plugin.isExcalidrawFile(file.file))) { return; } const newId = fileid(); diff --git a/src/ExcalidrawView.ts b/src/ExcalidrawView.ts index 07e35cf..5ecd289 100644 --- a/src/ExcalidrawView.ts +++ b/src/ExcalidrawView.ts @@ -457,6 +457,12 @@ export default class ExcalidrawView extends TextFileView { return; } this.semaphores.saving = true; + + //if there were no changes to the file super save will not save + //and consequently main.ts modifyEventHandler will not fire + //this.reload will not be called + //triggerReload is used to flag if there were no changes but file should be reloaded anyway + let triggerReload:boolean = false; if ( !this.getScene || @@ -500,6 +506,8 @@ export default class ExcalidrawView extends TextFileView { this.semaphores.preventReload = preventReload; await super.save(); + triggerReload = (this.lastSaveTimestamp === this.file.stat.mtime) && + !preventReload && forcesave; this.lastSaveTimestamp = this.file.stat.mtime; this.clearDirty(); @@ -512,7 +520,8 @@ export default class ExcalidrawView extends TextFileView { } } - if (!this.semaphores.autosaving && !this.semaphores.viewunload) { + // !triggerReload means file has not changed. No need to re-export + if (!triggerReload && !this.semaphores.autosaving && !this.semaphores.viewunload) { const autoexportPreference = this.excalidrawData.autoexportPreference; if ( (autoexportPreference === AutoexportPreference.inherit && this.plugin.settings.autoexportSVG) || @@ -542,6 +551,9 @@ export default class ExcalidrawView extends TextFileView { warningUnknowSeriousError(); } this.semaphores.saving = false; + if(triggerReload) { + this.reload(true, this.file); + } } // get the new file content @@ -1003,9 +1015,15 @@ export default class ExcalidrawView extends TextFileView { DISK_ICON_NAME, t("FORCE_SAVE"), async () => { - if (this.semaphores.autosaving) { + if (this.semaphores.autosaving || this.semaphores.saving) { + new Notice("Force Save aborted because saving is in progress)") return; } + if(this.preventReloadResetTimer) { + clearTimeout(this.preventReloadResetTimer); + this.preventReloadResetTimer = null; + } + this.semaphores.preventReload = false; this.semaphores.forceSaving = true; await this.save(false, true); this.plugin.triggerEmbedUpdates(); diff --git a/src/dialogs/Messages.ts b/src/dialogs/Messages.ts index 273b3e1..77daf79 100644 --- a/src/dialogs/Messages.ts +++ b/src/dialogs/Messages.ts @@ -16,6 +16,31 @@ export const RELEASE_NOTES: { [k: string]: string } = { I develop this plugin as a hobby, spending most of my free time doing this. If you'd like to contribute to the on-going work, I have a simple membership scheme with Bronze, Silver and Gold tiers. Many of you have already bought me a coffee. THANK YOU! It really means a lot to me! If you find this plugin valuable, please consider supporting me.
+`, +"1.7.21":` +# New from Excalidraw.com +- Image-mirroring in export preview and in exported SVG [#5700](https://github.com/excalidraw/excalidraw/pull/5700), [#811](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/811), [#617](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/617) + +# New +- Ctrl+s will force-save your drawing and update all your transclusions +- Added setting to parse ${String.fromCharCode(96)}- [ ] ${String.fromCharCode(96)} and ${String.fromCharCode(96)}- [x] ${String.fromCharCode(96)} todo items. Parsing is disabled by default. This feature can be found under "Links and Transclusions" in Plugin Settings. [#819](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/819) + +![image](https://user-images.githubusercontent.com/14358394/192145020-94bdd115-d24f-47c7-86fe-1417c53980c4.png) + + + + +- Added new scripts to the script library + - [Rename Image](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Rename%20Image.md) + - [Text Arch](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Text%20Arch.md) + + + + +# Fixed +- Fixed toast message to display script name on press and hold on mobile and iPad. +- Fixed save error when the embedded image file is not found (i.e. it was moved, renamed, or deleted) + `, "1.7.20":` # New from Excalidraw.com diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts index b49fe15..584ff6f 100644 --- a/src/lang/locale/en.ts +++ b/src/lang/locale/en.ts @@ -77,7 +77,7 @@ export default { FILE_DOES_NOT_EXIST: "File does not exist. Hold down ALT (or ALT+SHIFT) and CLICK link button to create a new file.", FORCE_SAVE: - "Force-save to update transclusions in adjacent panes.\n(Check autosave settings in plugin settings.)", + "Save (will also update transclusions)", RAW: "Change to PREVIEW mode (only effects text-elements with links or transclusions)", PARSED: "Change to RAW mode (only effects text-elements with links or transclusions)", @@ -226,6 +226,12 @@ export default { "In PREVIEW mode, if the Text Element contains a URL link, precede the text with these characters. " + "You can override this setting for a specific drawing by adding " }${FRONTMATTER_KEY_CUSTOM_URL_PREFIX}: "🌐 " to the file's frontmatter.`, + PARSE_TODO_NAME: "Parse todo", + PARSE_TODO_DESC: "Convert '- [ ] ' and '- [x] ' to checkpox and tick in the box.", + TODO_NAME: "Open TODO icon", + TODO_DESC: "Icon to use for open TODO items", + DONE_NAME: "Completed TODO icon", + DONE_DESC: "Icon to use for completed TODO items", HOVERPREVIEW_NAME: "Hover preview without CTRL/CMD key", HOVERPREVIEW_DESC: "Toggle On: In Exalidraw view mode the hover preview for [[wiki links]] will be shown immediately, without the need to hold the CTRL/CMD key. " + diff --git a/src/main.ts b/src/main.ts index 4c727f0..c7bd011 100644 --- a/src/main.ts +++ b/src/main.ts @@ -15,7 +15,8 @@ import { loadMathJax, request, MetadataCache, - FrontMatterCache + FrontMatterCache, + Command } from "obsidian"; import { BLANK_DRAWING, @@ -163,6 +164,7 @@ export default class ExcalidrawPlugin extends Plugin { public fourthFontDef: string = VIRGIL_FONT; private packageMap: WeakMap = new WeakMap(); public leafChangeTimeout: NodeJS.Timeout = null; + private forceSaveCommand:Command; constructor(app: App, manifest: PluginManifest) { super(app, manifest); @@ -362,6 +364,29 @@ export default class ExcalidrawPlugin extends Plugin { }); } + private forceSaveActiveView(checking:boolean):boolean { + if (checking) { + return Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView)); + } + const view = this.app.workspace.getActiveViewOfType(ExcalidrawView); + if (view) { + (async()=>{ + if (view.semaphores.autosaving) { + return; + } + view.semaphores.forceSaving = true; + await view.save(false, true); + view.plugin.triggerEmbedUpdates(); + view.loadSceneFiles(); + view.semaphores.forceSaving = false; + new Notice("Save successful", 1000); + })(); + return true; + } + return false; + } + + private registerInstallCodeblockProcessor() { const codeblockProcessor = async (source: string, el: HTMLElement) => { //Button next to the "List of available scripts" at the top @@ -951,6 +976,13 @@ export default class ExcalidrawPlugin extends Plugin { }, }); + this.forceSaveCommand = this.addCommand({ + id: "save", + hotkeys: [{modifiers: ["Ctrl"], key:"s"}], //See also Poposcope + name: t("FORCE_SAVE"), + checkCallback: (checking:boolean) => this.forceSaveActiveView(checking), + }) + this.addCommand({ id: "toggle-lock", hotkeys: [{ modifiers: ["Ctrl" || "Meta", "Shift"], key: "e" }], @@ -1624,8 +1656,20 @@ export default class ExcalidrawPlugin extends Plugin { if (newActiveviewEV) { const scope = this.app.keymap.getRootScope(); const handler = scope.register(["Mod"], "Enter", () => true); + const overridSaveShortcut = ( + this.forceSaveCommand && + this.forceSaveCommand.hotkeys[0].key === "s" && + this.forceSaveCommand.hotkeys[0].modifiers.includes("Ctrl") + ) + const self = this; + const saveHandler = overridSaveShortcut + ? scope.register(["Ctrl"], "s", () => self.forceSaveActiveView(false)) + : undefined; scope.keys.unshift(scope.keys.pop()); // Force our handler to the front of the list - self.popScope = () => scope.unregister(handler); + self.popScope = () => { + scope.unregister(handler); + Boolean(saveHandler) && scope.unregister(saveHandler); + } } }; self.registerEvent( diff --git a/src/menu/ActionButton.tsx b/src/menu/ActionButton.tsx index f4a07f3..7650d1d 100644 --- a/src/menu/ActionButton.tsx +++ b/src/menu/ActionButton.tsx @@ -42,7 +42,7 @@ export class ActionButton extends React.Component { onPointerDown={() => { this.toastMessageTimeout = window.setTimeout( () => - this.props.view.excalidrawAPI?.setToastMessage(this.props.title), + this.props.view.excalidrawAPI?.setToast({message:this.props.title}), 300, ); }} diff --git a/src/settings.ts b/src/settings.ts index 94def92..096e2fa 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -4,6 +4,7 @@ import { normalizePath, PluginSettingTab, Setting, + TextComponent, TFile, } from "obsidian"; import { VIEW_TYPE_EXCALIDRAW } from "./Constants"; @@ -48,6 +49,9 @@ export interface ExcalidrawSettings { showLinkBrackets: boolean; linkPrefix: string; urlPrefix: string; + parseTODO: boolean; + todo: string; + done: string; hoverPreviewWithoutCTRL: boolean; linkOpacity: number; allowCtrlClick: boolean; //if disabled only the link button in the view header will open links @@ -133,6 +137,9 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = { zoomToFitMaxLevel: 2, linkPrefix: "📍", urlPrefix: "🌐", + parseTODO: false, + todo: "☐", + done: "🗹", hoverPreviewWithoutCTRL: false, linkOpacity: 1, openInAdjacentPane: false, @@ -605,7 +612,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab { .addToggle((toggle) => toggle .setValue(this.plugin.settings.showLinkBrackets) - .onChange(async (value) => { + .onChange(value => { this.plugin.settings.showLinkBrackets = value; this.applySettingsUpdate(true); }), @@ -618,7 +625,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab { text .setPlaceholder(t("INSERT_EMOJI")) .setValue(this.plugin.settings.linkPrefix) - .onChange((value) => { + .onChange(value => { this.plugin.settings.linkPrefix = value; this.applySettingsUpdate(true); }), @@ -631,12 +638,61 @@ export class ExcalidrawSettingTab extends PluginSettingTab { text .setPlaceholder(t("INSERT_EMOJI")) .setValue(this.plugin.settings.urlPrefix) - .onChange(async (value) => { + .onChange(value => { this.plugin.settings.urlPrefix = value; this.applySettingsUpdate(true); }), ); + let todoPrefixSetting:TextComponent, donePrefixSetting:TextComponent; + + new Setting(containerEl) + .setName(t("PARSE_TODO_NAME")) + .setDesc(fragWithHTML(t("PARSE_TODO_DESC"))) + .addToggle((toggle) => + toggle + .setValue(this.plugin.settings.parseTODO) + .onChange(value => { + this.plugin.settings.parseTODO = value; + todoPrefixSetting.setDisabled(!value); + donePrefixSetting.setDisabled(!value); + this.applySettingsUpdate(true); + }) + ); + + new Setting(containerEl) + .setName(t("TODO_NAME")) + .setDesc(fragWithHTML(t("TODO_DESC"))) + .addText((text) => { + todoPrefixSetting = text; + text + .setPlaceholder(t("INSERT_EMOJI")) + .setValue(this.plugin.settings.todo) + .onChange(value => { + this.plugin.settings.todo = value; + this.applySettingsUpdate(true); + }) + } + ); + todoPrefixSetting.setDisabled(!this.plugin.settings.parseTODO); + + new Setting(containerEl) + .setName(t("DONE_NAME")) + .setDesc(fragWithHTML(t("DONE_DESC"))) + .setDisabled(!this.plugin.settings.parseTODO) + .addText((text) => { + donePrefixSetting = text; + text + .setPlaceholder(t("INSERT_EMOJI")) + .setValue(this.plugin.settings.done) + .onChange(value => { + this.plugin.settings.done = value; + this.applySettingsUpdate(true); + }) + } + ); + donePrefixSetting.setDisabled(!this.plugin.settings.parseTODO); + let opacityText: HTMLDivElement; new Setting(containerEl) .setName(t("LINKOPACITY_NAME"))