diff options
| author | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2021-01-12 22:47:07 -0500 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-01-12 22:47:07 -0500 | 
| commit | 5ae3acf6ff4d68379d9ea73c6ec90b8dfa69c6ad (patch) | |
| tree | bb5ef5fd289b8f2fff8f4d276e432082bbf2bfe4 | |
| parent | b7c9fa105764eb2cd5befea86c98fe49f5763a1d (diff) | |
Anki note data abstraction (#1228)
* Create AnkiNoteData
* Create AnkiNoteDataDefinitionProxyHandler
* Update media injection
* Create AnkiNoteDataDefinitionSecondaryProperties
* Update note context format
* Expose url and cloze on definition
* Simplify for understandability
* Remove unused _createNoteData
* Update public object
* Remove trims on sentence, since it should already be trimmed
* Fix unused global
| -rw-r--r-- | .eslintrc.json | 1 | ||||
| -rw-r--r-- | ext/bg/js/anki-note-builder.js | 42 | ||||
| -rw-r--r-- | ext/bg/js/anki-note-data.js | 240 | ||||
| -rw-r--r-- | ext/bg/js/template-renderer-frame-main.js | 6 | ||||
| -rw-r--r-- | ext/bg/template-renderer.html | 2 | ||||
| -rw-r--r-- | ext/mixed/js/display.js | 48 | 
6 files changed, 268 insertions, 71 deletions
| diff --git a/.eslintrc.json b/.eslintrc.json index 94551803..0f854e76 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -96,6 +96,7 @@              "excludedFiles": [                  "ext/mixed/js/core.js",                  "ext/bg/js/template-renderer.js", +                "ext/bg/js/anki-note-data.js",                  "ext/mixed/js/dictionary-data-util.js"              ],              "globals": { diff --git a/ext/bg/js/anki-note-builder.js b/ext/bg/js/anki-note-builder.js index 632d9f8a..eae5fbe4 100644 --- a/ext/bg/js/anki-note-builder.js +++ b/ext/bg/js/anki-note-builder.js @@ -16,7 +16,6 @@   */  /* global - * DictionaryDataUtil   * TemplateRendererProxy   */ @@ -35,6 +34,7 @@ class AnkiNoteBuilder {          modelName,          fields,          tags=[], +        injectedMedia=null,          checkForDuplicates=true,          duplicateScope='collection',          resultOutputMode='split', @@ -50,7 +50,15 @@ class AnkiNoteBuilder {              duplicateScopeCheckChildren = true;          } -        const data = this._createNoteData(definition, mode, context, resultOutputMode, glossaryLayoutMode, compactTags); +        const data = { +            definition, +            mode, +            context, +            resultOutputMode, +            glossaryLayoutMode, +            compactTags, +            injectedMedia +        };          const formattedFieldValuePromises = [];          for (const [, fieldValue] of fields) {              const formattedFieldValuePromise = this._formatField(fieldValue, data, templates, errors); @@ -104,36 +112,6 @@ class AnkiNoteBuilder {      // Private -    _createNoteData(definition, mode, context, resultOutputMode, glossaryLayoutMode, compactTags) { -        const pitches = DictionaryDataUtil.getPitchAccentInfos(definition); -        const pitchCount = pitches.reduce((i, v) => i + v.pitches.length, 0); -        const uniqueExpressions = new Set(); -        const uniqueReadings = new Set(); -        if (definition.type !== 'kanji') { -            for (const {expression, reading} of definition.expressions) { -                uniqueExpressions.add(expression); -                uniqueReadings.add(reading); -            } -        } -        return { -            marker: null, -            definition, -            uniqueExpressions: [...uniqueExpressions], -            uniqueReadings: [...uniqueReadings], -            pitches, -            pitchCount, -            group: resultOutputMode === 'group', -            merge: resultOutputMode === 'merge', -            modeTermKanji: mode === 'term-kanji', -            modeTermKana: mode === 'term-kana', -            modeKanji: mode === 'kanji', -            compactGlossaries: (glossaryLayoutMode === 'compact'), -            glossaryLayoutMode, -            compactTags, -            context -        }; -    } -      async _formatField(field, data, templates, errors=null) {          return await this._stringReplaceAsync(field, this._markerPattern, async (g0, marker) => {              try { diff --git a/ext/bg/js/anki-note-data.js b/ext/bg/js/anki-note-data.js new file mode 100644 index 00000000..a7d0f9f6 --- /dev/null +++ b/ext/bg/js/anki-note-data.js @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2021  Yomichan Authors + * + * 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/>. + */ + +/* global + * DictionaryDataUtil + */ + +/** + * This class represents the data that is exposed to the Anki template renderer. + * The public properties and data should be backwards compatible. + */ +class AnkiNoteData { +    constructor({ +        definition, +        resultOutputMode, +        mode, +        glossaryLayoutMode, +        compactTags, +        context, +        injectedMedia=null +    }, marker) { +        this._definition = definition; +        this._resultOutputMode = resultOutputMode; +        this._mode = mode; +        this._glossaryLayoutMode = glossaryLayoutMode; +        this._compactTags = compactTags; +        this._context = context; +        this._marker = marker; +        this._injectedMedia = injectedMedia; +        this._pitches = null; +        this._pitchCount = null; +        this._uniqueExpressions = null; +        this._uniqueReadings = null; +        this._publicContext = null; +        this._cloze = null; + +        this._prepareDefinition(definition, injectedMedia, context); +    } + +    get marker() { +        return this._marker; +    } + +    set marker(value) { +        this._marker = value; +    } + +    get definition() { +        return this._definition; +    } + +    get uniqueExpressions() { +        if (this._uniqueExpressions === null) { +            this._uniqueExpressions = this._getUniqueExpressions(); +        } +        return this._uniqueExpressions; +    } + +    get uniqueReadings() { +        if (this._uniqueReadings === null) { +            this._uniqueReadings = this._getUniqueReadings(); +        } +        return this._uniqueReadings; +    } + +    get pitches() { +        if (this._pitches === null) { +            this._pitches = DictionaryDataUtil.getPitchAccentInfos(this._definition); +        } +        return this._pitches; +    } + +    get pitchCount() { +        if (this._pitchCount === null) { +            this._pitchCount = this.pitches.reduce((i, v) => i + v.pitches.length, 0); +        } +        return this._pitchCount; +    } + +    get group() { +        return this._resultOutputMode === 'group'; +    } + +    get merge() { +        return this._resultOutputMode === 'merge'; +    } + +    get modeTermKanji() { +        return this._mode === 'term-kanji'; +    } + +    get modeTermKana() { +        return this._mode === 'term-kana'; +    } + +    get modeKanji() { +        return this._mode === 'kanji'; +    } + +    get compactGlossaries() { +        return this._glossaryLayoutMode === 'compact'; +    } + +    get glossaryLayoutMode() { +        return this._glossaryLayoutMode; +    } + +    get compactTags() { +        return this._compactTags; +    } + +    get context() { +        if (this._publicContext === null) { +            this._publicContext = this._getPublicContext(); +        } +        return this._publicContext; +    } + +    createPublic() { +        const self = this; +        return { +            get marker() { return self.marker; }, +            set marker(value) { self.marker = value; }, +            get definition() { return self.definition; }, +            get glossaryLayoutMode() { return self.glossaryLayoutMode; }, +            get compactTags() { return self.compactTags; }, +            get group() { return self.group; }, +            get merge() { return self.merge; }, +            get modeTermKanji() { return self.modeTermKanji; }, +            get modeTermKana() { return self.modeTermKana; }, +            get modeKanji() { return self.modeKanji; }, +            get compactGlossaries() { return self.compactGlossaries; }, +            get uniqueExpressions() { return self.uniqueExpressions; }, +            get uniqueReadings() { return self.uniqueReadings; }, +            get pitches() { return self.pitches; }, +            get pitchCount() { return self.pitchCount; }, +            get context() { return self.context; } +        }; +    } + +    // Private + +    _asObject(value) { +        return (typeof value === 'object' && value !== null ? value : {}); +    } + +    _getUniqueExpressions() { +        const results = new Set(); +        const definition = this._definition; +        if (definition.type !== 'kanji') { +            for (const {expression} of definition.expressions) { +                results.add(expression); +            } +        } +        return [...results]; +    } + +    _getUniqueReadings() { +        const results = new Set(); +        const definition = this._definition; +        if (definition.type !== 'kanji') { +            for (const {reading} of definition.expressions) { +                results.add(reading); +            } +        } +        return [...results]; +    } + +    _getPublicContext() { +        let {documentTitle} = this._asObject(this._context); +        if (typeof documentTitle !== 'string') { documentTitle = ''; } + +        return { +            document: { +                title: documentTitle +            } +        }; +    } + +    _getCloze() { +        const {sentence} = this._asObject(this._context); +        let {text, offset} = this._asObject(sentence); +        if (typeof text !== 'string') { text = ''; } +        if (typeof offset !== 'number') { offset = 0; } + +        const definition = this._definition; +        const source = definition.type === 'kanji' ? definition.character : definition.rawSource; + +        return { +            sentence: text, +            prefix: text.substring(0, offset), +            body: text.substring(offset, offset + source.length), +            suffix: text.substring(offset + source.length) +        }; +    } + +    _getClozeCached() { +        if (this._cloze === null) { +            this._cloze = this._getCloze(); +        } +        return this._cloze; +    } + +    _prepareDefinition(definition, injectedMedia, context) { +        const { +            screenshotFileName=null, +            clipboardImageFileName=null, +            clipboardText=null, +            audioFileName=null +        } = this._asObject(injectedMedia); + +        let {url} = this._asObject(context); +        if (typeof url !== 'string') { url = ''; } + +        definition.screenshotFileName = screenshotFileName; +        definition.clipboardImageFileName = clipboardImageFileName; +        definition.clipboardText = clipboardText; +        definition.audioFileName = audioFileName; +        definition.url = url; +        Object.defineProperty(definition, 'cloze', { +            configurable: true, +            enumerable: true, +            get: this._getClozeCached.bind(this) +        }); +    } +} diff --git a/ext/bg/js/template-renderer-frame-main.js b/ext/bg/js/template-renderer-frame-main.js index 92a8095e..d25eb56d 100644 --- a/ext/bg/js/template-renderer-frame-main.js +++ b/ext/bg/js/template-renderer-frame-main.js @@ -16,6 +16,7 @@   */  /* globals + * AnkiNoteData   * JapaneseUtil   * TemplateRenderer   * TemplateRendererFrameApi @@ -25,10 +26,7 @@      const japaneseUtil = new JapaneseUtil(null);      const templateRenderer = new TemplateRenderer(japaneseUtil);      templateRenderer.registerDataType('ankiNote', { -        modifier: ({data, marker}) => { -            data.marker = marker; -            return data; -        } +        modifier: ({data, marker}) => new AnkiNoteData(data, marker).createPublic()      });      const api = new TemplateRendererFrameApi(templateRenderer);      api.prepare(); diff --git a/ext/bg/template-renderer.html b/ext/bg/template-renderer.html index c58e604c..a44205ce 100644 --- a/ext/bg/template-renderer.html +++ b/ext/bg/template-renderer.html @@ -14,7 +14,9 @@  </head>  <body>      <script src="/mixed/lib/handlebars.min.js"></script> +    <script src="/mixed/js/dictionary-data-util.js"></script>      <script src="/mixed/js/japanese.js"></script> +    <script src="/bg/js/anki-note-data.js"></script>      <script src="/bg/js/template-renderer.js"></script>      <script src="/bg/js/template-renderer-frame-api.js"></script>      <script src="/bg/js/template-renderer-frame-main.js"></script> diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index a76e2b71..6a1ebba6 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -915,18 +915,14 @@ class Display extends EventDispatcher {              focusEntry=null,              scrollX=null,              scrollY=null, -            optionsContext=null, -            sentence=null, -            url +            optionsContext=null          } = state;          if (typeof focusEntry !== 'number') { focusEntry = 0; } -        if (typeof url !== 'string') { url = window.location.href; }          if (!(typeof optionsContext === 'object' && optionsContext !== null)) {              optionsContext = this.getOptionsContext();              state.optionsContext = optionsContext;              changeHistory = true;          } -        sentence = this._getValidSentenceData(sentence);          this._setFullQuery(queryFull);          this._setTitleText(query); @@ -957,11 +953,6 @@ class Display extends EventDispatcher {          this._definitions = definitions; -        for (const definition of definitions) { -            definition.cloze = this._clozeBuild(sentence, isTerms ? definition.rawSource : definition.character); -            definition.url = url; -        } -          this._updateNavigation(this._history.hasPrevious(), this._history.hasNext());          this._setNoContentVisible(definitions.length === 0); @@ -1318,15 +1309,6 @@ class Display extends EventDispatcher {          return {text, offset};      } -    _clozeBuild({text, offset}, source) { -        return { -            sentence: text.trim(), -            prefix: text.substring(0, offset).trim(), -            body: text.substring(offset, offset + source.length), -            suffix: text.substring(offset + source.length).trim() -        }; -    } -      _getClosestDefinitionIndex(element) {          return this._getClosestIndex(element, '.entry');      } @@ -1382,17 +1364,18 @@ class Display extends EventDispatcher {      _getNoteContext() {          const {state} = this._history; -        let documentTitle = null; -        if (typeof state === 'object' && state !== null) { -            ({documentTitle} = state); -        } +        let {documentTitle, url, sentence} = (isObject(state) ? state : {});          if (typeof documentTitle !== 'string') {              documentTitle = '';          } +        if (typeof url !== 'string') { +            url = window.location.href; +        } +        sentence = this._getValidSentenceData(sentence);          return { -            document: { -                title: documentTitle -            } +            url, +            sentence, +            documentTitle          };      } @@ -1534,9 +1517,7 @@ class Display extends EventDispatcher {          const {deck: deckName, model: modelName} = modeOptions;          const fields = Object.entries(modeOptions.fields); -        if (injectMedia) { -            await this._injectAnkiNoteMedia(definition, mode, options, fields); -        } +        const injectedMedia = (injectMedia ? await this._injectAnkiNoteMedia(definition, mode, options, fields) : null);          return await this._ankiNoteBuilder.createNote({              definition, @@ -1551,7 +1532,8 @@ class Display extends EventDispatcher {              duplicateScope,              resultOutputMode,              glossaryLayoutMode, -            compactTags +            compactTags, +            injectedMedia          });      } @@ -1570,17 +1552,13 @@ class Display extends EventDispatcher {              image: this._ankiNoteBuilder.containsMarker(fields, 'clipboard-image'),              text: this._ankiNoteBuilder.containsMarker(fields, 'clipboard-text')          }; -        const {screenshotFileName, clipboardImageFileName, clipboardText, audioFileName} = await api.injectAnkiNoteMedia( +        return await api.injectAnkiNoteMedia(              timestamp,              definitionDetails,              audioDetails,              screenshotDetails,              clipboardDetails          ); -        if (screenshotFileName !== null) { definition.screenshotFileName = screenshotFileName; } -        if (clipboardImageFileName !== null) { definition.clipboardImageFileName = clipboardImageFileName; } -        if (audioFileName !== null) { definition.audioFileName = audioFileName; } -        if (clipboardText !== null) { definition.clipboardText = clipboardText; }      }      _getDefinitionDetailsForNote(definition) { |