Update how dictionaries are displayed on the settings page

This commit is contained in:
toasted-nutbread 2019-11-02 14:06:16 -04:00
parent 79069d5908
commit 2ab871e7ee
6 changed files with 520 additions and 305 deletions

View File

@ -0,0 +1,482 @@
/*
* Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
*/
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();
}
}

View File

@ -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($('<option class="text-muted" value="">Not selected</option>'));
let mainDictionary = '';
for (const dictRow of toIterable(await utilDatabaseSummarize())) {
if (dictRow.sequenced) {
select.append($(`<option value="${dictRow.title}">${dictRow.title}</option>`));
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
*/

View File

@ -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 " <p class=\"text-warning\">This dictionary is outdated and may not support new extension features; please import the latest version.</p>\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 "<div class=\"dict-group well well-sm\" data-title=\""
+ 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)))
+ "\">\n <h4><span class=\"text-muted glyphicon glyphicon-book\"></span> "
+ 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)))
+ " <small>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)))
+ "</small></h4>\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 <div class=\"checkbox\">\n <label><input type=\"checkbox\" class=\"dict-enabled\" "
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.enabled : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "> Enable search</label>\n </div>\n <div class=\"checkbox options-advanced\">\n <label><input type=\"checkbox\" class=\"dict-allow-secondary-searches\" "
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.allowSecondarySearches : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "> Allow secondary searches</label>\n </div>\n <div class=\"form-group options-advanced\">\n <label for=\"dict-"
+ 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)))
+ "\">Result priority</label>\n <input type=\"number\" value=\""
+ alias4(((helper = (helper = helpers.priority || (depth0 != null ? depth0.priority : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"priority","hash":{},"data":data}) : helper)))
+ "\" id=\"dict-"
+ 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)))
+ "\" class=\"form-control dict-priority\">\n </div>\n</div>\n";
},"useData":true});
templates['kanji.html'] = template({"1":function(container,depth0,helpers,partials,data) {
var stack1;

View File

@ -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);
}

View File

@ -410,7 +410,7 @@
</div>
</div>
<div>
<div class="ignore-form-changes">
<div>
<img src="/mixed/img/spinner.gif" class="pull-right" id="dict-spinner" alt>
<h3>Dictionaries</h3>
@ -434,6 +434,7 @@
<div class="alert alert-danger" id="dict-error"></div>
<div id="dict-groups"></div>
<div id="dict-groups-extra"></div>
<div id="dict-import-progress">
Dictionary data is being imported, please be patient...
@ -451,6 +452,32 @@
<button class="btn btn-primary" id="dict-file-button">Import Dictionary</button>
<div hidden><input type="file" id="dict-file"></div>
</div>
<template id="dict-template"><div class="dict-group well well-sm">
<h4><span class="text-muted glyphicon glyphicon-book"></span> <span class="dict-title"></span> <small class="dict-revision"></small></h4>
<p class="text-warning" hidden>This dictionary is outdated and may not support new extension features; please import the latest version.</p>
<div class="checkbox">
<label><input type="checkbox" class="dict-enabled"> Enable search</label>
</div>
<div class="checkbox options-advanced">
<label><input type="checkbox" class="dict-allow-secondary-searches"> Allow secondary searches</label>
</div>
<div class="form-group options-advanced">
<label class="dict-result-priority-label">Result priority</label>
<input type="number" class="form-control dict-priority">
</div>
<pre class="debug dict-counts" hidden></pre>
</div></template>
<template id="dict-extra-template"><div class="well well-sm">
<h4><span class="text-muted glyphicon glyphicon-alert"></span> <span class="dict-title">Unassociated Data</span> <small class="dict-total-count"></small></h4>
<p class="text-warning">
The database contains extra data which is not associated with any installed dictionary.
Purging the database can fix this issue.
</p>
<pre class="debug dict-counts" hidden></pre>
</div></template>
</div>
<div id="storage-info">
@ -700,6 +727,7 @@
<script src="/bg/js/util.js"></script>
<script src="/mixed/js/audio.js"></script>
<script src="/bg/js/settings-dictionaries.js"></script>
<script src="/bg/js/settings-profiles.js"></script>
<script src="/bg/js/settings.js"></script>
</body>

View File

@ -1,17 +0,0 @@
<div class="dict-group well well-sm" data-title="{{title}}">
<h4><span class="text-muted glyphicon glyphicon-book"></span> {{title}} <small>rev.{{revision}}</small></h4>
{{#if outdated}}
<p class="text-warning">This dictionary is outdated and may not support new extension features; please import the latest version.</p>
{{/if}}
<div class="checkbox">
<label><input type="checkbox" class="dict-enabled" {{#if enabled}}checked{{/if}}> Enable search</label>
</div>
<div class="checkbox options-advanced">
<label><input type="checkbox" class="dict-allow-secondary-searches" {{#if allowSecondarySearches}}checked{{/if}}> Allow secondary searches</label>
</div>
<div class="form-group options-advanced">
<label for="dict-{{title}}">Result priority</label>
<input type="number" value="{{priority}}" id="dict-{{title}}" class="form-control dict-priority">
</div>
</div>