From d0b8b605db93c51b5ce2501a482c57432b45bfa6 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 30 Jan 2021 12:33:29 -0500 Subject: [PATCH] Add note errors (#1329) * Update _addAnkiNote to track errors * Change comparison * Update anki note adding to show errors * Fix template * Show errors when Anki card creation behaves unexpectedly * Update some errors related to anki media injection * Update addAnkiNote error handling * Improve Anki errors * Simplify error messages related to template rendering --- ext/bg/js/anki-note-builder.js | 6 +- ext/bg/js/anki.js | 53 ++++++--- ext/bg/js/backend.js | 71 +++++++----- ext/bg/js/clipboard-reader.js | 4 +- .../js/settings/anki-templates-controller.js | 32 ++++-- ext/mixed/css/display.css | 8 ++ ext/mixed/display-templates.html | 5 + ext/mixed/js/display-generator.js | 17 +++ ext/mixed/js/display.js | 106 ++++++++++++++---- 9 files changed, 230 insertions(+), 72 deletions(-) diff --git a/ext/bg/js/anki-note-builder.js b/ext/bg/js/anki-note-builder.js index eae5fbe4..e1399f66 100644 --- a/ext/bg/js/anki-note-builder.js +++ b/ext/bg/js/anki-note-builder.js @@ -117,7 +117,11 @@ class AnkiNoteBuilder { try { return await this._renderTemplate(templates, data, marker); } catch (e) { - if (errors) { errors.push(e); } + if (errors) { + const error = new Error(`Template render error for {${marker}}`); + error.data = {error: e}; + errors.push(error); + } return `{${marker}-render-error}`; } }); diff --git a/ext/bg/js/anki.js b/ext/bg/js/anki.js index 68d9fc43..251e0e0c 100644 --- a/ext/bg/js/anki.js +++ b/ext/bg/js/anki.js @@ -162,22 +162,49 @@ class AnkiConnect { } async _invoke(action, params) { - const response = await fetch(this._server, { - method: 'POST', - mode: 'cors', - cache: 'default', - credentials: 'omit', - redirect: 'follow', - referrerPolicy: 'no-referrer', - body: JSON.stringify({action, params, version: this._localVersion}) - }); - const result = await response.json(); + let response; + try { + response = await fetch(this._server, { + method: 'POST', + mode: 'cors', + cache: 'default', + credentials: 'omit', + redirect: 'follow', + referrerPolicy: 'no-referrer', + body: JSON.stringify({action, params, version: this._localVersion}) + }); + } catch (e) { + const error = new Error('Anki connection failure'); + error.data = {action, params}; + throw error; + } + + if (!response.ok) { + const error = new Error(`Anki connection error: ${response.status}`); + error.data = {action, params, status: response.status}; + throw error; + } + + let responseText = null; + let result; + try { + responseText = await response.text(); + result = JSON.parse(responseText); + } catch (e) { + const error = new Error('Invalid Anki response'); + error.data = {action, params, status: response.status, responseText}; + throw error; + } + if (isObject(result)) { - const error = result.error; - if (typeof error !== 'undefined') { - throw new Error(`AnkiConnect error: ${error}`); + const apiError = result.error; + if (typeof apiError !== 'undefined') { + const error = new Error(`Anki error: ${apiError}`); + error.data = {action, params, status: response.status, apiError}; + throw error; } } + return result; } diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 9a8844c5..b0648ac5 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -1539,13 +1539,14 @@ class Backend { let clipboardImageFileName = null; let clipboardText = null; let audioFileName = null; + const errors = []; try { if (screenshotDetails !== null) { screenshotFileName = await this._injectAnkNoteScreenshot(ankiConnect, timestamp, definitionDetails, screenshotDetails); } } catch (e) { - // NOP + errors.push(serializeError(e)); } try { @@ -1553,7 +1554,7 @@ class Backend { clipboardImageFileName = await this._injectAnkNoteClipboardImage(ankiConnect, timestamp, definitionDetails); } } catch (e) { - // NOP + errors.push(serializeError(e)); } try { @@ -1561,7 +1562,7 @@ class Backend { clipboardText = await this._clipboardReader.getText(); } } catch (e) { - // NOP + errors.push(serializeError(e)); } try { @@ -1569,34 +1570,50 @@ class Backend { audioFileName = await this._injectAnkNoteAudio(ankiConnect, timestamp, definitionDetails, audioDetails); } } catch (e) { - // NOP + errors.push(serializeError(e)); } - return {screenshotFileName, clipboardImageFileName, clipboardText, audioFileName}; + return { + result: { + screenshotFileName, + clipboardImageFileName, + clipboardText, + audioFileName + }, + errors + }; } async _injectAnkNoteAudio(ankiConnect, timestamp, definitionDetails, details) { const {type, expression, reading} = definitionDetails; - if (type === 'kanji') { - throw new Error('Cannot inject audio for kanji'); - } - if (!reading && !expression) { - throw new Error('Invalid reading and expression'); + if ( + type === 'kanji' || + typeof expression !== 'string' || + typeof reading !== 'string' || + (expression.length === 0 && reading.length === 0) + ) { + return null; } const {sources, customSourceUrl, customSourceType} = details; - const data = await this._downloadDefinitionAudio( - sources, - expression, - reading, - { - textToSpeechVoice: null, - customSourceUrl, - customSourceType, - binary: true, - disableCache: true - } - ); + let data; + try { + data = await this._downloadDefinitionAudio( + sources, + expression, + reading, + { + textToSpeechVoice: null, + customSourceUrl, + customSourceType, + binary: true, + disableCache: true + } + ); + } catch (e) { + // No audio + return null; + } let fileName = this._generateAnkiNoteMediaFileName('yomichan_audio', '.mp3', timestamp, definitionDetails); fileName = fileName.replace(/\]/g, ''); @@ -1611,7 +1628,9 @@ class Backend { const {mediaType, data} = this._getDataUrlInfo(dataUrl); const extension = this._mediaUtility.getFileExtensionFromImageMediaType(mediaType); - if (extension === null) { throw new Error('Unknown image media type'); } + if (extension === null) { + throw new Error('Unknown media type for screenshot image'); + } const fileName = this._generateAnkiNoteMediaFileName('yomichan_browser_screenshot', extension, timestamp, definitionDetails); await ankiConnect.storeMediaFile(fileName, data); @@ -1622,12 +1641,14 @@ class Backend { async _injectAnkNoteClipboardImage(ankiConnect, timestamp, definitionDetails) { const dataUrl = await this._clipboardReader.getImage(); if (dataUrl === null) { - throw new Error('No clipboard image'); + return null; } const {mediaType, data} = this._getDataUrlInfo(dataUrl); const extension = this._mediaUtility.getFileExtensionFromImageMediaType(mediaType); - if (extension === null) { throw new Error('Unknown image media type'); } + if (extension === null) { + throw new Error('Unknown media type for clipboard image'); + } const fileName = this._generateAnkiNoteMediaFileName('yomichan_clipboard_image', extension, timestamp, definitionDetails); await ankiConnect.storeMediaFile(fileName, data); diff --git a/ext/bg/js/clipboard-reader.js b/ext/bg/js/clipboard-reader.js index 8065cb16..ae432246 100644 --- a/ext/bg/js/clipboard-reader.js +++ b/ext/bg/js/clipboard-reader.js @@ -73,7 +73,7 @@ class ClipboardReader { const document = this._document; if (document === null) { - throw new Error('Not supported'); + throw new Error('Clipboard reading not supported in this context'); } let target = this._pasteTarget; @@ -118,7 +118,7 @@ class ClipboardReader { const document = this._document; if (document === null) { - throw new Error('Not supported'); + throw new Error('Clipboard reading not supported in this context'); } let target = this._imagePasteTarget; diff --git a/ext/bg/js/settings/anki-templates-controller.js b/ext/bg/js/settings/anki-templates-controller.js index 125d8e16..31bd1e92 100644 --- a/ext/bg/js/settings/anki-templates-controller.js +++ b/ext/bg/js/settings/anki-templates-controller.js @@ -165,7 +165,7 @@ class AnkiTemplatesController { async _validate(infoNode, field, mode, showSuccessResult, invalidateInput) { const text = this._renderTextInput.value || ''; - const exceptions = []; + const errors = []; let result = `No definition found for ${text}`; try { const optionsContext = this._settingsController.getOptionsContext(); @@ -193,20 +193,36 @@ class AnkiTemplatesController { resultOutputMode, glossaryLayoutMode, compactTags, - errors: exceptions + errors }); result = note.fields.field; } } catch (e) { - exceptions.push(e); + errors.push(e); } - const hasException = exceptions.length > 0; - infoNode.hidden = !(showSuccessResult || hasException); - infoNode.textContent = hasException ? exceptions.map((e) => `${e}`).join('\n') : (showSuccessResult ? result : ''); - infoNode.classList.toggle('text-danger', hasException); + const errorToMessageString = (e) => { + if (isObject(e)) { + let v = e.data; + if (isObject(v)) { + v = v.error; + if (isObject(v)) { + e = v; + } + } + + v = e.message; + if (typeof v === 'string') { return v; } + } + return `${e}`; + }; + + const hasError = errors.length > 0; + infoNode.hidden = !(showSuccessResult || hasError); + infoNode.textContent = hasError ? errors.map(errorToMessageString).join('\n') : (showSuccessResult ? result : ''); + infoNode.classList.toggle('text-danger', hasError); if (invalidateInput) { - this._fieldTemplatesTextarea.dataset.invalid = `${hasException}`; + this._fieldTemplatesTextarea.dataset.invalid = `${hasError}`; } } } diff --git a/ext/mixed/css/display.css b/ext/mixed/css/display.css index 4a1e2324..e13d8f91 100644 --- a/ext/mixed/css/display.css +++ b/ext/mixed/css/display.css @@ -1675,6 +1675,14 @@ button.footer-notification-close-button:active { } +/* Anki errors */ +.anki-note-error-list { + margin: 0; + padding-left: 1.5em; + list-style: disc; +} + + /* Conditional styles */ :root:not([data-enable-search-tags=true]) .tag[data-category=search] { display: none; diff --git a/ext/mixed/display-templates.html b/ext/mixed/display-templates.html index 6b744271..0eb92282 100644 --- a/ext/mixed/display-templates.html +++ b/ext/mixed/display-templates.html @@ -145,6 +145,11 @@ +