diff options
| -rw-r--r-- | .eslintrc.json | 1 | ||||
| -rw-r--r-- | ext/bg/background.html | 2 | ||||
| -rw-r--r-- | ext/bg/js/anki-note-builder.js | 100 | ||||
| -rw-r--r-- | ext/bg/js/api.js | 47 | ||||
| -rw-r--r-- | ext/bg/js/backend.js | 15 | ||||
| -rw-r--r-- | ext/bg/js/dictionary.js | 88 | ||||
| -rw-r--r-- | ext/bg/js/settings/anki-templates.js | 8 | ||||
| -rw-r--r-- | ext/bg/settings.html | 1 | ||||
| -rw-r--r-- | ext/mixed/js/core.js | 15 | 
9 files changed, 117 insertions, 160 deletions
diff --git a/.eslintrc.json b/.eslintrc.json index 4ee1f982..2730acb5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -86,7 +86,6 @@                  "toIterable": "readonly",                  "stringReverse": "readonly",                  "promiseTimeout": "readonly", -                "stringReplaceAsync": "readonly",                  "parseUrl": "readonly",                  "EventDispatcher": "readonly",                  "EventListenerCollection": "readonly", diff --git a/ext/bg/background.html b/ext/bg/background.html index f2f70d4d..f6e00bf5 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -22,7 +22,7 @@          <script src="/mixed/js/dom.js"></script>          <script src="/bg/js/anki.js"></script> -        <script src="/bg/js/api.js"></script> +        <script src="/bg/js/anki-note-builder.js"></script>          <script src="/bg/js/mecab.js"></script>          <script src="/bg/js/audio.js"></script>          <script src="/bg/js/backend-api-forwarder.js"></script> diff --git a/ext/bg/js/anki-note-builder.js b/ext/bg/js/anki-note-builder.js new file mode 100644 index 00000000..d0ff8205 --- /dev/null +++ b/ext/bg/js/anki-note-builder.js @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2020  Alex Yatskov <alex@foosoft.net> + * Author: Alex Yatskov <alex@foosoft.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <https://www.gnu.org/licenses/>. + */ + +class AnkiNoteBuilder { +    constructor({renderTemplate}) { +        this._renderTemplate = renderTemplate; +    } + +    async createNote(definition, mode, options, templates) { +        const isKanji = (mode === 'kanji'); +        const tags = options.anki.tags; +        const modeOptions = isKanji ? options.anki.kanji : options.anki.terms; +        const modeOptionsFieldEntries = Object.entries(modeOptions.fields); + +        const note = { +            fields: {}, +            tags, +            deckName: modeOptions.deck, +            modelName: modeOptions.model +        }; + +        for (const [fieldName, fieldValue] of modeOptionsFieldEntries) { +            note.fields[fieldName] = await this.formatField(fieldValue, definition, mode, options, templates, null); +        } + +        if (!isKanji && definition.audio) { +            const audioFields = []; + +            for (const [fieldName, fieldValue] of modeOptionsFieldEntries) { +                if (fieldValue.includes('{audio}')) { +                    audioFields.push(fieldName); +                } +            } + +            if (audioFields.length > 0) { +                note.audio = { +                    url: definition.audio.url, +                    filename: definition.audio.filename, +                    skipHash: '7e2c2f954ef6051373ba916f000168dc', // hash of audio data that should be skipped +                    fields: audioFields +                }; +            } +        } + +        return note; +    } + +    async formatField(field, definition, mode, options, templates, errors=null) { +        const data = { +            marker: null, +            definition, +            group: options.general.resultOutputMode === 'group', +            merge: options.general.resultOutputMode === 'merge', +            modeTermKanji: mode === 'term-kanji', +            modeTermKana: mode === 'term-kana', +            modeKanji: mode === 'kanji', +            compactGlossaries: options.general.compactGlossaries +        }; +        const pattern = /\{([\w-]+)\}/g; +        return await AnkiNoteBuilder.stringReplaceAsync(field, pattern, async (g0, marker) => { +            data.marker = marker; +            try { +                return await this._renderTemplate(templates, data); +            } catch (e) { +                if (errors) { errors.push(e); } +                return `{${marker}-render-error}`; +            } +        }); +    } + +    static stringReplaceAsync(str, regex, replacer) { +        let match; +        let index = 0; +        const parts = []; +        while ((match = regex.exec(str)) !== null) { +            parts.push(str.substring(index, match.index), replacer(...match, match.index, str)); +            index = regex.lastIndex; +        } +        if (parts.length === 0) { +            return Promise.resolve(str); +        } +        parts.push(str.substring(index)); +        return Promise.all(parts).then((v) => v.join('')); +    } +} diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js deleted file mode 100644 index 4e5d81db..00000000 --- a/ext/bg/js/api.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2019-2020  Alex Yatskov <alex@foosoft.net> - * Author: Alex Yatskov <alex@foosoft.net> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program.  If not, see <https://www.gnu.org/licenses/>. - */ - - -function apiTemplateRender(template, data) { -    return _apiInvoke('templateRender', {data, template}); -} - -function _apiInvoke(action, params={}) { -    const data = {action, params}; -    return new Promise((resolve, reject) => { -        try { -            const callback = (response) => { -                if (response !== null && typeof response === 'object') { -                    if (typeof response.error !== 'undefined') { -                        reject(jsonToError(response.error)); -                    } else { -                        resolve(response.result); -                    } -                } else { -                    const message = response === null ? 'Unexpected null response' : `Unexpected response of type ${typeof response}`; -                    reject(new Error(`${message} (${JSON.stringify(data)})`)); -                } -            }; -            const backend = window.yomichanBackend; -            backend.onMessage({action, params}, null, callback); -        } catch (e) { -            reject(e); -            yomichan.triggerOrphaned(e); -        } -    }); -} diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 60a87916..6e5235ed 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -20,10 +20,10 @@  conditionsTestValue, profileConditionsDescriptor  handlebarsRenderDynamic  requestText, requestJson, optionsLoad -dictConfigured, dictTermsSort, dictEnabledSet, dictNoteFormat +dictConfigured, dictTermsSort, dictEnabledSet  audioGetUrl, audioInject  jpConvertReading, jpDistributeFuriganaInflected, jpKatakanaToHiragana -AudioSystem, Translator, AnkiConnect, AnkiNull, Mecab, BackendApiForwarder, JsonSchema, ClipboardMonitor*/ +AnkiNoteBuilder, AudioSystem, Translator, AnkiConnect, AnkiNull, Mecab, BackendApiForwarder, JsonSchema, ClipboardMonitor*/  class Backend {      constructor() { @@ -31,6 +31,7 @@ class Backend {          this.anki = new AnkiNull();          this.mecab = new Mecab();          this.clipboardMonitor = new ClipboardMonitor({getClipboard: this._onApiClipboardGet.bind(this)}); +        this.ankiNoteBuilder = new AnkiNoteBuilder({renderTemplate: this._renderTemplate.bind(this)});          this.options = null;          this.optionsSchema = null;          this.defaultAnkiFieldTemplates = null; @@ -450,7 +451,7 @@ class Backend {              );          } -        const note = await dictNoteFormat(definition, mode, options, templates); +        const note = await this.ankiNoteBuilder.createNote(definition, mode, options, templates);          return this.anki.addNote(note);      } @@ -463,7 +464,7 @@ class Backend {              const notes = [];              for (const definition of definitions) {                  for (const mode of modes) { -                    const note = await dictNoteFormat(definition, mode, options, templates); +                    const note = await this.ankiNoteBuilder.createNote(definition, mode, options, templates);                      notes.push(note);                  }              } @@ -506,7 +507,7 @@ class Backend {      }      async _onApiTemplateRender({template, data}) { -        return handlebarsRenderDynamic(template, data); +        return this._renderTemplate(template, data);      }      async _onApiCommandExec({command, params}) { @@ -810,6 +811,10 @@ class Backend {          definition.screenshotFileName = filename;      } +    async _renderTemplate(template, data) { +        return handlebarsRenderDynamic(template, data); +    } +      static _getTabUrl(tab) {          return new Promise((resolve) => {              chrome.tabs.sendMessage(tab.id, {action: 'getUrl'}, {frameId: 0}, (response) => { diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js index ffeac80a..3dd1d0c1 100644 --- a/ext/bg/js/dictionary.js +++ b/ext/bg/js/dictionary.js @@ -16,8 +16,6 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ -/*global apiTemplateRender*/ -  function dictEnabledSet(options) {      const enabledDictionaryMap = new Map();      for (const [title, {enabled, priority, allowSecondarySearches}] of Object.entries(options.dictionaries)) { @@ -333,89 +331,3 @@ function dictTagsSort(tags) {  function dictFieldSplit(field) {      return field.length === 0 ? [] : field.split(' ');  } - -async function dictFieldFormat(field, definition, mode, options, templates, exceptions) { -    const data = { -        marker: null, -        definition, -        group: options.general.resultOutputMode === 'group', -        merge: options.general.resultOutputMode === 'merge', -        modeTermKanji: mode === 'term-kanji', -        modeTermKana: mode === 'term-kana', -        modeKanji: mode === 'kanji', -        compactGlossaries: options.general.compactGlossaries -    }; -    const markers = dictFieldFormat.markers; -    const pattern = /\{([\w-]+)\}/g; -    return await stringReplaceAsync(field, pattern, async (g0, marker) => { -        if (!markers.has(marker)) { -            return g0; -        } -        data.marker = marker; -        try { -            return await apiTemplateRender(templates, data); -        } catch (e) { -            if (exceptions) { exceptions.push(e); } -            return `{${marker}-render-error}`; -        } -    }); -} -dictFieldFormat.markers = new Set([ -    'audio', -    'character', -    'cloze-body', -    'cloze-prefix', -    'cloze-suffix', -    'dictionary', -    'expression', -    'furigana', -    'furigana-plain', -    'glossary', -    'glossary-brief', -    'kunyomi', -    'onyomi', -    'reading', -    'screenshot', -    'sentence', -    'tags', -    'url' -]); - -async function dictNoteFormat(definition, mode, options, templates) { -    const isKanji = (mode === 'kanji'); -    const tags = options.anki.tags; -    const modeOptions = isKanji ? options.anki.kanji : options.anki.terms; -    const modeOptionsFieldEntries = Object.entries(modeOptions.fields); - -    const note = { -        fields: {}, -        tags, -        deckName: modeOptions.deck, -        modelName: modeOptions.model -    }; - -    for (const [fieldName, fieldValue] of modeOptionsFieldEntries) { -        note.fields[fieldName] = await dictFieldFormat(fieldValue, definition, mode, options, templates); -    } - -    if (!isKanji && definition.audio) { -        const audioFields = []; - -        for (const [fieldName, fieldValue] of modeOptionsFieldEntries) { -            if (fieldValue.includes('{audio}')) { -                audioFields.push(fieldName); -            } -        } - -        if (audioFields.length > 0) { -            note.audio = { -                url: definition.audio.url, -                filename: definition.audio.filename, -                skipHash: '7e2c2f954ef6051373ba916f000168dc', -                fields: audioFields -            }; -        } -    } - -    return note; -} diff --git a/ext/bg/js/settings/anki-templates.js b/ext/bg/js/settings/anki-templates.js index 244ec42e..b1665048 100644 --- a/ext/bg/js/settings/anki-templates.js +++ b/ext/bg/js/settings/anki-templates.js @@ -17,8 +17,9 @@   */  /*global getOptionsContext, getOptionsMutable, settingsSaveOptions -ankiGetFieldMarkers, ankiGetFieldMarkersHtml, dictFieldFormat -apiOptionsGet, apiTermsFind, apiGetDefaultAnkiFieldTemplates*/ +ankiGetFieldMarkers, ankiGetFieldMarkersHtml +apiOptionsGet, apiTermsFind, apiGetDefaultAnkiFieldTemplates, apiTemplateRender +AnkiNoteBuilder*/  function onAnkiFieldTemplatesReset(e) {      e.preventDefault(); @@ -92,7 +93,8 @@ async function ankiTemplatesValidate(infoNode, field, mode, showSuccessResult, i              const options = await apiOptionsGet(optionsContext);              let templates = options.anki.fieldTemplates;              if (typeof templates !== 'string') { templates = await apiGetDefaultAnkiFieldTemplates(); } -            result = await dictFieldFormat(field, definition, mode, options, templates, exceptions); +            const ankiNoteBuilder = new AnkiNoteBuilder({renderTemplate: apiTemplateRender}); +            result = await ankiNoteBuilder.formatField(field, definition, mode, options, templates, exceptions);          }      } catch (e) {          exceptions.push(e); diff --git a/ext/bg/settings.html b/ext/bg/settings.html index e9fc6be5..0db76d71 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -1090,6 +1090,7 @@          <script src="/mixed/js/api.js"></script>          <script src="/bg/js/anki.js"></script> +        <script src="/bg/js/anki-note-builder.js"></script>          <script src="/bg/js/conditions.js"></script>          <script src="/bg/js/dictionary.js"></script>          <script src="/bg/js/handlebars.js"></script> diff --git a/ext/mixed/js/core.js b/ext/mixed/js/core.js index 0e22b9ac..0d50e915 100644 --- a/ext/mixed/js/core.js +++ b/ext/mixed/js/core.js @@ -175,21 +175,6 @@ function promiseTimeout(delay, resolveValue) {      return promise;  } -function stringReplaceAsync(str, regex, replacer) { -    let match; -    let index = 0; -    const parts = []; -    while ((match = regex.exec(str)) !== null) { -        parts.push(str.substring(index, match.index), replacer(...match, match.index, str)); -        index = regex.lastIndex; -    } -    if (parts.length === 0) { -        return Promise.resolve(str); -    } -    parts.push(str.substring(index)); -    return Promise.all(parts).then((v) => v.join('')); -} -  /*   * Common events  |