Improve error handling
All checks were successful
Build on Push and create Release on Tag / build (push) Successful in 1m40s

This commit is contained in:
MassiveBox 2025-07-16 15:57:14 +02:00
parent 05984a8913
commit eaf4a8e39e
7 changed files with 109 additions and 45 deletions

View file

@ -3,12 +3,14 @@
"editWhiteboard": "Edit whiteboard", "editWhiteboard": "Edit whiteboard",
"editShortcut": "Edit selected whiteboard", "editShortcut": "Edit selected whiteboard",
"errNoFileID": "File ID missing - couldn't open file.", "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. <a href='https://s.massive.box/jsdraw-plugin-instructions'>Usage instructions</a>",
"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.", "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.", "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.", "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.", "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.\n<a href='https://git.massive.box/massivebox/siyuan-jsdraw-plugin/wiki/Errors-and-Fixes#multiple-syncids-found'>Full explanation</a>",
"usageInstructionsLink": " <a href='https://s.massive.box/jsdraw-plugin-instructions'>Usage instructions</a>", "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. <a href='https://s.massive.box/jsdraw-plugin-instructions'>Usage instructions</a>",
"whiteboard": "Whiteboard", "whiteboard": "Whiteboard",
"settings": { "settings": {
"name": "js-draw Plugin Settings", "name": "js-draw Plugin Settings",

View file

@ -1,8 +1,9 @@
import {PluginFile} from "@/file"; import {PluginFile} from "@/file";
import {CONFIG_FILENAME, JSON_MIME, STORAGE_PATH} from "@/const"; 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 {SettingUtils} from "@/libs/setting-utils";
import {getFirstDefined} from "@/helper"; import {getFirstDefined} from "@/helper";
import {ErrorReporter, InvalidBackgroundColorError} from "@/errors";
export interface Options { export interface Options {
dialogOnDesktop: boolean dialogOnDesktop: boolean
@ -90,7 +91,7 @@ export class PluginConfigViewer {
let color = data.backgroundDropdown === "CUSTOM" ? data.background : data.backgroundDropdown; let color = data.backgroundDropdown === "CUSTOM" ? data.background : data.backgroundDropdown;
if(!PluginConfig.validateColor(color)) { if(!PluginConfig.validateColor(color)) {
showMessage(this.plugin.i18n.errInvalidBackgroundColor, 0, 'error'); ErrorReporter.error(new InvalidBackgroundColorError());
data.background = this.config.options.editorOptions.background; data.background = this.config.options.editorOptions.background;
this.settingUtils.set('background', data.background); this.settingUtils.set('background', data.background);
} }

View file

@ -10,12 +10,16 @@ import Editor, {
Vec2, Vec2,
Viewport Viewport
} from "js-draw"; } from "js-draw";
import {Dialog, getFrontend, openTab, Plugin, showMessage} from "siyuan"; import {Dialog, getFrontend, openTab, Plugin} from "siyuan";
import {findSyncIDInProtyle, replaceSyncID} from "@/protyle"; import {findSyncIDInProtyle, replaceSyncID} from "@/protyle";
import DrawJSPlugin from "@/index"; import DrawJSPlugin from "@/index";
import {EditorOptions} from "@/config"; import {EditorOptions} from "@/config";
import 'js-draw/styles'; import 'js-draw/styles';
import {SyncIDNotFoundError, UnchangedProtyleError} from "@/errors"; import {
ErrorReporter,
GenericSaveError, InternationalizedError, NoFileIDError, SyncIDNotFoundError,
UnchangedProtyleError
} from "@/errors";
export class PluginEditor { export class PluginEditor {
@ -65,7 +69,7 @@ export class PluginEditor {
let syncID = await findSyncIDInProtyle(fileID); let syncID = await findSyncIDInProtyle(fileID);
if(syncID == null) { if(syncID == null) {
throw new SyncIDNotFoundError(fileID); throw new SyncIDNotFoundError();
} }
instance.setSyncID(syncID); instance.setSyncID(syncID);
await instance.restoreOrInitFile(defaultEditorOptions); await instance.restoreOrInitFile(defaultEditorOptions);
@ -157,12 +161,13 @@ export class PluginEditor {
saveButton.setDisabled(false); saveButton.setDisabled(false);
}, 500); }, 500);
} catch (error) { } 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 InternationalizedError) {
if(error instanceof UnchangedProtyleError) { ErrorReporter.error(error);
showMessage("Make sure the image you're trying to edit still exists in your documents.", 0, 'error'); }else{
ErrorReporter.error(new GenericSaveError());
console.error(error);
} }
await navigator.clipboard.writeText(svgElem.outerHTML); await navigator.clipboard.writeText(svgElem.outerHTML);
console.error(error);
console.log("Couldn't save SVG: ", svgElem.outerHTML) console.log("Couldn't save SVG: ", svgElem.outerHTML)
return; return;
} }
@ -184,7 +189,7 @@ export class EditorManager {
let editor = await PluginEditor.create(fileID, p.config.options.editorOptions); let editor = await PluginEditor.create(fileID, p.config.options.editorOptions);
instance.setEditor(editor); instance.setEditor(editor);
}catch (error) { }catch (error) {
EditorManager.handleCreationError(error, p); ErrorReporter.error(error);
} }
return instance; return instance;
} }
@ -195,28 +200,19 @@ export class EditorManager {
async init() { async init() {
const fileID = this.data.fileID; const fileID = this.data.fileID;
if (fileID == null) { if (fileID == null) {
alert(p.i18n.errNoFileID); ErrorReporter.error(new NoFileIDError());
return; return;
} }
try { try {
const editor = await PluginEditor.create(fileID, p.config.options.editorOptions); const editor = await PluginEditor.create(fileID, p.config.options.editorOptions);
this.element.appendChild(editor.getElement()); this.element.appendChild(editor.getElement());
}catch (error){ }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) { toTab(p: Plugin) {
openTab({ openTab({
app: p.app, app: p.app,

View file

@ -1,12 +1,80 @@
import {showMessage} from "siyuan";
export class SyncIDNotFoundError extends Error { export class InternationalizedError extends Error {
readonly fileID: string; readonly key: string;
constructor(fileID: string) { constructor(key: string) {
super(`SyncID not found for file ${fileID}`); super(key);
this.fileID = fileID; this.key = key;
Object.setPrototypeOf(this, new.target.prototype);
} }
} }
export class UnchangedProtyleError extends Error {} 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');
}
}

View file

@ -52,7 +52,7 @@ abstract class PluginFileBase {
protected toFile(customFilename?: string): File { protected toFile(customFilename?: string): File {
let filename = customFilename || this.fileName; let filename = customFilename || this.fileName;
const blob = new Blob([this.content], { type: this.mimeType }); 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() });
} }
} }

View file

@ -1,4 +1,4 @@
import {Plugin, Protyle, showMessage} from 'siyuan'; import {Plugin, Protyle} from 'siyuan';
import { import {
getMarkdownBlock, getMarkdownBlock,
loadIcons, loadIcons,
@ -10,6 +10,7 @@ import {migrate} from "@/migration";
import {EditorManager} from "@/editor"; import {EditorManager} from "@/editor";
import {PluginConfig, PluginConfigViewer} from "@/config"; import {PluginConfig, PluginConfigViewer} from "@/config";
import {Analytics} from "@/analytics"; import {Analytics} from "@/analytics";
import {ErrorReporter, MustSelectError, NotAWhiteboardError} from "@/errors";
export default class DrawJSPlugin extends Plugin { export default class DrawJSPlugin extends Plugin {
@ -18,6 +19,7 @@ export default class DrawJSPlugin extends Plugin {
async onload() { async onload() {
new ErrorReporter(this.i18n);
loadIcons(this); loadIcons(this);
EditorManager.registerTab(this); EditorManager.registerTab(this);
migrate() migrate()
@ -55,7 +57,7 @@ export default class DrawJSPlugin extends Plugin {
langKey: "editShortcut", langKey: "editShortcut",
hotkey: "⌥⇧D", hotkey: "⌥⇧D",
callback: async () => { 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", icon: "iconDraw",
title: this.i18n.editShortcut, title: this.i18n.editShortcut,
callback: async () => { callback: async () => {
await this.editSelectedImg(); await this.editSelectedImg().catch(e => ErrorReporter.error(e, 5000));
}, },
position: "left" position: "left"
}) })
@ -82,14 +84,12 @@ export default class DrawJSPlugin extends Plugin {
let selectedImg = document.getElementsByClassName('img--select'); let selectedImg = document.getElementsByClassName('img--select');
if(selectedImg.length == 0) { if(selectedImg.length == 0) {
showMessage(this.i18n.msgMustSelect + this.i18n.usageInstructionsLink, 5000, 'info'); throw new MustSelectError();
return;
} }
let ids = imgSrcToIDs(findImgSrc(selectedImg[0] as HTMLElement)); let ids = imgSrcToIDs(findImgSrc(selectedImg[0] as HTMLElement));
if(ids == null) { if(ids == null) {
showMessage(this.i18n.errNotAWhiteboard + + this.i18n.usageInstructionsLink, 5000, 'error'); throw new NotAWhiteboardError();
return;
} }
void this.analytics.sendEvent('edit'); void this.analytics.sendEvent('edit');
(await EditorManager.create(ids.fileID, this)).open(this); (await EditorManager.create(ids.fileID, this)).open(this);

View file

@ -1,5 +1,6 @@
import {getBlockByID, sql, updateBlock} from "@/api"; import {getBlockByID, sql, updateBlock} from "@/api";
import {assetPathToIDs, IDsToAssetPath} from "@/helper"; import {assetPathToIDs, IDsToAssetPath} from "@/helper";
import {MultipleSyncIDsError} from "@/errors";
export async function findSyncIDInProtyle(fileID: string, iter?: number): Promise<string> { export async function findSyncIDInProtyle(fileID: string, iter?: number): Promise<string> {
@ -15,11 +16,7 @@ export async function findSyncIDInProtyle(fileID: string, iter?: number): Promis
if(syncID == null) { if(syncID == null) {
syncID = ids.syncID; syncID = ids.syncID;
}else if(ids.syncID !== syncID) { }else if(ids.syncID !== syncID) {
throw new Error( throw new MultipleSyncIDsError();
"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."
);
} }
} }
} }
@ -82,7 +79,7 @@ export async function replaceBlockContent(
} }
function extractImageSourcesFromMarkdown(markdown: string, mustStartWith?: string) { 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)) return Array.from(markdown.matchAll(imageRegex))
.map(match => match[1]) .map(match => match[1])
.filter(source => source.startsWith(mustStartWith)) // discard other images .filter(source => source.startsWith(mustStartWith)) // discard other images