diff options
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | ext/bg/css/settings.css | 37 | ||||
| -rw-r--r-- | ext/bg/data/default-anki-field-templates.handlebars | 4 | ||||
| -rw-r--r-- | ext/bg/data/dictionary-index-schema.json | 16 | ||||
| -rw-r--r-- | ext/bg/js/anki-note-builder.js | 76 | ||||
| -rw-r--r-- | ext/bg/js/backend.js | 99 | ||||
| -rw-r--r-- | ext/bg/js/dictionary-importer.js | 27 | ||||
| -rw-r--r-- | ext/bg/js/options.js | 9 | ||||
| -rw-r--r-- | ext/bg/js/settings/anki-templates.js | 7 | ||||
| -rw-r--r-- | ext/bg/js/settings/anki.js | 2 | ||||
| -rw-r--r-- | ext/bg/js/settings/dictionaries.js | 50 | ||||
| -rw-r--r-- | ext/bg/settings.html | 4 | ||||
| -rw-r--r-- | ext/fg/js/float.js | 27 | ||||
| -rw-r--r-- | ext/fg/js/frontend.js | 11 | ||||
| -rw-r--r-- | ext/mixed/js/api.js | 8 | ||||
| -rw-r--r-- | ext/mixed/js/display.js | 23 | 
16 files changed, 295 insertions, 107 deletions
| @@ -156,6 +156,7 @@ Flashcard fields can be configured with the following steps:      `{cloze-prefix}` | Text for the containing `{sentence}` from the start up to the value of `{cloze-body}`.      `{cloze-suffix}` | Text for the containing `{sentence}` from the value of `{cloze-body}` to the end.      `{dictionary}` | Name of the dictionary from which the card is being created (unavailable in *grouped* mode). +    `{document-title}` | Title of the web page that the term appeared in.      `{expression}` | Term expressed as Kanji (will be displayed in Kana if Kanji is not available).      `{furigana}` | Term expressed as Kanji with Furigana displayed above it (e.g. <ruby>日本語<rt>にほんご</rt></ruby>).      `{furigana-plain}` | Term expressed as Kanji with Furigana displayed next to it in brackets (e.g. 日本語[にほんご]). @@ -175,6 +176,7 @@ Flashcard fields can be configured with the following steps:      `{cloze-prefix}` | Text for the containing `{sentence}` from the start up to the value of `{cloze-body}`.      `{cloze-suffix}` | Text for the containing `{sentence}` from the value of `{cloze-body}` to the end.      `{dictionary}` | Name of the dictionary from which the card is being created. +    `{document-title}` | Title of the web page that the Kanji appeared in.      `{glossary}` | List of definitions for the Kanji.      `{kunyomi}` | Kunyomi (Japanese reading) for the Kanji expressed as Katakana.      `{onyomi}` | Onyomi (Chinese reading) for the Kanji expressed as Hiragana. diff --git a/ext/bg/css/settings.css b/ext/bg/css/settings.css index d686e8f8..6344bd38 100644 --- a/ext/bg/css/settings.css +++ b/ext/bg/css/settings.css @@ -235,6 +235,43 @@ html:root[data-operating-system=openbsd] [data-hide-for-operating-system~=openbs      display: none;  } +.dict-details-container { +    margin: 0.5em 0; +} + +.dict-details-toggle-link { +    cursor: pointer; +} + +.dict-details { +    margin-left: 1em; +} + +.dict-details-table { +    display: table; +    width: 100% +} + +.dict-details-entry { +    display: table-row; +} + +.dict-details-entry+.dict-details-entry>* { +    padding-top: 0.25em; +} + +.dict-details-entry-label { +    display: table-cell; +    font-weight: bold; +    white-space: nowrap; +    padding-right: 0.5em; +} + +.dict-details-entry-info { +    display: table-cell; +    white-space: pre-line; +} +  @media screen and (max-width: 740px) {      .col-xs-6 { diff --git a/ext/bg/data/default-anki-field-templates.handlebars b/ext/bg/data/default-anki-field-templates.handlebars index 0442f7c5..6061851f 100644 --- a/ext/bg/data/default-anki-field-templates.handlebars +++ b/ext/bg/data/default-anki-field-templates.handlebars @@ -158,4 +158,8 @@      <img src="{{definition.screenshotFileName}}" />  {{/inline}} +{{#*inline "document-title"}} +    {{~context.document.title~}} +{{/inline}} +  {{~> (lookup . "marker") ~}}
\ No newline at end of file diff --git a/ext/bg/data/dictionary-index-schema.json b/ext/bg/data/dictionary-index-schema.json index 9311f14c..09cff711 100644 --- a/ext/bg/data/dictionary-index-schema.json +++ b/ext/bg/data/dictionary-index-schema.json @@ -30,6 +30,22 @@              "description": "Alias for format.",              "enum": [1, 2, 3]          }, +        "author": { +            "type": "string", +            "description": "Creator of the dictionary." +        }, +        "url": { +            "type": "string", +            "description": "URL for the source of the dictionary." +        }, +        "description": { +            "type": "string", +            "description": "Description of the dictionary data." +        }, +        "attribution": { +            "type": "string", +            "description": "Attribution information for the dictionary data." +        },          "tagMeta": {              "type": "object",              "description": "Tag information for terms and kanji. This object is obsolete and individual tag files should be used instead.", diff --git a/ext/bg/js/anki-note-builder.js b/ext/bg/js/anki-note-builder.js index d0ff8205..244aaab8 100644 --- a/ext/bg/js/anki-note-builder.js +++ b/ext/bg/js/anki-note-builder.js @@ -17,11 +17,12 @@   */  class AnkiNoteBuilder { -    constructor({renderTemplate}) { +    constructor({audioSystem, renderTemplate}) { +        this._audioSystem = audioSystem;          this._renderTemplate = renderTemplate;      } -    async createNote(definition, mode, options, templates) { +    async createNote(definition, mode, context, options, templates) {          const isKanji = (mode === 'kanji');          const tags = options.anki.tags;          const modeOptions = isKanji ? options.anki.kanji : options.anki.terms; @@ -35,7 +36,7 @@ class AnkiNoteBuilder {          };          for (const [fieldName, fieldValue] of modeOptionsFieldEntries) { -            note.fields[fieldName] = await this.formatField(fieldValue, definition, mode, options, templates, null); +            note.fields[fieldName] = await this.formatField(fieldValue, definition, mode, context, options, templates, null);          }          if (!isKanji && definition.audio) { @@ -60,7 +61,7 @@ class AnkiNoteBuilder {          return note;      } -    async formatField(field, definition, mode, options, templates, errors=null) { +    async formatField(field, definition, mode, context, options, templates, errors=null) {          const data = {              marker: null,              definition, @@ -69,7 +70,8 @@ class AnkiNoteBuilder {              modeTermKanji: mode === 'term-kanji',              modeTermKana: mode === 'term-kana',              modeKanji: mode === 'kanji', -            compactGlossaries: options.general.compactGlossaries +            compactGlossaries: options.general.compactGlossaries, +            context          };          const pattern = /\{([\w-]+)\}/g;          return await AnkiNoteBuilder.stringReplaceAsync(field, pattern, async (g0, marker) => { @@ -83,6 +85,70 @@ class AnkiNoteBuilder {          });      } +    async injectAudio(definition, fields, sources, optionsContext) { +        if (!this._containsMarker(fields, 'audio')) { return; } + +        try { +            const expressions = definition.expressions; +            const audioSourceDefinition = Array.isArray(expressions) ? expressions[0] : definition; + +            const {uri} = await this._audioSystem.getDefinitionAudio(audioSourceDefinition, sources, {tts: false, optionsContext}); +            const filename = this._createInjectedAudioFileName(audioSourceDefinition); +            if (filename !== null) { +                definition.audio = {url: uri, filename}; +            } +        } catch (e) { +            // NOP +        } +    } + +    async injectScreenshot(definition, fields, screenshot, anki) { +        if (!this._containsMarker(fields, 'screenshot')) { return; } + +        const now = new Date(Date.now()); +        const filename = `yomichan_browser_screenshot_${definition.reading}_${this._dateToString(now)}.${screenshot.format}`; +        const data = screenshot.dataUrl.replace(/^data:[\w\W]*?,/, ''); + +        try { +            await anki.storeMediaFile(filename, data); +        } catch (e) { +            return; +        } + +        definition.screenshotFileName = filename; +    } + +    _createInjectedAudioFileName(definition) { +        const {reading, expression} = definition; +        if (!reading && !expression) { return null; } + +        let filename = 'yomichan'; +        if (reading) { filename += `_${reading}`; } +        if (expression) { filename += `_${expression}`; } +        filename += '.mp3'; +        return filename; +    } + +    _dateToString(date) { +        const year = date.getUTCFullYear(); +        const month = date.getUTCMonth().toString().padStart(2, '0'); +        const day = date.getUTCDate().toString().padStart(2, '0'); +        const hours = date.getUTCHours().toString().padStart(2, '0'); +        const minutes = date.getUTCMinutes().toString().padStart(2, '0'); +        const seconds = date.getUTCSeconds().toString().padStart(2, '0'); +        return `${year}-${month}-${day}-${hours}-${minutes}-${seconds}`; +    } + +    _containsMarker(fields, marker) { +        marker = `{${marker}}`; +        for (const fieldValue of Object.values(fields)) { +            if (fieldValue.includes(marker)) { +                return true; +            } +        } +        return false; +    } +      static stringReplaceAsync(str, regex, replacer) {          let match;          let index = 0; diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 1e8c979f..1fa7ede1 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -51,12 +51,16 @@ 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;          this.audioSystem = new AudioSystem({getAudioUri: this._getAudioUri.bind(this)});          this.audioUriBuilder = new AudioUriBuilder(); +        this.ankiNoteBuilder = new AnkiNoteBuilder({ +            audioSystem: this.audioSystem, +            renderTemplate: this._renderTemplate.bind(this) +        }); +          this.optionsContext = {              depth: 0,              url: window.location.href @@ -455,12 +459,12 @@ class Backend {          return results;      } -    async _onApiDefinitionAdd({definition, mode, context, optionsContext}) { +    async _onApiDefinitionAdd({definition, mode, context, details, optionsContext}) {          const options = this.getOptions(optionsContext);          const templates = this.defaultAnkiFieldTemplates;          if (mode !== 'kanji') { -            await this._audioInject( +            await this.ankiNoteBuilder.injectAudio(                  definition,                  options.anki.terms.fields,                  options.audio.sources, @@ -468,19 +472,20 @@ class Backend {              );          } -        if (context && context.screenshot) { -            await this._injectScreenshot( +        if (details && details.screenshot) { +            await this.ankiNoteBuilder.injectScreenshot(                  definition,                  options.anki.terms.fields, -                context.screenshot +                details.screenshot, +                this.anki              );          } -        const note = await this.ankiNoteBuilder.createNote(definition, mode, options, templates); +        const note = await this.ankiNoteBuilder.createNote(definition, mode, context, options, templates);          return this.anki.addNote(note);      } -    async _onApiDefinitionsAddable({definitions, modes, optionsContext}) { +    async _onApiDefinitionsAddable({definitions, modes, context, optionsContext}) {          const options = this.getOptions(optionsContext);          const templates = this.defaultAnkiFieldTemplates;          const states = []; @@ -489,7 +494,7 @@ class Backend {              const notes = [];              for (const definition of definitions) {                  for (const mode of modes) { -                    const note = await this.ankiNoteBuilder.createNote(definition, mode, options, templates); +                    const note = await this.ankiNoteBuilder.createNote(definition, mode, context, options, templates);                      notes.push(note);                  }              } @@ -800,86 +805,10 @@ class Backend {          return await this.audioUriBuilder.getUri(definition, source, options);      } -    async _audioInject(definition, fields, sources, optionsContext) { -        let usesAudio = false; -        for (const fieldValue of Object.values(fields)) { -            if (fieldValue.includes('{audio}')) { -                usesAudio = true; -                break; -            } -        } - -        if (!usesAudio) { -            return true; -        } - -        try { -            const expressions = definition.expressions; -            const audioSourceDefinition = Array.isArray(expressions) ? expressions[0] : definition; - -            const {uri} = await this.audioSystem.getDefinitionAudio(audioSourceDefinition, sources, {tts: false, optionsContext}); -            const filename = this._createInjectedAudioFileName(audioSourceDefinition); -            if (filename !== null) { -                definition.audio = {url: uri, filename}; -            } - -            return true; -        } catch (e) { -            return false; -        } -    } - -    async _injectScreenshot(definition, fields, screenshot) { -        let usesScreenshot = false; -        for (const fieldValue of Object.values(fields)) { -            if (fieldValue.includes('{screenshot}')) { -                usesScreenshot = true; -                break; -            } -        } - -        if (!usesScreenshot) { -            return; -        } - -        const dateToString = (date) => { -            const year = date.getUTCFullYear(); -            const month = date.getUTCMonth().toString().padStart(2, '0'); -            const day = date.getUTCDate().toString().padStart(2, '0'); -            const hours = date.getUTCHours().toString().padStart(2, '0'); -            const minutes = date.getUTCMinutes().toString().padStart(2, '0'); -            const seconds = date.getUTCSeconds().toString().padStart(2, '0'); -            return `${year}-${month}-${day}-${hours}-${minutes}-${seconds}`; -        }; - -        const now = new Date(Date.now()); -        const filename = `yomichan_browser_screenshot_${definition.reading}_${dateToString(now)}.${screenshot.format}`; -        const data = screenshot.dataUrl.replace(/^data:[\w\W]*?,/, ''); - -        try { -            await this.anki.storeMediaFile(filename, data); -        } catch (e) { -            return; -        } - -        definition.screenshotFileName = filename; -    } -      async _renderTemplate(template, data) {          return handlebarsRenderDynamic(template, data);      } -    _createInjectedAudioFileName(definition) { -        const {reading, expression} = definition; -        if (!reading && !expression) { return null; } - -        let filename = 'yomichan'; -        if (reading) { filename += `_${reading}`; } -        if (expression) { filename += `_${expression}`; } -        filename += '.mp3'; -        return filename; -    } -      static _getTabUrl(tab) {          return new Promise((resolve) => {              chrome.tabs.sendMessage(tab.id, {action: 'getUrl'}, {frameId: 0}, (response) => { diff --git a/ext/bg/js/dictionary-importer.js b/ext/bg/js/dictionary-importer.js index 607a8b5e..254fde4f 100644 --- a/ext/bg/js/dictionary-importer.js +++ b/ext/bg/js/dictionary-importer.js @@ -150,13 +150,7 @@ class DictionaryImporter {          }          // Add dictionary -        const summary = { -            title: dictionaryTitle, -            revision: index.revision, -            sequenced: index.sequenced, -            version, -            prefixWildcardsSupported -        }; +        const summary = this._createSummary(dictionaryTitle, version, index, {prefixWildcardsSupported});          database.bulkAdd('dictionaries', [summary], 0, 1); @@ -199,6 +193,25 @@ class DictionaryImporter {          return {result: summary, errors};      } +    _createSummary(dictionaryTitle, version, index, details) { +        const summary = { +            title: dictionaryTitle, +            revision: index.revision, +            sequenced: index.sequenced, +            version +        }; + +        const {author, url, description, attribution} = index; +        if (typeof author === 'string') { summary.author = author; } +        if (typeof url === 'string') { summary.url = url; } +        if (typeof description === 'string') { summary.description = description; } +        if (typeof attribution === 'string') { summary.attribution = attribution; } + +        Object.assign(summary, details); + +        return summary; +    } +      async _getSchema(fileName) {          let schemaPromise = this._schemas.get(fileName);          if (typeof schemaPromise !== 'undefined') { diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index 5c68c403..abb054d4 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -91,6 +91,15 @@ const profileOptionsVersionUpdates = [          if (utilStringHashCode(options.anki.fieldTemplates) === 1444379824) {              options.anki.fieldTemplates = null;          } +    }, +    (options) => { +        // Version 13 changes: +        //  Default anki field tempaltes updated to include {document-title}. +        let fieldTemplates = options.anki.fieldTemplates; +        if (typeof fieldTemplates === 'string') { +            fieldTemplates += '\n\n{{#*inline "document-title"}}\n    {{~context.document.title~}}\n{{/inline}}'; +            options.anki.fieldTemplates = fieldTemplates; +        }      }  ]; diff --git a/ext/bg/js/settings/anki-templates.js b/ext/bg/js/settings/anki-templates.js index c5222d30..e3852eb4 100644 --- a/ext/bg/js/settings/anki-templates.js +++ b/ext/bg/js/settings/anki-templates.js @@ -99,10 +99,15 @@ async function ankiTemplatesValidate(infoNode, field, mode, showSuccessResult, i          const definition = await ankiTemplatesValidateGetDefinition(text, optionsContext);          if (definition !== null) {              const options = await apiOptionsGet(optionsContext); +            const context = { +                document: { +                    title: document.title +                } +            };              let templates = options.anki.fieldTemplates;              if (typeof templates !== 'string') { templates = await apiGetDefaultAnkiFieldTemplates(); }              const ankiNoteBuilder = new AnkiNoteBuilder({renderTemplate: apiTemplateRender}); -            result = await ankiNoteBuilder.formatField(field, definition, mode, options, templates, exceptions); +            result = await ankiNoteBuilder.formatField(field, definition, mode, context, options, templates, exceptions);          }      } catch (e) {          exceptions.push(e); diff --git a/ext/bg/js/settings/anki.js b/ext/bg/js/settings/anki.js index b706cd1b..f2e1ca76 100644 --- a/ext/bg/js/settings/anki.js +++ b/ext/bg/js/settings/anki.js @@ -243,6 +243,7 @@ function ankiGetFieldMarkers(type) {                  'cloze-prefix',                  'cloze-suffix',                  'dictionary', +                'document-title',                  'expression',                  'furigana',                  'furigana-plain', @@ -258,6 +259,7 @@ function ankiGetFieldMarkers(type) {              return [                  'character',                  'dictionary', +                'document-title',                  'glossary',                  'kunyomi',                  'onyomi', diff --git a/ext/bg/js/settings/dictionaries.js b/ext/bg/js/settings/dictionaries.js index 00056e2e..33ced3b9 100644 --- a/ext/bg/js/settings/dictionaries.js +++ b/ext/bg/js/settings/dictionaries.js @@ -199,11 +199,16 @@ class SettingsDictionaryEntryUI {          this.allowSecondarySearchesCheckbox = this.content.querySelector('.dict-allow-secondary-searches');          this.priorityInput = this.content.querySelector('.dict-priority');          this.deleteButton = this.content.querySelector('.dict-delete-button'); +        this.detailsToggleLink = this.content.querySelector('.dict-details-toggle-link'); +        this.detailsContainer = this.content.querySelector('.dict-details'); +        this.detailsTable = this.content.querySelector('.dict-details-table');          if (this.dictionaryInfo.version < 3) {              this.content.querySelector('.dict-outdated').hidden = false;          } +        this.setupDetails(dictionaryInfo); +          this.content.querySelector('.dict-title').textContent = this.dictionaryInfo.title;          this.content.querySelector('.dict-revision').textContent = `rev.${this.dictionaryInfo.revision}`;          this.content.querySelector('.dict-prefix-wildcard-searches-supported').checked = !!this.dictionaryInfo.prefixWildcardsSupported; @@ -214,6 +219,45 @@ class SettingsDictionaryEntryUI {          this.eventListeners.addEventListener(this.allowSecondarySearchesCheckbox, 'change', this.onAllowSecondarySearchesChanged.bind(this), false);          this.eventListeners.addEventListener(this.priorityInput, 'change', this.onPriorityChanged.bind(this), false);          this.eventListeners.addEventListener(this.deleteButton, 'click', this.onDeleteButtonClicked.bind(this), false); +        this.eventListeners.addEventListener(this.detailsToggleLink, 'click', this.onDetailsToggleLinkClicked.bind(this), false); +    } + +    setupDetails(dictionaryInfo) { +        const targets = [ +            ['Author', 'author'], +            ['URL', 'url'], +            ['Description', 'description'], +            ['Attribution', 'attribution'] +        ]; + +        let count = 0; +        for (const [label, key] of targets) { +            const info = dictionaryInfo[key]; +            if (typeof info !== 'string') { continue; } + +            const n1 = document.createElement('div'); +            n1.className = 'dict-details-entry'; +            n1.dataset.type = key; + +            const n2 = document.createElement('span'); +            n2.className = 'dict-details-entry-label'; +            n2.textContent = `${label}:`; +            n1.appendChild(n2); + +            const n3 = document.createElement('span'); +            n3.className = 'dict-details-entry-info'; +            n3.textContent = info; +            n1.appendChild(n3); + +            this.detailsTable.appendChild(n1); + +            ++count; +        } + +        if (count === 0) { +            this.detailsContainer.hidden = true; +            this.detailsToggleLink.hidden = true; +        }      }      cleanup() { @@ -318,6 +362,12 @@ class SettingsDictionaryEntryUI {          document.querySelector('#dict-remove-modal-dict-name').textContent = title;          $(n).modal('show');      } + +    onDetailsToggleLinkClicked(e) { +        e.preventDefault(); + +        this.detailsContainer.hidden = !this.detailsContainer.hidden; +    }  }  class SettingsDictionaryExtraUI { diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 237162c7..1297a9cc 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -674,6 +674,10 @@                          <label class="dict-result-priority-label">Result priority</label>                          <input type="number" class="form-control dict-priority">                      </div> +                    <div class="dict-details-container"> +                        <a class="dict-details-toggle-link">Details...</a> +                        <div class="dict-details" hidden><div class="dict-details-table"></div></div> +                    </div>                      <div class="dict-delete-table">                          <div>                              <button class="btn btn-default dict-delete-button">Delete Dictionary</button> diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 9b720ebe..01055ca6 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -162,6 +162,33 @@ class DisplayFloat extends Display {      setContentScale(scale) {          document.body.style.fontSize = `${scale}em`;      } + +    async getDocumentTitle() { +        try { +            const uniqueId = yomichan.generateId(16); + +            const promise = yomichan.getTemporaryListenerResult( +                chrome.runtime.onMessage, +                ({action, params}, {resolve}) => { +                    if ( +                        action === 'documentInformationBroadcast' && +                        isObject(params) && +                        params.uniqueId === uniqueId && +                        params.frameId === 0 +                    ) { +                        resolve(params); +                    } +                }, +                2000 +            ); +            apiForward('requestDocumentInformationBroadcast', {uniqueId}); + +            const {title} = await promise; +            return title; +        } catch (e) { +            return ''; +        } +    }  }  DisplayFloat.instance = new DisplayFloat(); diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 4e9d474c..31843212 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -54,7 +54,8 @@ class Frontend extends TextScanner {          this._runtimeMessageHandlers = new Map([              ['popupSetVisibleOverride', ({visible}) => { this.popup.setVisibleOverride(visible); }], -            ['rootPopupRequestInformationBroadcast', () => { this._broadcastRootPopupInformation(); }] +            ['rootPopupRequestInformationBroadcast', () => { this._broadcastRootPopupInformation(); }], +            ['requestDocumentInformationBroadcast', ({uniqueId}) => { this._broadcastDocumentInformation(uniqueId); }]          ]);      } @@ -264,6 +265,14 @@ class Frontend extends TextScanner {          }      } +    _broadcastDocumentInformation(uniqueId) { +        apiForward('documentInformationBroadcast', { +            uniqueId, +            frameId: this.popup.frameId, +            title: document.title +        }); +    } +      async _updatePopupPosition() {          const textSource = this.getCurrentTextSource();          if (textSource !== null && await this.popup.isVisible()) { diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js index 0ab07039..feec94df 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -53,12 +53,12 @@ function apiKanjiFind(text, optionsContext) {      return _apiInvoke('kanjiFind', {text, optionsContext});  } -function apiDefinitionAdd(definition, mode, context, optionsContext) { -    return _apiInvoke('definitionAdd', {definition, mode, context, optionsContext}); +function apiDefinitionAdd(definition, mode, context, details, optionsContext) { +    return _apiInvoke('definitionAdd', {definition, mode, context, details, optionsContext});  } -function apiDefinitionsAddable(definitions, modes, optionsContext) { -    return _apiInvoke('definitionsAddable', {definitions, modes, optionsContext}); +function apiDefinitionsAddable(definitions, modes, context, optionsContext) { +    return _apiInvoke('definitionsAddable', {definitions, modes, context, optionsContext});  }  function apiNoteView(noteId) { diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 4a71efe0..2f456c3e 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -752,15 +752,16 @@ class Display {          try {              this.setSpinnerVisible(true); -            const context = {}; +            const details = {};              if (this.noteUsesScreenshot(mode)) {                  const screenshot = await this.getScreenshot();                  if (screenshot) { -                    context.screenshot = screenshot; +                    details.screenshot = screenshot;                  }              } -            const noteId = await apiDefinitionAdd(definition, mode, context, this.getOptionsContext()); +            const context = await this._getNoteContext(); +            const noteId = await apiDefinitionAdd(definition, mode, context, details, this.getOptionsContext());              if (noteId) {                  const index = this.definitions.indexOf(definition);                  const adderButton = this.adderButtonFind(index, mode); @@ -908,12 +909,17 @@ class Display {      async getDefinitionsAddable(definitions, modes) {          try { -            return await apiDefinitionsAddable(definitions, modes, this.getOptionsContext()); +            const context = await this._getNoteContext(); +            return await apiDefinitionsAddable(definitions, modes, context, this.getOptionsContext());          } catch (e) {              return [];          }      } +    async getDocumentTitle() { +        return document.title; +    } +      static indexOf(nodeList, node) {          for (let i = 0, ii = nodeList.length; i < ii; ++i) {              if (nodeList[i] === node) { @@ -934,6 +940,15 @@ class Display {          return (typeof key === 'string' ? (key.length === 1 ? key.toUpperCase() : key) : '');      } +    async _getNoteContext() { +        const documentTitle = await this.getDocumentTitle(); +        return { +            document: { +                title: documentTitle +            } +        }; +    } +      async _getAudioUri(definition, source) {          const optionsContext = this.getOptionsContext();          return await apiAudioGetUri(definition, source, optionsContext); |