From 2ab871e7ee93ddc4a2f1ca41aad10e4b189b6c0f Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 2 Nov 2019 14:06:16 -0400 Subject: Update how dictionaries are displayed on the settings page --- ext/bg/js/settings-dictionaries.js | 482 +++++++++++++++++++++++++++++++++++++ ext/bg/js/settings.js | 261 +------------------- ext/bg/js/templates.js | 27 --- ext/bg/js/util.js | 8 + 4 files changed, 491 insertions(+), 287 deletions(-) create mode 100644 ext/bg/js/settings-dictionaries.js (limited to 'ext/bg/js') diff --git a/ext/bg/js/settings-dictionaries.js b/ext/bg/js/settings-dictionaries.js new file mode 100644 index 00000000..635b95c6 --- /dev/null +++ b/ext/bg/js/settings-dictionaries.js @@ -0,0 +1,482 @@ +/* + * Copyright (C) 2019 Alex Yatskov + * Author: Alex Yatskov + * + * 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 . + */ + + +let dictionaryUI = null; + + +class SettingsDictionaryListUI { + constructor(container, template, extraContainer, extraTemplate, optionsDictionaries) { + this.container = container; + this.template = template; + this.extraContainer = extraContainer; + this.extraTemplate = extraTemplate; + this.optionsDictionaries = optionsDictionaries; + + this.dictionaryEntries = []; + this.extra = null; + } + + setDictionaries(dictionaries) { + for (const dictionaryEntry of this.dictionaryEntries) { + dictionaryEntry.cleanup(); + } + + this.dictionaryEntries = []; + + let changed = false; + for (const dictionaryInfo of toIterable(dictionaries)) { + if (this.createEntry(dictionaryInfo)) { + changed = true; + } + } + + const titles = this.dictionaryEntries.map(e => e.dictionaryInfo.title); + const removeKeys = Object.keys(this.optionsDictionaries).filter(key => titles.indexOf(key) < 0); + if (removeKeys.length >= 0) { + for (const key of removeKeys) { + delete this.optionsDictionaries[key]; + } + changed = true; + } + + if (changed) { + this.save(); + } + } + + createEntry(dictionaryInfo) { + const title = dictionaryInfo.title; + let changed = false; + let optionsDictionary; + const optionsDictionaries = this.optionsDictionaries; + if (optionsDictionaries.hasOwnProperty(title)) { + optionsDictionary = optionsDictionaries[title]; + } else { + optionsDictionary = SettingsDictionaryListUI.createDictionaryOptions(); + optionsDictionaries[title] = optionsDictionary; + changed = true; + } + + const content = document.importNode(this.template.content, true).firstChild; + this.container.appendChild(content); + + this.dictionaryEntries.push(new SettingsDictionaryEntryUI(this, dictionaryInfo, content, optionsDictionary)); + + return changed; + } + + static createDictionaryOptions() { + return utilBackgroundIsolate({ + priority: 0, + enabled: false, + allowSecondarySearches: false + }); + } + + createExtra(totalCounts, remainders, totalRemainder) { + const content = document.importNode(this.extraTemplate.content, true).firstChild; + this.extraContainer.appendChild(content); + return new SettingsDictionaryExtraUI(this, totalCounts, remainders, totalRemainder, content); + } + + setCounts(dictionaryCounts, totalCounts) { + const remainders = Object.assign({}, totalCounts); + const keys = Object.keys(remainders); + + for (let i = 0, ii = Math.min(this.dictionaryEntries.length, dictionaryCounts.length); i < ii; ++i) { + const counts = dictionaryCounts[i]; + this.dictionaryEntries[i].setCounts(counts); + + for (const key of keys) { + remainders[key] -= counts[key]; + } + } + + let totalRemainder = 0; + for (const key of keys) { + totalRemainder += remainders[key]; + } + + if (this.extra !== null) { + this.extra.cleanup(); + this.extra = null; + } + + if (totalRemainder > 0) { + this.extra = this.createExtra(totalCounts, remainders, totalRemainder); + } + } + + save() { + // Overwrite + } +} + +class SettingsDictionaryEntryUI { + constructor(parent, dictionaryInfo, content, optionsDictionary) { + this.parent = parent; + this.dictionaryInfo = dictionaryInfo; + this.optionsDictionary = optionsDictionary; + this.counts = null; + this.eventListeners = []; + + this.content = content; + this.enabledCheckbox = this.content.querySelector('.dict-enabled'); + this.allowSecondarySearchesCheckbox = this.content.querySelector('.dict-allow-secondary-searches'); + this.priorityInput = this.content.querySelector('.dict-priority'); + + this.content.querySelector('.dict-title').textContent = this.dictionaryInfo.title; + this.content.querySelector('.dict-revision').textContent = `rev.${this.dictionaryInfo.revision}`; + + this.applyValues(); + + this.addEventListener(this.enabledCheckbox, 'change', (e) => this.onEnabledChanged(e), false); + this.addEventListener(this.allowSecondarySearchesCheckbox, 'change', (e) => this.onAllowSecondarySearchesChanged(e), false); + this.addEventListener(this.priorityInput, 'change', (e) => this.onPriorityChanged(e), false); + } + + cleanup() { + if (this.content !== null) { + if (this.content.parentNode !== null) { + this.content.parentNode.removeChild(this.content); + } + this.content = null; + } + this.dictionaryInfo = null; + this.clearEventListeners(); + } + + setCounts(counts) { + this.counts = counts; + const node = this.content.querySelector('.dict-counts'); + node.textContent = JSON.stringify({ + info: this.dictionaryInfo, + counts + }, null, 4); + node.removeAttribute('hidden'); + } + + save() { + this.parent.save(); + } + + addEventListener(node, type, listener, options) { + node.addEventListener(type, listener, options); + this.eventListeners.push([node, type, listener, options]); + } + + clearEventListeners() { + for (const [node, type, listener, options] of this.eventListeners) { + node.removeEventListener(type, listener, options); + } + this.eventListeners = []; + } + + applyValues() { + this.enabledCheckbox.checked = this.optionsDictionary.enabled; + this.allowSecondarySearchesCheckbox.checked = this.optionsDictionary.allowSecondarySearches; + this.priorityInput.value = `${this.optionsDictionary.priority}`; + } + + onEnabledChanged(e) { + this.optionsDictionary.enabled = !!e.target.checked; + this.save(); + } + + onAllowSecondarySearchesChanged(e) { + this.optionsDictionary.allowSecondarySearches = !!e.target.checked; + this.save(); + } + + onPriorityChanged(e) { + let value = Number.parseFloat(e.target.value); + if (Number.isNaN(value)) { + value = this.optionsDictionary.priority; + } else { + this.optionsDictionary.priority = value; + this.save(); + } + + e.target.value = `${value}`; + } +} + +class SettingsDictionaryExtraUI { + constructor(parent, totalCounts, remainders, totalRemainder, content) { + this.parent = parent; + this.content = content; + + this.content.querySelector('.dict-total-count').textContent = `${totalRemainder} item${totalRemainder !== 1 ? 's' : ''}`; + + const node = this.content.querySelector('.dict-counts'); + node.textContent = JSON.stringify({ + counts: totalCounts, + remainders: remainders + }, null, 4); + node.removeAttribute('hidden'); + } + + cleanup() { + if (this.content !== null) { + if (this.content.parentNode !== null) { + this.content.parentNode.removeChild(this.content); + } + this.content = null; + } + } +} + + +async function dictSettingsInitialize() { + const optionsContext = getOptionsContext(); + const options = await apiOptionsGet(optionsContext); + + dictionaryUI = new SettingsDictionaryListUI( + document.querySelector('#dict-groups'), + document.querySelector('#dict-template'), + document.querySelector('#dict-groups-extra'), + document.querySelector('#dict-extra-template'), + options.dictionaries + ); + dictionaryUI.save = () => apiOptionsSave(); + + document.querySelector('#dict-purge-link').addEventListener('click', (e) => onDictionaryPurge(e), false); + document.querySelector('#dict-file-button').addEventListener('click', (e) => onDictionaryImportButtonClick(e), false); + document.querySelector('#dict-file').addEventListener('change', (e) => onDictionaryImport(e), false); + document.querySelector('#dict-main').addEventListener('change', (e) => onDictionaryMainChanged(e), false); + + onDatabaseUpdated(options); +} + +async function onDatabaseUpdated(options) { + try { + const dictionaries = await utilDatabaseGetDictionaryInfo(); + dictionaryUI.setDictionaries(dictionaries); + + updateMainDictionarySelect(options, dictionaries); + + const {counts, total} = await utilDatabaseGetDictionaryCounts(dictionaries.map(v => v.title), true); + dictionaryUI.setCounts(counts, total); + } catch (e) { + dictionaryErrorsShow([e]); + } +} + +async function updateMainDictionarySelect(options, dictionaries) { + const select = document.querySelector('#dict-main'); + select.textContent = ''; // Empty + + let option = document.createElement('option'); + option.className = 'text-muted'; + option.value = ''; + option.textContent = 'Not selected'; + select.appendChild(option); + + let value = ''; + const currentValue = options.general.mainDictionary; + for (const {title, sequenced} of toIterable(dictionaries)) { + if (!sequenced) { continue; } + + option = document.createElement('option'); + option.value = title; + option.textContent = title; + select.appendChild(option); + + if (title === currentValue) { + value = title; + } + } + + select.value = value; + + if (options.general.mainDictionary !== value) { + options.general.mainDictionary = value; + apiOptionsSave(); + } +} + +async function onDictionaryMainChanged(e) { + const value = e.target.value; + const optionsContext = getOptionsContext(); + const options = await apiOptionsGet(optionsContext); + options.general.mainDictionary = value; + apiOptionsSave(); +} + + +function dictionaryErrorToString(error) { + if (error.toString) { + error = error.toString(); + } else { + error = `${error}`; + } + + for (const [match, subst] of dictionaryErrorToString.overrides) { + if (error.includes(match)) { + error = subst; + break; + } + } + + return error; +} +dictionaryErrorToString.overrides = [ + [ + 'A mutation operation was attempted on a database that did not allow mutations.', + 'Access to IndexedDB appears to be restricted. Firefox seems to require that the history preference is set to "Remember history" before IndexedDB use of any kind is allowed.' + ], + [ + 'The operation failed for reasons unrelated to the database itself and not covered by any other error code.', + 'Unable to access IndexedDB due to a possibly corrupt user profile. Try using the "Refresh Firefox" feature to reset your user profile.' + ], + [ + 'BulkError', + 'Unable to finish importing dictionary data into IndexedDB. This may indicate that you do not have sufficient disk space available to complete this operation.' + ] +]; + +function dictionaryErrorsShow(errors) { + const dialog = $('#dict-error'); + dialog.show().text(''); + + if (errors !== null && errors.length > 0) { + const uniqueErrors = {}; + for (let e of errors) { + console.error(e); + e = dictionaryErrorToString(e); + uniqueErrors[e] = uniqueErrors.hasOwnProperty(e) ? uniqueErrors[e] + 1 : 1; + } + + for (const e in uniqueErrors) { + const count = uniqueErrors[e]; + const div = document.createElement('p'); + if (count > 1) { + div.textContent = `${e} `; + const em = document.createElement('em'); + em.textContent = `(${count})`; + div.appendChild(em); + } else { + div.textContent = `${e}`; + } + dialog.append($(div)); + } + + dialog.show(); + } else { + dialog.hide(); + } +} + + +function dictionarySpinnerShow(show) { + const spinner = $('#dict-spinner'); + if (show) { + spinner.show(); + } else { + spinner.hide(); + } +} + +function onDictionaryImportButtonClick() { + const dictFile = document.querySelector('#dict-file'); + dictFile.click(); +} + +async function onDictionaryPurge(e) { + e.preventDefault(); + + const dictControls = $('#dict-importer, #dict-groups, #dict-groups-extra, #dict-main-group').hide(); + const dictProgress = $('#dict-purge').show(); + + try { + dictionaryErrorsShow(null); + dictionarySpinnerShow(true); + + await utilDatabasePurge(); + for (const options of toIterable(await getOptionsArray())) { + options.dictionaries = utilBackgroundIsolate({}); + options.general.mainDictionary = ''; + } + await settingsSaveOptions(); + + const optionsContext = getOptionsContext(); + const options = await apiOptionsGet(optionsContext); + onDatabaseUpdated(options); + } catch (err) { + dictionaryErrorsShow([err]); + } finally { + dictionarySpinnerShow(false); + + dictControls.show(); + dictProgress.hide(); + + if (storageEstimate.mostRecent !== null) { + storageUpdateStats(); + } + } +} + +async function onDictionaryImport(e) { + const dictFile = $('#dict-file'); + const dictControls = $('#dict-importer').hide(); + const dictProgress = $('#dict-import-progress').show(); + + try { + dictionaryErrorsShow(null); + dictionarySpinnerShow(true); + + const setProgress = percent => dictProgress.find('.progress-bar').css('width', `${percent}%`); + const updateProgress = (total, current) => { + setProgress(current / total * 100.0); + if (storageEstimate.mostRecent !== null && !storageUpdateStats.isUpdating) { + storageUpdateStats(); + } + }; + setProgress(0.0); + + const exceptions = []; + const summary = await utilDatabaseImport(e.target.files[0], updateProgress, exceptions); + for (const options of toIterable(await getOptionsArray())) { + const dictionaryOptions = SettingsDictionaryListUI.createDictionaryOptions(); + dictionaryOptions.enabled = true; + options.dictionaries[summary.title] = dictionaryOptions; + if (summary.sequenced && options.general.mainDictionary === '') { + options.general.mainDictionary = summary.title; + } + } + await settingsSaveOptions(); + + if (exceptions.length > 0) { + exceptions.push(`Dictionary may not have been imported properly: ${exceptions.length} error${exceptions.length === 1 ? '' : 's'} reported.`); + dictionaryErrorsShow(exceptions); + } + + const optionsContext = getOptionsContext(); + const options = await apiOptionsGet(optionsContext); + onDatabaseUpdated(options); + } catch (err) { + dictionaryErrorsShow([err]); + } finally { + dictionarySpinnerShow(false); + + dictFile.val(''); + dictControls.show(); + dictProgress.hide(); + } +} diff --git a/ext/bg/js/settings.js b/ext/bg/js/settings.js index f2250911..e4446851 100644 --- a/ext/bg/js/settings.js +++ b/ext/bg/js/settings.js @@ -81,16 +81,6 @@ async function formRead(options) { options.anki.kanji.model = $('#anki-kanji-model').val(); options.anki.kanji.fields = utilBackgroundIsolate(ankiFieldsToDict($('#kanji .anki-field-value'))); } - - options.general.mainDictionary = $('#dict-main').val(); - $('.dict-group').each((index, element) => { - const dictionary = $(element); - options.dictionaries[dictionary.data('title')] = utilBackgroundIsolate({ - priority: parseInt(dictionary.find('.dict-priority').val(), 10), - enabled: dictionary.find('.dict-enabled').prop('checked'), - allowSecondarySearches: dictionary.find('.dict-allow-secondary-searches').prop('checked') - }); - }); } async function formWrite(options) { @@ -144,13 +134,6 @@ async function formWrite(options) { $('#screenshot-quality').val(options.anki.screenshot.quality); $('#field-templates').val(options.anki.fieldTemplates); - try { - await dictionaryGroupsPopulate(options); - await formMainDictionaryOptionsPopulate(options); - } catch (e) { - dictionaryErrorsShow([e]); - } - try { await ankiDeckAndModelPopulate(options); } catch (e) { @@ -161,10 +144,6 @@ async function formWrite(options) { } function formSetupEventListeners() { - $('#dict-purge-link').click(utilAsync(onDictionaryPurge)); - $('#dict-file').change(utilAsync(onDictionaryImport)); - $('#dict-file-button').click(onDictionaryImportButtonClick); - $('#field-templates-reset').click(utilAsync(onAnkiFieldTemplatesReset)); $('input, select, textarea').not('.anki-model').not('.ignore-form-changes *').change(utilAsync(onFormOptionsChanged)); $('.anki-model').change(utilAsync(onAnkiModelChanged)); @@ -184,23 +163,6 @@ function formUpdateVisibility(options) { } } -async function formMainDictionaryOptionsPopulate(options) { - const select = $('#dict-main').empty(); - select.append($('')); - - let mainDictionary = ''; - for (const dictRow of toIterable(await utilDatabaseSummarize())) { - if (dictRow.sequenced) { - select.append($(``)); - if (dictRow.title === options.general.mainDictionary) { - mainDictionary = dictRow.title; - } - } - } - - select.val(mainDictionary); -} - async function onFormOptionsChanged(e) { if (!e.originalEvent && !e.isTrigger) { return; @@ -239,6 +201,7 @@ async function onReady() { appearanceInitialize(); await audioSettingsInitialize(); await profileOptionsSetup(); + await dictSettingsInitialize(); storageInfoInitialize(); @@ -422,228 +385,6 @@ function onMessage({action, params}, sender, callback) { } -/* - * Dictionary - */ - -function dictionaryErrorToString(error) { - if (error.toString) { - error = error.toString(); - } else { - error = `${error}`; - } - - for (const [match, subst] of dictionaryErrorToString.overrides) { - if (error.includes(match)) { - error = subst; - break; - } - } - - return error; -} -dictionaryErrorToString.overrides = [ - [ - 'A mutation operation was attempted on a database that did not allow mutations.', - 'Access to IndexedDB appears to be restricted. Firefox seems to require that the history preference is set to "Remember history" before IndexedDB use of any kind is allowed.' - ], - [ - 'The operation failed for reasons unrelated to the database itself and not covered by any other error code.', - 'Unable to access IndexedDB due to a possibly corrupt user profile. Try using the "Refresh Firefox" feature to reset your user profile.' - ], - [ - 'BulkError', - 'Unable to finish importing dictionary data into IndexedDB. This may indicate that you do not have sufficient disk space available to complete this operation.' - ] -]; - -function dictionaryErrorsShow(errors) { - const dialog = $('#dict-error'); - dialog.show().text(''); - - if (errors !== null && errors.length > 0) { - const uniqueErrors = {}; - for (let e of errors) { - e = dictionaryErrorToString(e); - uniqueErrors[e] = uniqueErrors.hasOwnProperty(e) ? uniqueErrors[e] + 1 : 1; - } - - for (const e in uniqueErrors) { - const count = uniqueErrors[e]; - const div = document.createElement('p'); - if (count > 1) { - div.textContent = `${e} `; - const em = document.createElement('em'); - em.textContent = `(${count})`; - div.appendChild(em); - } else { - div.textContent = `${e}`; - } - dialog.append($(div)); - } - - dialog.show(); - } else { - dialog.hide(); - } -} - -function dictionarySpinnerShow(show) { - const spinner = $('#dict-spinner'); - if (show) { - spinner.show(); - } else { - spinner.hide(); - } -} - -function dictionaryGroupsSort() { - const dictGroups = $('#dict-groups'); - const dictGroupChildren = dictGroups.children('.dict-group').sort((ca, cb) => { - const pa = parseInt($(ca).find('.dict-priority').val(), 10); - const pb = parseInt($(cb).find('.dict-priority').val(), 10); - if (pa < pb) { - return 1; - } else if (pa > pb) { - return -1; - } else { - return 0; - } - }); - - dictGroups.append(dictGroupChildren); -} - -async function dictionaryGroupsPopulate(options) { - const dictGroups = $('#dict-groups').empty(); - const dictWarning = $('#dict-warning').hide(); - - const dictRows = toIterable(await utilDatabaseSummarize()); - if (dictRows.length === 0) { - dictWarning.show(); - } - - for (const dictRow of toIterable(dictRowsSort(dictRows, options))) { - const dictOptions = options.dictionaries[dictRow.title] || { - enabled: false, - priority: 0, - allowSecondarySearches: false - }; - - const dictHtml = await apiTemplateRender('dictionary.html', { - enabled: dictOptions.enabled, - priority: dictOptions.priority, - allowSecondarySearches: dictOptions.allowSecondarySearches, - title: dictRow.title, - version: dictRow.version, - revision: dictRow.revision, - outdated: dictRow.version < 3 - }); - - dictGroups.append($(dictHtml)); - } - - formUpdateVisibility(options); - - $('.dict-enabled, .dict-priority, .dict-allow-secondary-searches').change(e => { - dictionaryGroupsSort(); - onFormOptionsChanged(e); - }); -} - -async function onDictionaryPurge(e) { - e.preventDefault(); - - const dictControls = $('#dict-importer, #dict-groups, #dict-main-group').hide(); - const dictProgress = $('#dict-purge').show(); - - try { - dictionaryErrorsShow(null); - dictionarySpinnerShow(true); - - await utilDatabasePurge(); - for (const options of toIterable(await getOptionsArray())) { - options.dictionaries = utilBackgroundIsolate({}); - options.general.mainDictionary = ''; - } - await settingsSaveOptions(); - - const optionsContext = getOptionsContext(); - const options = await apiOptionsGet(optionsContext); - await dictionaryGroupsPopulate(options); - await formMainDictionaryOptionsPopulate(options); - } catch (e) { - dictionaryErrorsShow([e]); - } finally { - dictionarySpinnerShow(false); - - dictControls.show(); - dictProgress.hide(); - - if (storageEstimate.mostRecent !== null) { - storageUpdateStats(); - } - } -} - -function onDictionaryImportButtonClick() { - const dictFile = document.querySelector('#dict-file'); - dictFile.click(); -} - -async function onDictionaryImport(e) { - const dictFile = $('#dict-file'); - const dictControls = $('#dict-importer').hide(); - const dictProgress = $('#dict-import-progress').show(); - - try { - dictionaryErrorsShow(null); - dictionarySpinnerShow(true); - - const setProgress = percent => dictProgress.find('.progress-bar').css('width', `${percent}%`); - const updateProgress = (total, current) => { - setProgress(current / total * 100.0); - if (storageEstimate.mostRecent !== null && !storageUpdateStats.isUpdating) { - storageUpdateStats(); - } - }; - setProgress(0.0); - - const exceptions = []; - const summary = await utilDatabaseImport(e.target.files[0], updateProgress, exceptions); - for (const options of toIterable(await getOptionsArray())) { - options.dictionaries[summary.title] = utilBackgroundIsolate({ - enabled: true, - priority: 0, - allowSecondarySearches: false - }); - if (summary.sequenced && options.general.mainDictionary === '') { - options.general.mainDictionary = summary.title; - } - } - await settingsSaveOptions(); - - if (exceptions.length > 0) { - exceptions.push(`Dictionary may not have been imported properly: ${exceptions.length} error${exceptions.length === 1 ? '' : 's'} reported.`); - dictionaryErrorsShow(exceptions); - } - - const optionsContext = getOptionsContext(); - const options = await apiOptionsGet(optionsContext); - await dictionaryGroupsPopulate(options); - await formMainDictionaryOptionsPopulate(options); - } catch (e) { - dictionaryErrorsShow([e]); - } finally { - dictionarySpinnerShow(false); - - dictFile.val(''); - dictControls.show(); - dictProgress.hide(); - } -} - - /* * Anki */ diff --git a/ext/bg/js/templates.js b/ext/bg/js/templates.js index 59516d97..26d1575f 100644 --- a/ext/bg/js/templates.js +++ b/ext/bg/js/templates.js @@ -1,32 +1,5 @@ (function() { var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; -templates['dictionary.html'] = template({"1":function(container,depth0,helpers,partials,data) { - return "

This dictionary is outdated and may not support new extension features; please import the latest version.

\n"; -},"3":function(container,depth0,helpers,partials,data) { - return "checked"; -},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { - var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; - - return "
\n

" - + alias4(((helper = (helper = helpers.title || (depth0 != null ? depth0.title : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"title","hash":{},"data":data}) : helper))) - + " rev." - + alias4(((helper = (helper = helpers.revision || (depth0 != null ? depth0.revision : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"revision","hash":{},"data":data}) : helper))) - + "

\n" - + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.outdated : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + "\n
\n \n
\n
\n \n
\n
\n \n \n
\n
\n"; -},"useData":true}); templates['kanji.html'] = template({"1":function(container,depth0,helpers,partials,data) { var stack1; diff --git a/ext/bg/js/util.js b/ext/bg/js/util.js index 1ca0833b..3554ec3d 100644 --- a/ext/bg/js/util.js +++ b/ext/bg/js/util.js @@ -84,6 +84,14 @@ function utilDatabaseSummarize() { return utilBackend().translator.database.summarize(); } +function utilDatabaseGetDictionaryInfo() { + return utilBackend().translator.database.getDictionaryInfo(); +} + +function utilDatabaseGetDictionaryCounts(dictionaryNames, getTotal) { + return utilBackend().translator.database.getDictionaryCounts(dictionaryNames, getTotal); +} + function utilAnkiGetModelFieldNames(modelName) { return utilBackend().anki.getModelFieldNames(modelName); } -- cgit v1.2.3