From eaf4a8e39e16777c4004f39959997b0e2934622f Mon Sep 17 00:00:00 2001 From: MassiveBox Date: Wed, 16 Jul 2025 15:57:14 +0200 Subject: [PATCH] Improve error handling --- public/i18n/en_US.json | 8 +++-- src/config.ts | 5 +-- src/editor.ts | 34 ++++++++---------- src/errors.ts | 82 ++++++++++++++++++++++++++++++++++++++---- src/file.ts | 2 +- src/index.ts | 14 ++++---- src/protyle.ts | 9 ++--- 7 files changed, 109 insertions(+), 45 deletions(-) diff --git a/public/i18n/en_US.json b/public/i18n/en_US.json index 494c8ac..1513654 100644 --- a/public/i18n/en_US.json +++ b/public/i18n/en_US.json @@ -3,12 +3,14 @@ "editWhiteboard": "Edit whiteboard", "editShortcut": "Edit selected whiteboard", "errNoFileID": "File ID missing - couldn't open file.", - "errNotAWhiteboard": "You must select a whiteboard, not a regular image.", + "errNotAWhiteboard": "You must select a whiteboard, not a regular image. Usage instructions", "errSyncIDNotFound": "Couldn't find SyncID in document for drawing, make sure you're trying to edit a whiteboard that is included in at least a note.", "errCreateUnknown": "Unknown error while creating editor, please try again.", "errInvalidBackgroundColor": "Invalid background color! Please enter an HEX color, like #000000 (black) or #FFFFFF (white). The old background color will be used.", - "msgMustSelect": "Select a whiteboard in your document by left-clicking it, then use this icon/shortcut to open the editor directly.", - "usageInstructionsLink": " Usage instructions", + "errMultipleSyncIDs": "Multiple syncIDs found in documents. Remove the drawings that don't exist from your documents.\n Sync conflict copies can cause this error, so make sure to delete them.\nFile IDs (the part you can change in the Rename menu) must be unique across all documents.\nFull explanation", + "errUnchangedProtyle": "Make sure the image you're trying to edit still exists in your documents.", + "errSaveGeneric": "Error saving! The current drawing has been copied to your clipboard. You may need to create a new drawing and paste it there.", + "errMustSelect": "Select a whiteboard in your document by left-clicking it, then use this icon/shortcut to open the editor directly. Usage instructions", "whiteboard": "Whiteboard", "settings": { "name": "js-draw Plugin Settings", diff --git a/src/config.ts b/src/config.ts index 7c4bfca..116a1af 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,8 +1,9 @@ import {PluginFile} from "@/file"; import {CONFIG_FILENAME, JSON_MIME, STORAGE_PATH} from "@/const"; -import {Plugin, showMessage} from "siyuan"; +import {Plugin} from "siyuan"; import {SettingUtils} from "@/libs/setting-utils"; import {getFirstDefined} from "@/helper"; +import {ErrorReporter, InvalidBackgroundColorError} from "@/errors"; export interface Options { dialogOnDesktop: boolean @@ -90,7 +91,7 @@ export class PluginConfigViewer { let color = data.backgroundDropdown === "CUSTOM" ? data.background : data.backgroundDropdown; if(!PluginConfig.validateColor(color)) { - showMessage(this.plugin.i18n.errInvalidBackgroundColor, 0, 'error'); + ErrorReporter.error(new InvalidBackgroundColorError()); data.background = this.config.options.editorOptions.background; this.settingUtils.set('background', data.background); } diff --git a/src/editor.ts b/src/editor.ts index 7267593..8fbeefb 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -10,12 +10,16 @@ import Editor, { Vec2, Viewport } from "js-draw"; -import {Dialog, getFrontend, openTab, Plugin, showMessage} from "siyuan"; +import {Dialog, getFrontend, openTab, Plugin} from "siyuan"; import {findSyncIDInProtyle, replaceSyncID} from "@/protyle"; import DrawJSPlugin from "@/index"; import {EditorOptions} from "@/config"; import 'js-draw/styles'; -import {SyncIDNotFoundError, UnchangedProtyleError} from "@/errors"; +import { + ErrorReporter, + GenericSaveError, InternationalizedError, NoFileIDError, SyncIDNotFoundError, + UnchangedProtyleError +} from "@/errors"; export class PluginEditor { @@ -65,7 +69,7 @@ export class PluginEditor { let syncID = await findSyncIDInProtyle(fileID); if(syncID == null) { - throw new SyncIDNotFoundError(fileID); + throw new SyncIDNotFoundError(); } instance.setSyncID(syncID); await instance.restoreOrInitFile(defaultEditorOptions); @@ -157,12 +161,13 @@ export class PluginEditor { saveButton.setDisabled(false); }, 500); } catch (error) { - showMessage("Error saving! The current drawing has been copied to your clipboard. You may need to create a new drawing and paste it there.", 0, 'error'); - if(error instanceof UnchangedProtyleError) { - showMessage("Make sure the image you're trying to edit still exists in your documents.", 0, 'error'); + if(error instanceof InternationalizedError) { + ErrorReporter.error(error); + }else{ + ErrorReporter.error(new GenericSaveError()); + console.error(error); } await navigator.clipboard.writeText(svgElem.outerHTML); - console.error(error); console.log("Couldn't save SVG: ", svgElem.outerHTML) return; } @@ -184,7 +189,7 @@ export class EditorManager { let editor = await PluginEditor.create(fileID, p.config.options.editorOptions); instance.setEditor(editor); }catch (error) { - EditorManager.handleCreationError(error, p); + ErrorReporter.error(error); } return instance; } @@ -195,28 +200,19 @@ export class EditorManager { async init() { const fileID = this.data.fileID; if (fileID == null) { - alert(p.i18n.errNoFileID); + ErrorReporter.error(new NoFileIDError()); return; } try { const editor = await PluginEditor.create(fileID, p.config.options.editorOptions); this.element.appendChild(editor.getElement()); }catch (error){ - EditorManager.handleCreationError(error, p); + ErrorReporter.error(error); } } }); } - static handleCreationError(error: any, p: DrawJSPlugin) { - console.error(error); - let errorTxt = p.i18n.errCreateUnknown; - if(error instanceof SyncIDNotFoundError) { - errorTxt = p.i18n.errSyncIDNotFound; - } - showMessage(errorTxt, 0, 'error'); - } - toTab(p: Plugin) { openTab({ app: p.app, diff --git a/src/errors.ts b/src/errors.ts index 914bd9c..f7bf264 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -1,12 +1,80 @@ +import {showMessage} from "siyuan"; -export class SyncIDNotFoundError extends Error { - readonly fileID: string; +export class InternationalizedError extends Error { + readonly key: string; - constructor(fileID: string) { - super(`SyncID not found for file ${fileID}`); - this.fileID = fileID; - Object.setPrototypeOf(this, new.target.prototype); + constructor(key: string) { + super(key); + this.key = key; } } -export class UnchangedProtyleError extends Error {} \ No newline at end of file +export class ErrorReporter { + + static i18n: any; + + constructor(i18n: any) { + ErrorReporter.i18n = i18n; + } + + static error(err: Error, timeout?: number) { + console.error(err); + let errorTxt = err.message; + if(err instanceof InternationalizedError) { + errorTxt = ErrorReporter.i18n[err.key]; + } + if(!timeout) { + timeout = 0; + } + showMessage(errorTxt, timeout, 'error'); + } + +} + +export class SyncIDNotFoundError extends InternationalizedError { + constructor() { + super('errSyncIDNotFound'); + } +} + +export class UnchangedProtyleError extends InternationalizedError { + constructor() { + super('errUnchangedProtyle'); + } +} + +export class MultipleSyncIDsError extends InternationalizedError { + constructor() { + super('errMultipleSyncIDs'); + } +} + +export class GenericSaveError extends InternationalizedError { + constructor() { + super('errSaveGeneric'); + } +} + +export class NotAWhiteboardError extends InternationalizedError { + constructor() { + super('errNotAWhiteboard'); + } +} + +export class InvalidBackgroundColorError extends InternationalizedError { + constructor() { + super('errInvalidBackgroundColor'); + } +} + +export class NoFileIDError extends InternationalizedError { + constructor() { + super('errNoFileID'); + } +} + +export class MustSelectError extends InternationalizedError { + constructor() { + super('errMustSelect'); + } +} \ No newline at end of file diff --git a/src/file.ts b/src/file.ts index dc2a86c..a3a1a10 100644 --- a/src/file.ts +++ b/src/file.ts @@ -52,7 +52,7 @@ abstract class PluginFileBase { protected toFile(customFilename?: string): File { let filename = customFilename || this.fileName; const blob = new Blob([this.content], { type: this.mimeType }); - return new File([blob], filename, { type: this.mimeType }); + return new File([blob], filename, { type: this.mimeType, lastModified: Date.now() }); } } diff --git a/src/index.ts b/src/index.ts index 9872ac7..6dbee74 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import {Plugin, Protyle, showMessage} from 'siyuan'; +import {Plugin, Protyle} from 'siyuan'; import { getMarkdownBlock, loadIcons, @@ -10,6 +10,7 @@ import {migrate} from "@/migration"; import {EditorManager} from "@/editor"; import {PluginConfig, PluginConfigViewer} from "@/config"; import {Analytics} from "@/analytics"; +import {ErrorReporter, MustSelectError, NotAWhiteboardError} from "@/errors"; export default class DrawJSPlugin extends Plugin { @@ -18,6 +19,7 @@ export default class DrawJSPlugin extends Plugin { async onload() { + new ErrorReporter(this.i18n); loadIcons(this); EditorManager.registerTab(this); migrate() @@ -55,7 +57,7 @@ export default class DrawJSPlugin extends Plugin { langKey: "editShortcut", hotkey: "⌥⇧D", callback: async () => { - await this.editSelectedImg(); + this.editSelectedImg().catch(e => ErrorReporter.error(e, 5000)); }, }) @@ -63,7 +65,7 @@ export default class DrawJSPlugin extends Plugin { icon: "iconDraw", title: this.i18n.editShortcut, callback: async () => { - await this.editSelectedImg(); + await this.editSelectedImg().catch(e => ErrorReporter.error(e, 5000)); }, position: "left" }) @@ -82,14 +84,12 @@ export default class DrawJSPlugin extends Plugin { let selectedImg = document.getElementsByClassName('img--select'); if(selectedImg.length == 0) { - showMessage(this.i18n.msgMustSelect + this.i18n.usageInstructionsLink, 5000, 'info'); - return; + throw new MustSelectError(); } let ids = imgSrcToIDs(findImgSrc(selectedImg[0] as HTMLElement)); if(ids == null) { - showMessage(this.i18n.errNotAWhiteboard + + this.i18n.usageInstructionsLink, 5000, 'error'); - return; + throw new NotAWhiteboardError(); } void this.analytics.sendEvent('edit'); (await EditorManager.create(ids.fileID, this)).open(this); diff --git a/src/protyle.ts b/src/protyle.ts index 5c3e842..6812864 100644 --- a/src/protyle.ts +++ b/src/protyle.ts @@ -1,5 +1,6 @@ import {getBlockByID, sql, updateBlock} from "@/api"; import {assetPathToIDs, IDsToAssetPath} from "@/helper"; +import {MultipleSyncIDsError} from "@/errors"; export async function findSyncIDInProtyle(fileID: string, iter?: number): Promise { @@ -15,11 +16,7 @@ export async function findSyncIDInProtyle(fileID: string, iter?: number): Promis if(syncID == null) { syncID = ids.syncID; }else if(ids.syncID !== syncID) { - throw new Error( - "Multiple syncIDs found in documents. Remove the drawings that don't exist from your documents.\n" + - "Sync conflict copies can cause this error, so make sure to delete them, or at least the js-draw drawings they contain.\n" + - "File IDs must be unique. Close this editor tab now." - ); + throw new MultipleSyncIDsError(); } } } @@ -82,7 +79,7 @@ export async function replaceBlockContent( } function extractImageSourcesFromMarkdown(markdown: string, mustStartWith?: string) { - const imageRegex = /!\[.*?\]\((.*?)\)/g; // only get images + const imageRegex = /!\[.*?\]\(([^)\s]+)(?:\s+"[^"]+")?\)/g; // only get images return Array.from(markdown.matchAll(imageRegex)) .map(match => match[1]) .filter(source => source.startsWith(mustStartWith)) // discard other images