aboutsummaryrefslogtreecommitdiff
path: root/ext/bg
diff options
context:
space:
mode:
Diffstat (limited to 'ext/bg')
-rw-r--r--ext/bg/background.html3
-rw-r--r--ext/bg/context.html3
-rw-r--r--ext/bg/css/settings.css2
-rw-r--r--ext/bg/js/anki.js12
-rw-r--r--ext/bg/js/api.js10
-rw-r--r--ext/bg/js/audio.js8
-rw-r--r--ext/bg/js/backend-api-forwarder.js4
-rw-r--r--ext/bg/js/backend.js10
-rw-r--r--ext/bg/js/conditions-ui.js20
-rw-r--r--ext/bg/js/conditions.js30
-rw-r--r--ext/bg/js/context.js34
-rw-r--r--ext/bg/js/database.js24
-rw-r--r--ext/bg/js/deinflector.js2
-rw-r--r--ext/bg/js/dictionary.js16
-rw-r--r--ext/bg/js/handlebars.js2
-rw-r--r--ext/bg/js/mecab.js4
-rw-r--r--ext/bg/js/options.js12
-rw-r--r--ext/bg/js/page-exit-prevention.js60
-rw-r--r--ext/bg/js/profile-conditions.js2
-rw-r--r--ext/bg/js/request.js2
-rw-r--r--ext/bg/js/search-query-parser.js18
-rw-r--r--ext/bg/js/search.js13
-rw-r--r--ext/bg/js/settings.js852
-rw-r--r--ext/bg/js/settings/anki-templates.js109
-rw-r--r--ext/bg/js/settings/anki.js247
-rw-r--r--ext/bg/js/settings/audio.js102
-rw-r--r--ext/bg/js/settings/dictionaries.js (renamed from ext/bg/js/settings-dictionaries.js)14
-rw-r--r--ext/bg/js/settings/main.js239
-rw-r--r--ext/bg/js/settings/popup-preview-frame.js (renamed from ext/bg/js/settings-popup-preview.js)2
-rw-r--r--ext/bg/js/settings/popup-preview.js62
-rw-r--r--ext/bg/js/settings/profiles.js (renamed from ext/bg/js/settings-profiles.js)18
-rw-r--r--ext/bg/js/settings/storage.js138
-rw-r--r--ext/bg/js/templates.js212
-rw-r--r--ext/bg/js/translator.js20
-rw-r--r--ext/bg/js/util.js14
-rw-r--r--ext/bg/legal.html2
-rw-r--r--ext/bg/search.html6
-rw-r--r--ext/bg/settings-popup-preview.html6
-rw-r--r--ext/bg/settings.html30
39 files changed, 1249 insertions, 1115 deletions
diff --git a/ext/bg/background.html b/ext/bg/background.html
index 6e6e7c26..5a6970c3 100644
--- a/ext/bg/background.html
+++ b/ext/bg/background.html
@@ -18,7 +18,8 @@
<script src="/mixed/lib/jszip.min.js"></script>
<script src="/mixed/lib/wanakana.min.js"></script>
- <script src="/mixed/js/extension.js"></script>
+ <script src="/mixed/js/core.js"></script>
+ <script src="/mixed/js/dom.js"></script>
<script src="/bg/js/anki.js"></script>
<script src="/bg/js/mecab.js"></script>
diff --git a/ext/bg/context.html b/ext/bg/context.html
index bd62270b..eda09a68 100644
--- a/ext/bg/context.html
+++ b/ext/bg/context.html
@@ -178,7 +178,8 @@
</a>
</div>
- <script src="/mixed/js/extension.js"></script>
+ <script src="/mixed/js/core.js"></script>
+ <script src="/mixed/js/dom.js"></script>
<script src="/bg/js/api.js"></script>
<script src="/bg/js/options.js"></script>
diff --git a/ext/bg/css/settings.css b/ext/bg/css/settings.css
index 5dfbd931..8adae47c 100644
--- a/ext/bg/css/settings.css
+++ b/ext/bg/css/settings.css
@@ -17,7 +17,7 @@
*/
-#anki-spinner, #anki-error,
+#anki-spinner,
#dict-spinner, #dict-import-progress,
.storage-hidden, #storage-spinner {
display: none;
diff --git a/ext/bg/js/anki.js b/ext/bg/js/anki.js
index 9f851f13..17b93620 100644
--- a/ext/bg/js/anki.js
+++ b/ext/bg/js/anki.js
@@ -74,7 +74,7 @@ class AnkiConnect {
async findNoteIds(notes) {
await this.checkVersion();
- const actions = notes.map(note => ({
+ const actions = notes.map((note) => ({
action: 'findNotes',
params: {
query: `deck:"${AnkiConnect.escapeQuery(note.deckName)}" ${AnkiConnect.fieldsToQuery(note.fields)}`
@@ -108,11 +108,11 @@ class AnkiConnect {
*/
class AnkiNull {
- async addNote(note) {
+ async addNote() {
return null;
}
- async canAddNotes(notes) {
+ async canAddNotes() {
return [];
}
@@ -124,15 +124,15 @@ class AnkiNull {
return [];
}
- async getModelFieldNames(modelName) {
+ async getModelFieldNames() {
return [];
}
- async guiBrowse(query) {
+ async guiBrowse() {
return [];
}
- async findNoteIds(notes) {
+ async findNoteIds() {
return [];
}
}
diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js
index 766fb0ed..b489b8d2 100644
--- a/ext/bg/js/api.js
+++ b/ext/bg/js/api.js
@@ -45,7 +45,7 @@ async function apiOptionsSet(changedOptions, optionsContext, source) {
function modifyOption(path, value, options) {
let pivot = options;
for (const key of path.slice(0, -1)) {
- if (!pivot.hasOwnProperty(key)) {
+ if (!hasOwn(pivot, key)) {
return false;
}
pivot = pivot[key];
@@ -207,7 +207,7 @@ async function apiDefinitionsAddable(definitions, modes, optionsContext) {
}
if (cannotAdd.length > 0) {
- const noteIdsArray = await anki.findNoteIds(cannotAdd.map(e => e[0]));
+ const noteIdsArray = await anki.findNoteIds(cannotAdd.map((e) => e[0]));
for (let i = 0, ii = Math.min(cannotAdd.length, noteIdsArray.length); i < ii; ++i) {
const noteIds = noteIdsArray[i];
if (noteIds.length > 0) {
@@ -236,7 +236,7 @@ async function apiTemplateRender(template, data, dynamic) {
async function apiCommandExec(command, params) {
const handlers = apiCommandExec.handlers;
- if (handlers.hasOwnProperty(command)) {
+ if (hasOwn(handlers, command)) {
const handler = handlers[command];
handler(params);
}
@@ -404,7 +404,9 @@ async function apiGetBrowser() {
if (info.name === 'Fennec') {
return 'firefox-mobile';
}
- } catch (e) { }
+ } catch (e) {
+ // NOP
+ }
return 'firefox';
} else {
return 'chrome';
diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js
index cd42a158..dc0ba5eb 100644
--- a/ext/bg/js/audio.js
+++ b/ext/bg/js/audio.js
@@ -107,12 +107,12 @@ const audioUrlBuilders = {
'custom': async (definition, optionsContext) => {
const options = await apiOptionsGet(optionsContext);
const customSourceUrl = options.audio.customSourceUrl;
- return customSourceUrl.replace(/\{([^\}]*)\}/g, (m0, m1) => (definition.hasOwnProperty(m1) ? `${definition[m1]}` : m0));
+ return customSourceUrl.replace(/\{([^}]*)\}/g, (m0, m1) => (hasOwn(definition, m1) ? `${definition[m1]}` : m0));
}
};
async function audioGetUrl(definition, mode, optionsContext, download) {
- if (audioUrlBuilders.hasOwnProperty(mode)) {
+ if (hasOwn(audioUrlBuilders, mode)) {
const handler = audioUrlBuilders[mode];
try {
return await handler(definition, optionsContext, download);
@@ -133,7 +133,7 @@ function audioUrlNormalize(url, baseUrl, basePath) {
// Begins with "/"
url = baseUrl + url;
}
- } else if (!/^[a-z][a-z0-9\+\-\.]*:/i.test(url)) {
+ } else if (!/^[a-z][a-z0-9\-+.]*:/i.test(url)) {
// No URI scheme => relative path
url = baseUrl + basePath + url;
}
@@ -171,7 +171,7 @@ async function audioInject(definition, fields, sources, optionsContext) {
try {
let audioSourceDefinition = definition;
- if (definition.hasOwnProperty('expressions')) {
+ if (hasOwn(definition, 'expressions')) {
audioSourceDefinition = definition.expressions[0];
}
diff --git a/ext/bg/js/backend-api-forwarder.js b/ext/bg/js/backend-api-forwarder.js
index 979afd16..db4d30b9 100644
--- a/ext/bg/js/backend-api-forwarder.js
+++ b/ext/bg/js/backend-api-forwarder.js
@@ -37,8 +37,8 @@ class BackendApiForwarder {
const forwardPort = chrome.tabs.connect(tabId, {name: 'frontend-api-receiver'});
- port.onMessage.addListener(message => forwardPort.postMessage(message));
- forwardPort.onMessage.addListener(message => port.postMessage(message));
+ port.onMessage.addListener((message) => forwardPort.postMessage(message));
+ forwardPort.onMessage.addListener((message) => port.postMessage(message));
port.onDisconnect.addListener(() => forwardPort.disconnect());
forwardPort.onDisconnect.addListener(() => port.disconnect());
}
diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js
index 45db9660..d9f9b586 100644
--- a/ext/bg/js/backend.js
+++ b/ext/bg/js/backend.js
@@ -60,7 +60,7 @@ class Backend {
this.applyOptions();
const callback = () => this.checkLastError(chrome.runtime.lastError);
- chrome.tabs.query({}, tabs => {
+ chrome.tabs.query({}, (tabs) => {
for (const tab of tabs) {
chrome.tabs.sendMessage(tab.id, {action: 'optionsUpdate', params: {source}}, callback);
}
@@ -73,12 +73,12 @@ class Backend {
onMessage({action, params}, sender, callback) {
const handlers = Backend.messageHandlers;
- if (handlers.hasOwnProperty(action)) {
+ if (hasOwn(handlers, action)) {
const handler = handlers[action];
const promise = handler(params, sender);
promise.then(
- result => callback({result}),
- error => callback({error: errorToJson(error)})
+ (result) => callback({result}),
+ (error) => callback({error: errorToJson(error)})
);
}
@@ -177,7 +177,7 @@ class Backend {
}
}
- checkLastError(e) {
+ checkLastError() {
// NOP
}
}
diff --git a/ext/bg/js/conditions-ui.js b/ext/bg/js/conditions-ui.js
index 43c6dc08..cc9db087 100644
--- a/ext/bg/js/conditions-ui.js
+++ b/ext/bg/js/conditions-ui.js
@@ -84,7 +84,7 @@ ConditionsUI.Container = class Container {
createDefaultCondition(type) {
let operator = '';
let value = '';
- if (this.conditionDescriptors.hasOwnProperty(type)) {
+ if (hasOwn(this.conditionDescriptors, type)) {
const conditionDescriptor = this.conditionDescriptors[type];
operator = conditionDescriptor.defaultOperator;
({value} = this.getOperatorDefaultValue(type, operator));
@@ -96,15 +96,15 @@ ConditionsUI.Container = class Container {
}
getOperatorDefaultValue(type, operator) {
- if (this.conditionDescriptors.hasOwnProperty(type)) {
+ if (hasOwn(this.conditionDescriptors, type)) {
const conditionDescriptor = this.conditionDescriptors[type];
- if (conditionDescriptor.operators.hasOwnProperty(operator)) {
+ if (hasOwn(conditionDescriptor.operators, operator)) {
const operatorDescriptor = conditionDescriptor.operators[operator];
- if (operatorDescriptor.hasOwnProperty('defaultValue')) {
+ if (hasOwn(operatorDescriptor, 'defaultValue')) {
return {value: operatorDescriptor.defaultValue, fromOperator: true};
}
}
- if (conditionDescriptor.hasOwnProperty('defaultValue')) {
+ if (hasOwn(conditionDescriptor, 'defaultValue')) {
return {value: conditionDescriptor.defaultValue, fromOperator: false};
}
}
@@ -219,7 +219,7 @@ ConditionsUI.Condition = class Condition {
optionGroup.empty();
const type = this.condition.type;
- if (conditionDescriptors.hasOwnProperty(type)) {
+ if (hasOwn(conditionDescriptors, type)) {
const conditionDescriptor = conditionDescriptors[type];
const operators = conditionDescriptor.operators;
for (const operatorName of Object.keys(operators)) {
@@ -240,23 +240,23 @@ ConditionsUI.Condition = class Condition {
};
const objects = [];
- if (conditionDescriptors.hasOwnProperty(type)) {
+ if (hasOwn(conditionDescriptors, type)) {
const conditionDescriptor = conditionDescriptors[type];
objects.push(conditionDescriptor);
- if (conditionDescriptor.operators.hasOwnProperty(operator)) {
+ if (hasOwn(conditionDescriptor.operators, operator)) {
const operatorDescriptor = conditionDescriptor.operators[operator];
objects.push(operatorDescriptor);
}
}
for (const object of objects) {
- if (object.hasOwnProperty('placeholder')) {
+ if (hasOwn(object, 'placeholder')) {
props.placeholder = object.placeholder;
}
if (object.type === 'number') {
props.type = 'number';
for (const prop of ['step', 'min', 'max']) {
- if (object.hasOwnProperty(prop)) {
+ if (hasOwn(object, prop)) {
props[prop] = object[prop];
}
}
diff --git a/ext/bg/js/conditions.js b/ext/bg/js/conditions.js
index ed4b14f5..c0f0f301 100644
--- a/ext/bg/js/conditions.js
+++ b/ext/bg/js/conditions.js
@@ -18,14 +18,14 @@
function conditionsValidateOptionValue(object, value) {
- if (object.hasOwnProperty('validate') && !object.validate(value)) {
+ if (hasOwn(object, 'validate') && !object.validate(value)) {
throw new Error('Invalid value for condition');
}
- if (object.hasOwnProperty('transform')) {
+ if (hasOwn(object, 'transform')) {
value = object.transform(value);
- if (object.hasOwnProperty('validateTransformed') && !object.validateTransformed(value)) {
+ if (hasOwn(object, 'validateTransformed') && !object.validateTransformed(value)) {
throw new Error('Invalid value for condition');
}
}
@@ -34,12 +34,12 @@ function conditionsValidateOptionValue(object, value) {
}
function conditionsNormalizeOptionValue(descriptors, type, operator, optionValue) {
- if (!descriptors.hasOwnProperty(type)) {
+ if (!hasOwn(descriptors, type)) {
throw new Error('Invalid type');
}
const conditionDescriptor = descriptors[type];
- if (!conditionDescriptor.operators.hasOwnProperty(operator)) {
+ if (!hasOwn(conditionDescriptor.operators, operator)) {
throw new Error('Invalid operator');
}
@@ -48,28 +48,28 @@ function conditionsNormalizeOptionValue(descriptors, type, operator, optionValue
let transformedValue = conditionsValidateOptionValue(conditionDescriptor, optionValue);
transformedValue = conditionsValidateOptionValue(operatorDescriptor, transformedValue);
- if (operatorDescriptor.hasOwnProperty('transformReverse')) {
+ if (hasOwn(operatorDescriptor, 'transformReverse')) {
transformedValue = operatorDescriptor.transformReverse(transformedValue);
}
return transformedValue;
}
function conditionsTestValueThrowing(descriptors, type, operator, optionValue, value) {
- if (!descriptors.hasOwnProperty(type)) {
+ if (!hasOwn(descriptors, type)) {
throw new Error('Invalid type');
}
const conditionDescriptor = descriptors[type];
- if (!conditionDescriptor.operators.hasOwnProperty(operator)) {
+ if (!hasOwn(conditionDescriptor.operators, operator)) {
throw new Error('Invalid operator');
}
const operatorDescriptor = conditionDescriptor.operators[operator];
- if (operatorDescriptor.hasOwnProperty('transform')) {
- if (operatorDescriptor.hasOwnProperty('transformCache')) {
+ if (hasOwn(operatorDescriptor, 'transform')) {
+ if (hasOwn(operatorDescriptor, 'transformCache')) {
const key = `${optionValue}`;
const transformCache = operatorDescriptor.transformCache;
- if (transformCache.hasOwnProperty(key)) {
+ if (hasOwn(transformCache, key)) {
optionValue = transformCache[key];
} else {
optionValue = operatorDescriptor.transform(optionValue);
@@ -93,23 +93,23 @@ function conditionsTestValue(descriptors, type, operator, optionValue, value) {
function conditionsClearCaches(descriptors) {
for (const type in descriptors) {
- if (!descriptors.hasOwnProperty(type)) {
+ if (!hasOwn(descriptors, type)) {
continue;
}
const conditionDescriptor = descriptors[type];
- if (conditionDescriptor.hasOwnProperty('transformCache')) {
+ if (hasOwn(conditionDescriptor, 'transformCache')) {
conditionDescriptor.transformCache = {};
}
const operatorDescriptors = conditionDescriptor.operators;
for (const operator in operatorDescriptors) {
- if (!operatorDescriptors.hasOwnProperty(operator)) {
+ if (!hasOwn(operatorDescriptors, operator)) {
continue;
}
const operatorDescriptor = operatorDescriptors[operator];
- if (operatorDescriptor.hasOwnProperty('transformCache')) {
+ if (hasOwn(operatorDescriptor, 'transformCache')) {
operatorDescriptor.transformCache = {};
}
}
diff --git a/ext/bg/js/context.js b/ext/bg/js/context.js
index b288a79a..0b21f662 100644
--- a/ext/bg/js/context.js
+++ b/ext/bg/js/context.js
@@ -26,22 +26,24 @@ function showExtensionInfo() {
}
function setupButtonEvents(selector, command, url) {
- const node = document.querySelector(selector);
- node.addEventListener('click', (e) => {
- if (e.button !== 0) { return; }
- apiCommandExec(command, {newTab: e.ctrlKey});
- e.preventDefault();
- }, false);
- node.addEventListener('auxclick', (e) => {
- if (e.button !== 1) { return; }
- apiCommandExec(command, {newTab: true});
- e.preventDefault();
- }, false);
+ const nodes = document.querySelectorAll(selector);
+ for (const node of nodes) {
+ node.addEventListener('click', (e) => {
+ if (e.button !== 0) { return; }
+ apiCommandExec(command, {newTab: e.ctrlKey});
+ e.preventDefault();
+ }, false);
+ node.addEventListener('auxclick', (e) => {
+ if (e.button !== 1) { return; }
+ apiCommandExec(command, {newTab: true});
+ e.preventDefault();
+ }, false);
- if (typeof url === 'string') {
- node.href = url;
- node.target = '_blank';
- node.rel = 'noopener';
+ if (typeof url === 'string') {
+ node.href = url;
+ node.target = '_blank';
+ node.rel = 'noopener';
+ }
}
}
@@ -63,7 +65,7 @@ window.addEventListener('DOMContentLoaded', () => {
depth: 0,
url: window.location.href
};
- apiOptionsGet(optionsContext).then(options => {
+ apiOptionsGet(optionsContext).then((options) => {
const toggle = document.querySelector('#enable-search');
toggle.checked = options.general.enable;
toggle.addEventListener('change', () => apiCommandExec('toggle'), false);
diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js
index 9b560f78..a20d5f15 100644
--- a/ext/bg/js/database.js
+++ b/ext/bg/js/database.js
@@ -137,7 +137,7 @@ class Database {
const visited = {};
const results = [];
const processRow = (row, index) => {
- if (titles.includes(row.dictionary) && !visited.hasOwnProperty(row.id)) {
+ if (titles.includes(row.dictionary) && !hasOwn(visited, row.id)) {
visited[row.id] = true;
results.push(Database.createTerm(row, index));
}
@@ -257,7 +257,7 @@ class Database {
const dbTerms = dbTransaction.objectStore('tagMeta');
const dbIndex = dbTerms.index('name');
const only = IDBKeyRange.only(name);
- await Database.getAll(dbIndex, only, null, row => {
+ await Database.getAll(dbIndex, only, null, (row) => {
if (title === row.dictionary) {
result = row;
}
@@ -273,7 +273,7 @@ class Database {
const dbTransaction = this.db.transaction(['dictionaries'], 'readonly');
const dbDictionaries = dbTransaction.objectStore('dictionaries');
- await Database.getAll(dbDictionaries, null, null, info => results.push(info));
+ await Database.getAll(dbDictionaries, null, null, (info) => results.push(info));
return results;
}
@@ -308,7 +308,7 @@ class Database {
counts.push(null);
const index = i;
const query2 = IDBKeyRange.only(dictionaryNames[i]);
- const countPromise = Database.getCounts(targets, query2).then(v => counts[index] = v);
+ const countPromise = Database.getCounts(targets, query2).then((v) => counts[index] = v);
countPromises.push(countPromise);
}
await Promise.all(countPromises);
@@ -346,7 +346,7 @@ class Database {
}
};
- const indexDataLoaded = async summary => {
+ const indexDataLoaded = async (summary) => {
if (summary.version > 3) {
throw new Error('Unsupported dictionary version');
}
@@ -522,13 +522,13 @@ class Database {
await indexDataLoaded(summary);
- const buildTermBankName = index => `term_bank_${index + 1}.json`;
- const buildTermMetaBankName = index => `term_meta_bank_${index + 1}.json`;
- const buildKanjiBankName = index => `kanji_bank_${index + 1}.json`;
- const buildKanjiMetaBankName = index => `kanji_meta_bank_${index + 1}.json`;
- const buildTagBankName = index => `tag_bank_${index + 1}.json`;
+ const buildTermBankName = (index) => `term_bank_${index + 1}.json`;
+ const buildTermMetaBankName = (index) => `term_meta_bank_${index + 1}.json`;
+ const buildKanjiBankName = (index) => `kanji_bank_${index + 1}.json`;
+ const buildKanjiMetaBankName = (index) => `kanji_meta_bank_${index + 1}.json`;
+ const buildTagBankName = (index) => `tag_bank_${index + 1}.json`;
- const countBanks = namer => {
+ const countBanks = (namer) => {
let count = 0;
while (zip.files[namer(count)]) {
++count;
@@ -657,7 +657,7 @@ class Database {
const counts = {};
for (const [objectStoreName, index] of targets) {
const n = objectStoreName;
- const countPromise = Database.getCount(index, query).then(count => counts[n] = count);
+ const countPromise = Database.getCount(index, query).then((count) => counts[n] = count);
countPromises.push(countPromise);
}
return Promise.all(countPromises).then(() => counts);
diff --git a/ext/bg/js/deinflector.js b/ext/bg/js/deinflector.js
index e2fb7461..51f4723c 100644
--- a/ext/bg/js/deinflector.js
+++ b/ext/bg/js/deinflector.js
@@ -88,5 +88,5 @@ Deinflector.ruleTypes = {
'vs': 0b0000100, // Verb suru
'vk': 0b0001000, // Verb kuru
'adj-i': 0b0010000, // Adjective i
- 'iru': 0b0100000, // Intermediate -iru endings for progressive or perfect tense
+ 'iru': 0b0100000 // Intermediate -iru endings for progressive or perfect tense
};
diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js
index 9aa0af9c..0b35e32e 100644
--- a/ext/bg/js/dictionary.js
+++ b/ext/bg/js/dictionary.js
@@ -81,7 +81,7 @@ function dictTermsUndupe(definitions) {
const definitionGroups = {};
for (const definition of definitions) {
const definitionExisting = definitionGroups[definition.id];
- if (!definitionGroups.hasOwnProperty(definition.id) || definition.expression.length > definitionExisting.expression.length) {
+ if (!hasOwn(definitionGroups, definition.id) || definition.expression.length > definitionExisting.expression.length) {
definitionGroups[definition.id] = definition;
}
}
@@ -99,8 +99,8 @@ function dictTermsCompressTags(definitions) {
let lastPartOfSpeech = '';
for (const definition of definitions) {
- const dictionary = JSON.stringify(definition.definitionTags.filter(tag => tag.category === 'dictionary').map(tag => tag.name).sort());
- const partOfSpeech = JSON.stringify(definition.definitionTags.filter(tag => tag.category === 'partOfSpeech').map(tag => tag.name).sort());
+ const dictionary = JSON.stringify(definition.definitionTags.filter((tag) => tag.category === 'dictionary').map((tag) => tag.name).sort());
+ const partOfSpeech = JSON.stringify(definition.definitionTags.filter((tag) => tag.category === 'partOfSpeech').map((tag) => tag.name).sort());
const filterOutCategories = [];
@@ -117,7 +117,7 @@ function dictTermsCompressTags(definitions) {
lastPartOfSpeech = partOfSpeech;
}
- definition.definitionTags = definition.definitionTags.filter(tag => !filterOutCategories.includes(tag.category));
+ definition.definitionTags = definition.definitionTags.filter((tag) => !filterOutCategories.includes(tag.category));
}
}
@@ -131,7 +131,7 @@ function dictTermsGroup(definitions, dictionaries) {
}
const keyString = key.toString();
- if (groups.hasOwnProperty(keyString)) {
+ if (hasOwn(groups, keyString)) {
groups[keyString].push(definition);
} else {
groups[keyString] = [definition];
@@ -231,7 +231,7 @@ function dictTermsMergeByGloss(result, definitions, appendTo, mergedIndices) {
result.reading.add(definition.reading);
for (const tag of definition.definitionTags) {
- if (!definitionsByGloss[gloss].definitionTags.find(existingTag => existingTag.name === tag.name)) {
+ if (!definitionsByGloss[gloss].definitionTags.find((existingTag) => existingTag.name === tag.name)) {
definitionsByGloss[gloss].definitionTags.push(tag);
}
}
@@ -246,7 +246,7 @@ function dictTermsMergeByGloss(result, definitions, appendTo, mergedIndices) {
}
for (const tag of definition.termTags) {
- if (!result.expressions.get(definition.expression).get(definition.reading).find(existingTag => existingTag.name === tag.name)) {
+ if (!result.expressions.get(definition.expression).get(definition.reading).find((existingTag) => existingTag.name === tag.name)) {
result.expressions.get(definition.expression).get(definition.reading).push(tag);
}
}
@@ -322,7 +322,7 @@ async function dictFieldFormat(field, definition, mode, options, exceptions) {
compactGlossaries: options.general.compactGlossaries
};
const markers = dictFieldFormat.markers;
- const pattern = /\{([\w\-]+)\}/g;
+ const pattern = /\{([\w-]+)\}/g;
return await stringReplaceAsync(field, pattern, async (g0, marker) => {
if (!markers.has(marker)) {
return g0;
diff --git a/ext/bg/js/handlebars.js b/ext/bg/js/handlebars.js
index d6307e1d..8f43cf9a 100644
--- a/ext/bg/js/handlebars.js
+++ b/ext/bg/js/handlebars.js
@@ -89,7 +89,7 @@ function handlebarsRegexReplace(...args) {
let value = args[args.length - 1].fn(this);
if (args.length >= 3) {
try {
- let flags = args.length > 3 ? args[2] : 'g';
+ const flags = args.length > 3 ? args[2] : 'g';
const regex = new RegExp(args[0], flags);
value = value.replace(regex, args[1]);
} catch (e) {
diff --git a/ext/bg/js/mecab.js b/ext/bg/js/mecab.js
index 246f8bba..62111f73 100644
--- a/ext/bg/js/mecab.js
+++ b/ext/bg/js/mecab.js
@@ -60,7 +60,7 @@ class Mecab {
}
onNativeMessage({sequence, data}) {
- if (this.listeners.hasOwnProperty(sequence)) {
+ if (hasOwn(this.listeners, sequence)) {
const {callback, timer} = this.listeners[sequence];
clearTimeout(timer);
callback(data);
@@ -81,7 +81,7 @@ class Mecab {
delete this.listeners[sequence];
reject(new Error(`Mecab invoke timed out in ${Mecab.timeout} ms`));
}, Mecab.timeout)
- }
+ };
this.port.postMessage({action, params, sequence});
});
diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js
index b9bf85f3..e53a8a13 100644
--- a/ext/bg/js/options.js
+++ b/ext/bg/js/options.js
@@ -336,7 +336,7 @@ function profileOptionsSetDefaults(options) {
const combine = (target, source) => {
for (const key in source) {
- if (!target.hasOwnProperty(key)) {
+ if (!hasOwn(target, key)) {
target[key] = source[key];
}
}
@@ -389,7 +389,7 @@ function optionsUpdateVersion(options, defaultProfileOptions) {
// Remove invalid
const profiles = options.profiles;
for (let i = profiles.length - 1; i >= 0; --i) {
- if (!utilIsObject(profiles[i])) {
+ if (!isObject(profiles[i])) {
profiles.splice(i, 1);
}
}
@@ -429,7 +429,7 @@ function optionsUpdateVersion(options, defaultProfileOptions) {
function optionsLoad() {
return new Promise((resolve, reject) => {
- chrome.storage.local.get(['options'], store => {
+ chrome.storage.local.get(['options'], (store) => {
const error = chrome.runtime.lastError;
if (error) {
reject(new Error(error));
@@ -437,17 +437,17 @@ function optionsLoad() {
resolve(store.options);
}
});
- }).then(optionsStr => {
+ }).then((optionsStr) => {
if (typeof optionsStr === 'string') {
const options = JSON.parse(optionsStr);
- if (utilIsObject(options)) {
+ if (isObject(options)) {
return options;
}
}
return {};
}).catch(() => {
return {};
- }).then(options => {
+ }).then((options) => {
return (
Array.isArray(options.profiles) ?
optionsUpdateVersion(options, {}) :
diff --git a/ext/bg/js/page-exit-prevention.js b/ext/bg/js/page-exit-prevention.js
new file mode 100644
index 00000000..aee4e3c2
--- /dev/null
+++ b/ext/bg/js/page-exit-prevention.js
@@ -0,0 +1,60 @@
+/*
+ * 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/>.
+ */
+
+
+class PageExitPrevention {
+ constructor() {
+ }
+
+ start() {
+ PageExitPrevention._addInstance(this);
+ }
+
+ end() {
+ PageExitPrevention._removeInstance(this);
+ }
+
+ static _addInstance(instance) {
+ const size = PageExitPrevention._instances.size;
+ PageExitPrevention._instances.set(instance, true);
+ if (size === 0) {
+ window.addEventListener('beforeunload', PageExitPrevention._onBeforeUnload);
+ }
+ }
+
+ static _removeInstance(instance) {
+ if (
+ PageExitPrevention._instances.delete(instance) &&
+ PageExitPrevention._instances.size === 0
+ ) {
+ window.removeEventListener('beforeunload', PageExitPrevention._onBeforeUnload);
+ }
+ }
+
+ static _onBeforeUnload(e) {
+ if (PageExitPrevention._instances.size === 0) {
+ return;
+ }
+
+ e.preventDefault();
+ e.returnValue = '';
+ return '';
+ }
+}
+
+PageExitPrevention._instances = new Map();
diff --git a/ext/bg/js/profile-conditions.js b/ext/bg/js/profile-conditions.js
index 8272e5dd..ebc6680a 100644
--- a/ext/bg/js/profile-conditions.js
+++ b/ext/bg/js/profile-conditions.js
@@ -86,7 +86,7 @@ const profileConditionsDescriptor = {
placeholder: 'Comma separated list of domains',
defaultValue: 'example.com',
transformCache: {},
- transform: (optionValue) => optionValue.split(/[,;\s]+/).map(v => v.trim().toLowerCase()).filter(v => v.length > 0),
+ transform: (optionValue) => optionValue.split(/[,;\s]+/).map((v) => v.trim().toLowerCase()).filter((v) => v.length > 0),
transformReverse: (transformedOptionValue) => transformedOptionValue.join(', '),
validateTransformed: (transformedOptionValue) => (transformedOptionValue.length > 0),
test: ({url}, transformedOptionValue) => _profileConditionTestDomainList(url, transformedOptionValue)
diff --git a/ext/bg/js/request.js b/ext/bg/js/request.js
index 3afc1506..7d73d49b 100644
--- a/ext/bg/js/request.js
+++ b/ext/bg/js/request.js
@@ -29,7 +29,7 @@ function requestJson(url, action, params) {
} else {
xhr.send();
}
- }).then(responseText => {
+ }).then((responseText) => {
try {
return JSON.parse(responseText);
}
diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js
index 42e53989..8dc2e30a 100644
--- a/ext/bg/js/search-query-parser.js
+++ b/ext/bg/js/search-query-parser.js
@@ -38,16 +38,16 @@ class QueryParser {
}
onMouseDown(e) {
- if (Frontend.isMouseButton('primary', e)) {
+ if (DOM.isMouseButtonPressed(e, 'primary')) {
this.clickScanPrevent = false;
}
}
onMouseUp(e) {
if (
- this.search.options.scanning.clickGlossary &&
+ this.search.options.scanning.enablePopupSearch &&
!this.clickScanPrevent &&
- Frontend.isMouseButton('primary', e)
+ DOM.isMouseButtonPressed(e, 'primary')
) {
const selectText = this.search.options.scanning.selectText;
this.onTermLookup(e, {disableScroll: true, selectText});
@@ -55,7 +55,7 @@ class QueryParser {
}
onMouseMove(e) {
- if (this.pendingLookup || Frontend.isMouseButton('primary', e)) {
+ if (this.pendingLookup || DOM.isMouseButtonDown(e, 'primary')) {
return;
}
@@ -63,7 +63,7 @@ class QueryParser {
const scanningModifier = scanningOptions.modifier;
if (!(
Frontend.isScanningModifierPressed(scanningModifier, e) ||
- (scanningOptions.middleMouse && Frontend.isMouseButton('auxiliary', e))
+ (scanningOptions.middleMouse && DOM.isMouseButtonDown(e, 'auxiliary'))
)) {
return;
}
@@ -107,7 +107,7 @@ class QueryParser {
}
getParseResult() {
- return this.parseResults.find(r => r.id === this.selectedParser);
+ return this.parseResults.find((r) => r.id === this.selectedParser);
}
async setText(text) {
@@ -134,7 +134,7 @@ class QueryParser {
});
}
if (this.search.options.parsing.enableMecabParser) {
- let mecabResults = await apiTextParseMecab(text, this.search.getOptionsContext());
+ const mecabResults = await apiTextParseMecab(text, this.search.getOptionsContext());
for (const mecabDictName in mecabResults) {
results.push({
name: `MeCab: ${mecabDictName}`,
@@ -216,11 +216,11 @@ class QueryParser {
static processParseResultForDisplay(result) {
return result.map((term) => {
- return term.filter(part => part.text.trim()).map((part) => {
+ return term.filter((part) => part.text.trim()).map((part) => {
return {
text: Array.from(part.text),
reading: part.reading,
- raw: !part.reading || !part.reading.trim(),
+ raw: !part.reading || !part.reading.trim()
};
});
});
diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js
index 0922d938..00b7ca4b 100644
--- a/ext/bg/js/search.js
+++ b/ext/bg/js/search.js
@@ -167,7 +167,7 @@ class DisplaySearch extends Display {
this.onSearchQueryUpdated(query, true);
}
- onPopState(e) {
+ onPopState() {
const query = DisplaySearch.getSearchQueryFromLocation(window.location.href) || '';
if (this.query !== null) {
if (this.isWanakanaEnabled()) {
@@ -207,7 +207,7 @@ class DisplaySearch extends Display {
async onSearchQueryUpdated(query, animate) {
try {
const details = {};
- const match = /[\*\uff0a]+$/.exec(query);
+ const match = /[*\uff0a]+$/.exec(query);
if (match !== null) {
details.wildcard = true;
query = query.substring(0, query.length - match[0].length);
@@ -220,6 +220,7 @@ class DisplaySearch extends Display {
const {definitions} = await apiTermsFind(query, details, this.optionsContext);
this.setContentTerms(definitions, {
focus: false,
+ disableHistory: true,
sentence: {text: query, offset: 0},
url: window.location.href
});
@@ -234,7 +235,7 @@ class DisplaySearch extends Display {
onRuntimeMessage({action, params}, sender, callback) {
const handlers = DisplaySearch.runtimeMessageHandlers;
- if (handlers.hasOwnProperty(action)) {
+ if (hasOwn(handlers, action)) {
const handler = handlers[action];
const result = handler(this, params);
callback(result);
@@ -245,7 +246,7 @@ class DisplaySearch extends Display {
initClipboardMonitor() {
// ignore copy from search page
- window.addEventListener('copy', (e) => {
+ window.addEventListener('copy', () => {
this.clipboardPrevText = document.getSelection().toString().trim();
});
}
@@ -324,7 +325,7 @@ class DisplaySearch extends Display {
this.intro.style.transition = '';
this.intro.style.height = '';
const size = this.intro.getBoundingClientRect();
- this.intro.style.height = `0px`;
+ this.intro.style.height = '0px';
this.intro.style.transition = `height ${duration}s ease-in-out 0s`;
window.getComputedStyle(this.intro).getPropertyValue('height'); // Commits height so next line can start animation
this.intro.style.height = `${size.height}px`;
@@ -356,7 +357,7 @@ class DisplaySearch extends Display {
}
static getSearchQueryFromLocation(url) {
- let match = /^[^\?#]*\?(?:[^&#]*&)?query=([^&#]*)/.exec(url);
+ const match = /^[^?#]*\?(?:[^&#]*&)?query=([^&#]*)/.exec(url);
return match !== null ? decodeURIComponent(match[1]) : null;
}
}
diff --git a/ext/bg/js/settings.js b/ext/bg/js/settings.js
deleted file mode 100644
index ab267c32..00000000
--- a/ext/bg/js/settings.js
+++ /dev/null
@@ -1,852 +0,0 @@
-/*
- * Copyright (C) 2016-2017 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/>.
- */
-
-async function getOptionsArray() {
- const optionsFull = await apiOptionsGetFull();
- return optionsFull.profiles.map(profile => profile.options);
-}
-
-async function formRead(options) {
- options.general.enable = $('#enable').prop('checked');
- options.general.showGuide = $('#show-usage-guide').prop('checked');
- options.general.compactTags = $('#compact-tags').prop('checked');
- options.general.compactGlossaries = $('#compact-glossaries').prop('checked');
- options.general.resultOutputMode = $('#result-output-mode').val();
- options.general.debugInfo = $('#show-debug-info').prop('checked');
- options.general.showAdvanced = $('#show-advanced-options').prop('checked');
- options.general.maxResults = parseInt($('#max-displayed-results').val(), 10);
- options.general.popupDisplayMode = $('#popup-display-mode').val();
- options.general.popupHorizontalTextPosition = $('#popup-horizontal-text-position').val();
- options.general.popupVerticalTextPosition = $('#popup-vertical-text-position').val();
- options.general.popupWidth = parseInt($('#popup-width').val(), 10);
- options.general.popupHeight = parseInt($('#popup-height').val(), 10);
- options.general.popupHorizontalOffset = parseInt($('#popup-horizontal-offset').val(), 0);
- options.general.popupVerticalOffset = parseInt($('#popup-vertical-offset').val(), 10);
- options.general.popupHorizontalOffset2 = parseInt($('#popup-horizontal-offset2').val(), 0);
- options.general.popupVerticalOffset2 = parseInt($('#popup-vertical-offset2').val(), 10);
- options.general.popupTheme = $('#popup-theme').val();
- options.general.popupOuterTheme = $('#popup-outer-theme').val();
- options.general.customPopupCss = $('#custom-popup-css').val();
- options.general.customPopupOuterCss = $('#custom-popup-outer-css').val();
-
- options.audio.enabled = $('#audio-playback-enabled').prop('checked');
- options.audio.autoPlay = $('#auto-play-audio').prop('checked');
- options.audio.volume = parseFloat($('#audio-playback-volume').val());
- options.audio.customSourceUrl = $('#audio-custom-source').val();
- options.audio.textToSpeechVoice = $('#text-to-speech-voice').val();
-
- options.scanning.middleMouse = $('#middle-mouse-button-scan').prop('checked');
- options.scanning.touchInputEnabled = $('#touch-input-enabled').prop('checked');
- options.scanning.selectText = $('#select-matched-text').prop('checked');
- options.scanning.alphanumeric = $('#search-alphanumeric').prop('checked');
- options.scanning.autoHideResults = $('#auto-hide-results').prop('checked');
- options.scanning.deepDomScan = $('#deep-dom-scan').prop('checked');
- options.scanning.enablePopupSearch = $('#enable-search-within-first-popup').prop('checked');
- options.scanning.enableOnPopupExpressions = $('#enable-scanning-of-popup-expressions').prop('checked');
- options.scanning.enableOnSearchPage = $('#enable-scanning-on-search-page').prop('checked');
- options.scanning.delay = parseInt($('#scan-delay').val(), 10);
- options.scanning.length = parseInt($('#scan-length').val(), 10);
- options.scanning.modifier = $('#scan-modifier-key').val();
- options.scanning.popupNestingMaxDepth = parseInt($('#popup-nesting-max-depth').val(), 10);
-
- options.parsing.enableScanningParser = $('#parsing-scan-enable').prop('checked');
- options.parsing.enableMecabParser = $('#parsing-mecab-enable').prop('checked');
- options.parsing.readingMode = $('#parsing-reading-mode').val();
-
- const optionsAnkiEnableOld = options.anki.enable;
- options.anki.enable = $('#anki-enable').prop('checked');
- options.anki.tags = utilBackgroundIsolate($('#card-tags').val().split(/[,; ]+/));
- options.anki.sentenceExt = parseInt($('#sentence-detection-extent').val(), 10);
- options.anki.server = $('#interface-server').val();
- options.anki.screenshot.format = $('#screenshot-format').val();
- options.anki.screenshot.quality = parseInt($('#screenshot-quality').val(), 10);
- options.anki.fieldTemplates = $('#field-templates').val();
-
- if (optionsAnkiEnableOld && !ankiErrorShown()) {
- options.anki.terms.deck = $('#anki-terms-deck').val();
- options.anki.terms.model = $('#anki-terms-model').val();
- options.anki.terms.fields = utilBackgroundIsolate(ankiFieldsToDict($('#terms .anki-field-value')));
- options.anki.kanji.deck = $('#anki-kanji-deck').val();
- options.anki.kanji.model = $('#anki-kanji-model').val();
- options.anki.kanji.fields = utilBackgroundIsolate(ankiFieldsToDict($('#kanji .anki-field-value')));
- }
-}
-
-async function formWrite(options) {
- $('#enable').prop('checked', options.general.enable);
- $('#show-usage-guide').prop('checked', options.general.showGuide);
- $('#compact-tags').prop('checked', options.general.compactTags);
- $('#compact-glossaries').prop('checked', options.general.compactGlossaries);
- $('#result-output-mode').val(options.general.resultOutputMode);
- $('#show-debug-info').prop('checked', options.general.debugInfo);
- $('#show-advanced-options').prop('checked', options.general.showAdvanced);
- $('#max-displayed-results').val(options.general.maxResults);
- $('#popup-display-mode').val(options.general.popupDisplayMode);
- $('#popup-horizontal-text-position').val(options.general.popupHorizontalTextPosition);
- $('#popup-vertical-text-position').val(options.general.popupVerticalTextPosition);
- $('#popup-width').val(options.general.popupWidth);
- $('#popup-height').val(options.general.popupHeight);
- $('#popup-horizontal-offset').val(options.general.popupHorizontalOffset);
- $('#popup-vertical-offset').val(options.general.popupVerticalOffset);
- $('#popup-horizontal-offset2').val(options.general.popupHorizontalOffset2);
- $('#popup-vertical-offset2').val(options.general.popupVerticalOffset2);
- $('#popup-theme').val(options.general.popupTheme);
- $('#popup-outer-theme').val(options.general.popupOuterTheme);
- $('#custom-popup-css').val(options.general.customPopupCss);
- $('#custom-popup-outer-css').val(options.general.customPopupOuterCss);
-
- $('#audio-playback-enabled').prop('checked', options.audio.enabled);
- $('#auto-play-audio').prop('checked', options.audio.autoPlay);
- $('#audio-playback-volume').val(options.audio.volume);
- $('#audio-custom-source').val(options.audio.customSourceUrl);
- $('#text-to-speech-voice').val(options.audio.textToSpeechVoice).attr('data-value', options.audio.textToSpeechVoice);
-
- $('#middle-mouse-button-scan').prop('checked', options.scanning.middleMouse);
- $('#touch-input-enabled').prop('checked', options.scanning.touchInputEnabled);
- $('#select-matched-text').prop('checked', options.scanning.selectText);
- $('#search-alphanumeric').prop('checked', options.scanning.alphanumeric);
- $('#auto-hide-results').prop('checked', options.scanning.autoHideResults);
- $('#deep-dom-scan').prop('checked', options.scanning.deepDomScan);
- $('#enable-search-within-first-popup').prop('checked', options.scanning.enablePopupSearch);
- $('#enable-scanning-of-popup-expressions').prop('checked', options.scanning.enableOnPopupExpressions);
- $('#enable-scanning-on-search-page').prop('checked', options.scanning.enableOnSearchPage);
- $('#scan-delay').val(options.scanning.delay);
- $('#scan-length').val(options.scanning.length);
- $('#scan-modifier-key').val(options.scanning.modifier);
- $('#popup-nesting-max-depth').val(options.scanning.popupNestingMaxDepth);
-
- $('#parsing-scan-enable').prop('checked', options.parsing.enableScanningParser);
- $('#parsing-mecab-enable').prop('checked', options.parsing.enableMecabParser);
- $('#parsing-reading-mode').val(options.parsing.readingMode);
-
- $('#anki-enable').prop('checked', options.anki.enable);
- $('#card-tags').val(options.anki.tags.join(' '));
- $('#sentence-detection-extent').val(options.anki.sentenceExt);
- $('#interface-server').val(options.anki.server);
- $('#screenshot-format').val(options.anki.screenshot.format);
- $('#screenshot-quality').val(options.anki.screenshot.quality);
- $('#field-templates').val(options.anki.fieldTemplates);
-
- onAnkiTemplatesValidateCompile();
- await onDictionaryOptionsChanged(options);
-
- try {
- await ankiDeckAndModelPopulate(options);
- } catch (e) {
- ankiErrorShow(e);
- }
-
- formUpdateVisibility(options);
-}
-
-function formSetupEventListeners() {
- $('input, select, textarea').not('.anki-model').not('.ignore-form-changes *').change(utilAsync(onFormOptionsChanged));
- $('.anki-model').change(utilAsync(onAnkiModelChanged));
-}
-
-function formUpdateVisibility(options) {
- document.documentElement.dataset.optionsAnkiEnable = `${!!options.anki.enable}`;
- document.documentElement.dataset.optionsGeneralDebugInfo = `${!!options.general.debugInfo}`;
- document.documentElement.dataset.optionsGeneralShowAdvanced = `${!!options.general.showAdvanced}`;
- document.documentElement.dataset.optionsGeneralResultOutputMode = `${options.general.resultOutputMode}`;
-
- if (options.general.debugInfo) {
- const temp = utilIsolate(options);
- temp.anki.fieldTemplates = '...';
- const text = JSON.stringify(temp, null, 4);
- $('#debug').text(text);
- }
-}
-
-async function onFormOptionsChanged(e) {
- if (!e.originalEvent && !e.isTrigger) {
- return;
- }
-
- const optionsContext = getOptionsContext();
- const options = await apiOptionsGet(optionsContext);
- const optionsAnkiEnableOld = options.anki.enable;
- const optionsAnkiServerOld = options.anki.server;
-
- await formRead(options);
- await settingsSaveOptions();
- formUpdateVisibility(options);
-
- try {
- const ankiUpdated =
- options.anki.enable !== optionsAnkiEnableOld ||
- options.anki.server !== optionsAnkiServerOld;
-
- if (ankiUpdated) {
- ankiSpinnerShow(true);
- await ankiDeckAndModelPopulate(options);
- ankiErrorShow();
- }
- } catch (error) {
- ankiErrorShow(error);
- } finally {
- ankiSpinnerShow(false);
- }
-}
-
-async function onReady() {
- showExtensionInformation();
-
- formSetupEventListeners();
- appearanceInitialize();
- await audioSettingsInitialize();
- await profileOptionsSetup();
- await dictSettingsInitialize();
- ankiTemplatesInitialize();
-
- storageInfoInitialize();
-
- chrome.runtime.onMessage.addListener(onMessage);
-}
-
-$(document).ready(utilAsync(onReady));
-
-
-/*
- * Page exit prevention
- */
-
-class PageExitPrevention {
- constructor() {
- }
-
- start() {
- PageExitPrevention._addInstance(this);
- }
-
- end() {
- PageExitPrevention._removeInstance(this);
- }
-
- static _addInstance(instance) {
- const size = PageExitPrevention._instances.size;
- PageExitPrevention._instances.set(instance, true);
- if (size === 0) {
- window.addEventListener('beforeunload', PageExitPrevention._onBeforeUnload);
- }
- }
-
- static _removeInstance(instance) {
- if (
- PageExitPrevention._instances.delete(instance) &&
- PageExitPrevention._instances.size === 0
- ) {
- window.removeEventListener('beforeunload', PageExitPrevention._onBeforeUnload);
- }
- }
-
- static _onBeforeUnload(e) {
- if (PageExitPrevention._instances.size === 0) {
- return;
- }
-
- e.preventDefault();
- e.returnValue = '';
- return '';
- }
-}
-PageExitPrevention._instances = new Map();
-
-
-/*
- * Appearance
- */
-
-function appearanceInitialize() {
- let previewVisible = false;
- $('#settings-popup-preview-button').on('click', () => {
- if (previewVisible) { return; }
- showAppearancePreview();
- previewVisible = true;
- });
-}
-
-function showAppearancePreview() {
- const container = $('#settings-popup-preview-container');
- const buttonContainer = $('#settings-popup-preview-button-container');
- const settings = $('#settings-popup-preview-settings');
- const text = $('#settings-popup-preview-text');
- const customCss = $('#custom-popup-css');
- const customOuterCss = $('#custom-popup-outer-css');
-
- const frame = document.createElement('iframe');
- frame.src = '/bg/settings-popup-preview.html';
- frame.id = 'settings-popup-preview-frame';
-
- window.wanakana.bind(text[0]);
-
- text.on('input', () => {
- const action = 'setText';
- const params = {text: text.val()};
- frame.contentWindow.postMessage({action, params}, '*');
- });
- customCss.on('input', () => {
- const action = 'setCustomCss';
- const params = {css: customCss.val()};
- frame.contentWindow.postMessage({action, params}, '*');
- });
- customOuterCss.on('input', () => {
- const action = 'setCustomOuterCss';
- const params = {css: customOuterCss.val()};
- frame.contentWindow.postMessage({action, params}, '*');
- });
-
- container.append(frame);
- buttonContainer.remove();
- settings.css('display', '');
-}
-
-
-/*
- * Audio
- */
-
-let audioSourceUI = null;
-
-async function audioSettingsInitialize() {
- const optionsContext = getOptionsContext();
- const options = await apiOptionsGet(optionsContext);
- audioSourceUI = new AudioSourceUI.Container(options.audio.sources, $('.audio-source-list'), $('.audio-source-add'));
- audioSourceUI.save = () => settingsSaveOptions();
-
- textToSpeechInitialize();
-}
-
-function textToSpeechInitialize() {
- if (typeof speechSynthesis === 'undefined') { return; }
-
- speechSynthesis.addEventListener('voiceschanged', () => updateTextToSpeechVoices(), false);
- updateTextToSpeechVoices();
-
- $('#text-to-speech-voice-test').on('click', () => textToSpeechTest());
-}
-
-function updateTextToSpeechVoices() {
- const voices = Array.prototype.map.call(speechSynthesis.getVoices(), (voice, index) => ({voice, index}));
- voices.sort(textToSpeechVoiceCompare);
- if (voices.length > 0) {
- $('#text-to-speech-voice-container').css('display', '');
- }
-
- const select = $('#text-to-speech-voice');
- select.empty();
- select.append($('<option>').val('').text('None'));
- for (const {voice} of voices) {
- select.append($('<option>').val(voice.voiceURI).text(`${voice.name} (${voice.lang})`));
- }
-
- select.val(select.attr('data-value'));
-}
-
-function languageTagIsJapanese(languageTag) {
- return (
- languageTag.startsWith('ja-') ||
- languageTag.startsWith('jpn-')
- );
-}
-
-function textToSpeechVoiceCompare(a, b) {
- const aIsJapanese = languageTagIsJapanese(a.voice.lang);
- const bIsJapanese = languageTagIsJapanese(b.voice.lang);
- if (aIsJapanese) {
- if (!bIsJapanese) { return -1; }
- } else {
- if (bIsJapanese) { return 1; }
- }
-
- const aIsDefault = a.voice.default;
- const bIsDefault = b.voice.default;
- if (aIsDefault) {
- if (!bIsDefault) { return -1; }
- } else {
- if (bIsDefault) { return 1; }
- }
-
- if (a.index < b.index) { return -1; }
- if (a.index > b.index) { return 1; }
- return 0;
-}
-
-function textToSpeechTest() {
- try {
- const text = $('#text-to-speech-voice-test').attr('data-speech-text') || '';
- const voiceURI = $('#text-to-speech-voice').val();
- const voice = audioGetTextToSpeechVoice(voiceURI);
- if (voice === null) { return; }
-
- const utterance = new SpeechSynthesisUtterance(text);
- utterance.lang = 'ja-JP';
- utterance.voice = voice;
- utterance.volume = 1.0;
-
- speechSynthesis.speak(utterance);
- } catch (e) {
- // NOP
- }
-}
-
-
-/*
- * Remote options updates
- */
-
-function settingsGetSource() {
- return new Promise((resolve) => {
- chrome.tabs.getCurrent((tab) => resolve(`settings${tab ? tab.id : ''}`));
- });
-}
-
-async function settingsSaveOptions() {
- const source = await settingsGetSource();
- await apiOptionsSave(source);
-}
-
-async function onOptionsUpdate({source}) {
- const thisSource = await settingsGetSource();
- if (source === thisSource) { return; }
-
- const optionsContext = getOptionsContext();
- const options = await apiOptionsGet(optionsContext);
- await formWrite(options);
-}
-
-function onMessage({action, params}, sender, callback) {
- switch (action) {
- case 'optionsUpdate':
- onOptionsUpdate(params);
- break;
- case 'getUrl':
- callback({url: window.location.href});
- break;
- }
-}
-
-
-/*
- * Anki
- */
-
-function ankiSpinnerShow(show) {
- const spinner = $('#anki-spinner');
- if (show) {
- spinner.show();
- } else {
- spinner.hide();
- }
-}
-
-function ankiErrorShow(error) {
- const dialog = $('#anki-error');
- if (error) {
- dialog.show().text(error);
- }
- else {
- dialog.hide();
- }
-}
-
-function ankiErrorShown() {
- return $('#anki-error').is(':visible');
-}
-
-function ankiFieldsToDict(selection) {
- const result = {};
- selection.each((index, element) => {
- result[$(element).data('field')] = $(element).val();
- });
-
- return result;
-}
-
-async function ankiDeckAndModelPopulate(options) {
- const ankiFormat = $('#anki-format').hide();
-
- const deckNames = await utilAnkiGetDeckNames();
- const ankiDeck = $('.anki-deck');
- ankiDeck.find('option').remove();
- deckNames.sort().forEach(name => ankiDeck.append($('<option/>', {value: name, text: name})));
-
- const modelNames = await utilAnkiGetModelNames();
- const ankiModel = $('.anki-model');
- ankiModel.find('option').remove();
- modelNames.sort().forEach(name => ankiModel.append($('<option/>', {value: name, text: name})));
-
- $('#anki-terms-deck').val(options.anki.terms.deck);
- await ankiFieldsPopulate($('#anki-terms-model').val(options.anki.terms.model), options);
-
- $('#anki-kanji-deck').val(options.anki.kanji.deck);
- await ankiFieldsPopulate($('#anki-kanji-model').val(options.anki.kanji.model), options);
-
- ankiFormat.show();
-}
-
-function ankiCreateFieldTemplate(name, value, markers) {
- const template = document.querySelector('#anki-field-template').content;
- const content = document.importNode(template, true).firstChild;
-
- content.querySelector('.anki-field-name').textContent = name;
-
- const field = content.querySelector('.anki-field-value');
- field.dataset.field = name;
- field.value = value;
-
- content.querySelector('.anki-field-marker-list').appendChild(ankiGetFieldMarkersHtml(markers));
-
- return content;
-}
-
-function ankiGetFieldMarkersHtml(markers, fragment) {
- const template = document.querySelector('#anki-field-marker-template').content;
- if (!fragment) {
- fragment = new DocumentFragment();
- }
- for (const marker of markers) {
- const markerNode = document.importNode(template, true).firstChild;
- markerNode.querySelector('.marker-link').textContent = marker;
- fragment.appendChild(markerNode);
- }
- return fragment;
-}
-
-function ankiGetFieldMarkers(type) {
- switch (type) {
- case 'terms':
- return [
- 'audio',
- 'cloze-body',
- 'cloze-prefix',
- 'cloze-suffix',
- 'dictionary',
- 'expression',
- 'furigana',
- 'furigana-plain',
- 'glossary',
- 'glossary-brief',
- 'reading',
- 'screenshot',
- 'sentence',
- 'tags',
- 'url'
- ];
- case 'kanji':
- return [
- 'character',
- 'dictionary',
- 'glossary',
- 'kunyomi',
- 'onyomi',
- 'screenshot',
- 'sentence',
- 'tags',
- 'url'
- ];
- default:
- return [];
- }
-}
-
-async function ankiFieldsPopulate(element, options) {
- const modelName = element.val();
- if (!modelName) {
- return;
- }
-
- const tab = element.closest('.tab-pane');
- const tabId = tab.attr('id');
- const container = tab.find('tbody').empty();
- const markers = ankiGetFieldMarkers(tabId);
-
- for (const name of await utilAnkiGetModelFieldNames(modelName)) {
- const value = options.anki[tabId].fields[name] || '';
- const html = ankiCreateFieldTemplate(name, value, markers);
- container.append($(html));
- }
-
- tab.find('.anki-field-value').change(utilAsync(onFormOptionsChanged));
- tab.find('.marker-link').click(onAnkiMarkerClicked);
-}
-
-function onAnkiMarkerClicked(e) {
- e.preventDefault();
- const link = e.target;
- $(link).closest('.input-group').find('.anki-field-value').val(`{${link.text}}`).trigger('change');
-}
-
-async function onAnkiModelChanged(e) {
- try {
- if (!e.originalEvent) {
- return;
- }
-
- const element = $(this);
- const tab = element.closest('.tab-pane');
- const tabId = tab.attr('id');
-
- const optionsContext = getOptionsContext();
- const options = await apiOptionsGet(optionsContext);
- await formRead(options);
- options.anki[tabId].fields = utilBackgroundIsolate({});
- await settingsSaveOptions();
-
- ankiSpinnerShow(true);
- await ankiFieldsPopulate(element, options);
- ankiErrorShow();
- } catch (error) {
- ankiErrorShow(error);
- } finally {
- ankiSpinnerShow(false);
- }
-}
-
-function onAnkiFieldTemplatesReset(e) {
- e.preventDefault();
- $('#field-template-reset-modal').modal('show');
-}
-
-async function onAnkiFieldTemplatesResetConfirm(e) {
- try {
- e.preventDefault();
-
- $('#field-template-reset-modal').modal('hide');
-
- const optionsContext = getOptionsContext();
- const options = await apiOptionsGet(optionsContext);
- const fieldTemplates = profileOptionsGetDefaultFieldTemplates();
- options.anki.fieldTemplates = fieldTemplates;
- $('#field-templates').val(fieldTemplates);
- onAnkiTemplatesValidateCompile();
- await settingsSaveOptions();
- } catch (error) {
- ankiErrorShow(error);
- }
-}
-
-function ankiTemplatesInitialize() {
- const markers = new Set(ankiGetFieldMarkers('terms').concat(ankiGetFieldMarkers('kanji')));
- const fragment = ankiGetFieldMarkersHtml(markers);
-
- const list = document.querySelector('#field-templates-list');
- list.appendChild(fragment);
- for (const node of list.querySelectorAll('.marker-link')) {
- node.addEventListener('click', onAnkiTemplateMarkerClicked, false);
- }
-
- $('#field-templates').on('change', onAnkiTemplatesValidateCompile);
- $('#field-template-render').on('click', onAnkiTemplateRender);
- $('#field-templates-reset').on('click', onAnkiFieldTemplatesReset);
- $('#field-templates-reset-confirm').on('click', onAnkiFieldTemplatesResetConfirm);
-}
-
-const ankiTemplatesValidateGetDefinition = (() => {
- let cachedValue = null;
- let cachedText = null;
-
- return async (text, optionsContext) => {
- if (cachedText !== text) {
- const {definitions} = await apiTermsFind(text, {}, optionsContext);
- if (definitions.length === 0) { return null; }
-
- cachedValue = definitions[0];
- cachedText = text;
- }
- return cachedValue;
- };
-})();
-
-async function ankiTemplatesValidate(infoNode, field, mode, showSuccessResult, invalidateInput) {
- const text = document.querySelector('#field-templates-preview-text').value || '';
- const exceptions = [];
- let result = `No definition found for ${text}`;
- try {
- const optionsContext = getOptionsContext();
- const definition = await ankiTemplatesValidateGetDefinition(text, optionsContext);
- if (definition !== null) {
- const options = await apiOptionsGet(optionsContext);
- result = await dictFieldFormat(field, definition, mode, options, exceptions);
- }
- } catch (e) {
- exceptions.push(e);
- }
-
- const hasException = exceptions.length > 0;
- infoNode.hidden = !(showSuccessResult || hasException);
- infoNode.textContent = hasException ? exceptions.map(e => `${e}`).join('\n') : (showSuccessResult ? result : '');
- infoNode.classList.toggle('text-danger', hasException);
- if (invalidateInput) {
- const input = document.querySelector('#field-templates');
- input.classList.toggle('is-invalid', hasException);
- }
-}
-
-function onAnkiTemplatesValidateCompile() {
- const infoNode = document.querySelector('#field-template-compile-result');
- ankiTemplatesValidate(infoNode, '{expression}', 'term-kanji', false, true);
-}
-
-function onAnkiTemplateMarkerClicked(e) {
- e.preventDefault();
- document.querySelector('#field-template-render-text').value = `{${e.target.textContent}}`;
-}
-
-function onAnkiTemplateRender(e) {
- e.preventDefault();
-
- const field = document.querySelector('#field-template-render-text').value;
- const infoNode = document.querySelector('#field-template-render-result');
- infoNode.hidden = true;
- ankiTemplatesValidate(infoNode, field, 'term-kanji', true, false);
-}
-
-
-/*
- * Storage
- */
-
-function storageBytesToLabeledString(size) {
- const base = 1000;
- const labels = [' bytes', 'KB', 'MB', 'GB'];
- let labelIndex = 0;
- while (size >= base) {
- size /= base;
- ++labelIndex;
- }
- const label = labelIndex === 0 ? `${size}` : size.toFixed(1);
- return `${label}${labels[labelIndex]}`;
-}
-
-async function storageEstimate() {
- try {
- return (storageEstimate.mostRecent = await navigator.storage.estimate());
- } catch (e) { }
- return null;
-}
-storageEstimate.mostRecent = null;
-
-async function isStoragePeristent() {
- try {
- return await navigator.storage.persisted();
- } catch (e) { }
- return false;
-}
-
-async function storageInfoInitialize() {
- storagePersistInitialize();
- const {browser, platform} = await apiGetEnvironmentInfo();
- document.documentElement.dataset.browser = browser;
- document.documentElement.dataset.operatingSystem = platform.os;
-
- await storageShowInfo();
-
- document.querySelector('#storage-refresh').addEventListener('click', () => storageShowInfo(), false);
-}
-
-async function storageUpdateStats() {
- storageUpdateStats.isUpdating = true;
-
- const estimate = await storageEstimate();
- const valid = (estimate !== null);
-
- if (valid) {
- // Firefox reports usage as 0 when persistent storage is enabled.
- const finite = (estimate.usage > 0 || !(await isStoragePeristent()));
- if (finite) {
- document.querySelector('#storage-usage').textContent = storageBytesToLabeledString(estimate.usage);
- document.querySelector('#storage-quota').textContent = storageBytesToLabeledString(estimate.quota);
- }
- document.querySelector('#storage-use-finite').classList.toggle('storage-hidden', !finite);
- document.querySelector('#storage-use-infinite').classList.toggle('storage-hidden', finite);
- }
-
- storageUpdateStats.isUpdating = false;
- return valid;
-}
-storageUpdateStats.isUpdating = false;
-
-async function storageShowInfo() {
- storageSpinnerShow(true);
-
- const valid = await storageUpdateStats();
- document.querySelector('#storage-use').classList.toggle('storage-hidden', !valid);
- document.querySelector('#storage-error').classList.toggle('storage-hidden', valid);
-
- storageSpinnerShow(false);
-}
-
-function storageSpinnerShow(show) {
- const spinner = $('#storage-spinner');
- if (show) {
- spinner.show();
- } else {
- spinner.hide();
- }
-}
-
-async function storagePersistInitialize() {
- if (!(navigator.storage && navigator.storage.persist)) {
- // Not supported
- return;
- }
-
- const info = document.querySelector('#storage-persist-info');
- const button = document.querySelector('#storage-persist-button');
- const checkbox = document.querySelector('#storage-persist-button-checkbox');
-
- info.classList.remove('storage-hidden');
- button.classList.remove('storage-hidden');
-
- let persisted = await isStoragePeristent();
- checkbox.checked = persisted;
-
- button.addEventListener('click', async () => {
- if (persisted) {
- return;
- }
- let result = false;
- try {
- result = await navigator.storage.persist();
- } catch (e) {
- // NOP
- }
-
- if (result) {
- persisted = true;
- checkbox.checked = true;
- storageShowInfo();
- } else {
- $('.storage-persist-fail-warning').removeClass('storage-hidden');
- }
- }, false);
-}
-
-
-/*
- * Information
- */
-
-function showExtensionInformation() {
- const node = document.getElementById('extension-info');
- if (node === null) { return; }
-
- const manifest = chrome.runtime.getManifest();
- node.textContent = `${manifest.name} v${manifest.version}`;
-}
diff --git a/ext/bg/js/settings/anki-templates.js b/ext/bg/js/settings/anki-templates.js
new file mode 100644
index 00000000..9cdfc134
--- /dev/null
+++ b/ext/bg/js/settings/anki-templates.js
@@ -0,0 +1,109 @@
+/*
+ * 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/>.
+ */
+
+
+function onAnkiFieldTemplatesReset(e) {
+ e.preventDefault();
+ $('#field-template-reset-modal').modal('show');
+}
+
+function onAnkiFieldTemplatesResetConfirm(e) {
+ e.preventDefault();
+
+ $('#field-template-reset-modal').modal('hide');
+
+ const element = document.querySelector('#field-templates');
+ element.value = profileOptionsGetDefaultFieldTemplates();
+ element.dispatchEvent(new Event('change'));
+}
+
+function ankiTemplatesInitialize() {
+ const markers = new Set(ankiGetFieldMarkers('terms').concat(ankiGetFieldMarkers('kanji')));
+ const fragment = ankiGetFieldMarkersHtml(markers);
+
+ const list = document.querySelector('#field-templates-list');
+ list.appendChild(fragment);
+ for (const node of list.querySelectorAll('.marker-link')) {
+ node.addEventListener('click', onAnkiTemplateMarkerClicked, false);
+ }
+
+ $('#field-templates').on('change', (e) => onAnkiTemplatesValidateCompile(e));
+ $('#field-template-render').on('click', (e) => onAnkiTemplateRender(e));
+ $('#field-templates-reset').on('click', (e) => onAnkiFieldTemplatesReset(e));
+ $('#field-templates-reset-confirm').on('click', (e) => onAnkiFieldTemplatesResetConfirm(e));
+}
+
+const ankiTemplatesValidateGetDefinition = (() => {
+ let cachedValue = null;
+ let cachedText = null;
+
+ return async (text, optionsContext) => {
+ if (cachedText !== text) {
+ const {definitions} = await apiTermsFind(text, {}, optionsContext);
+ if (definitions.length === 0) { return null; }
+
+ cachedValue = definitions[0];
+ cachedText = text;
+ }
+ return cachedValue;
+ };
+})();
+
+async function ankiTemplatesValidate(infoNode, field, mode, showSuccessResult, invalidateInput) {
+ const text = document.querySelector('#field-templates-preview-text').value || '';
+ const exceptions = [];
+ let result = `No definition found for ${text}`;
+ try {
+ const optionsContext = getOptionsContext();
+ const definition = await ankiTemplatesValidateGetDefinition(text, optionsContext);
+ if (definition !== null) {
+ const options = await apiOptionsGet(optionsContext);
+ result = await dictFieldFormat(field, definition, mode, options, exceptions);
+ }
+ } catch (e) {
+ exceptions.push(e);
+ }
+
+ const hasException = exceptions.length > 0;
+ infoNode.hidden = !(showSuccessResult || hasException);
+ infoNode.textContent = hasException ? exceptions.map((e) => `${e}`).join('\n') : (showSuccessResult ? result : '');
+ infoNode.classList.toggle('text-danger', hasException);
+ if (invalidateInput) {
+ const input = document.querySelector('#field-templates');
+ input.classList.toggle('is-invalid', hasException);
+ }
+}
+
+function onAnkiTemplatesValidateCompile() {
+ const infoNode = document.querySelector('#field-template-compile-result');
+ ankiTemplatesValidate(infoNode, '{expression}', 'term-kanji', false, true);
+}
+
+function onAnkiTemplateMarkerClicked(e) {
+ e.preventDefault();
+ document.querySelector('#field-template-render-text').value = `{${e.target.textContent}}`;
+}
+
+function onAnkiTemplateRender(e) {
+ e.preventDefault();
+
+ const field = document.querySelector('#field-template-render-text').value;
+ const infoNode = document.querySelector('#field-template-render-result');
+ infoNode.hidden = true;
+ ankiTemplatesValidate(infoNode, field, 'term-kanji', true, false);
+}
diff --git a/ext/bg/js/settings/anki.js b/ext/bg/js/settings/anki.js
new file mode 100644
index 00000000..e1aabbaf
--- /dev/null
+++ b/ext/bg/js/settings/anki.js
@@ -0,0 +1,247 @@
+/*
+ * 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/>.
+ */
+
+
+// Private
+
+let _ankiDataPopulated = false;
+
+
+function _ankiSpinnerShow(show) {
+ const spinner = $('#anki-spinner');
+ if (show) {
+ spinner.show();
+ } else {
+ spinner.hide();
+ }
+}
+
+function _ankiSetError(error) {
+ const node = document.querySelector('#anki-error');
+ if (!node) { return; }
+ if (error) {
+ node.hidden = false;
+ node.textContent = `${error}`;
+ }
+ else {
+ node.hidden = true;
+ node.textContent = '';
+ }
+}
+
+function _ankiSetDropdownOptions(dropdown, optionValues) {
+ const fragment = document.createDocumentFragment();
+ for (const optionValue of optionValues) {
+ const option = document.createElement('option');
+ option.value = optionValue;
+ option.textContent = optionValue;
+ fragment.appendChild(option);
+ }
+ dropdown.textContent = '';
+ dropdown.appendChild(fragment);
+}
+
+async function _ankiDeckAndModelPopulate(options) {
+ const termsDeck = {value: options.anki.terms.deck, selector: '#anki-terms-deck'};
+ const kanjiDeck = {value: options.anki.kanji.deck, selector: '#anki-kanji-deck'};
+ const termsModel = {value: options.anki.terms.model, selector: '#anki-terms-model'};
+ const kanjiModel = {value: options.anki.kanji.model, selector: '#anki-kanji-model'};
+ try {
+ _ankiSpinnerShow(true);
+ const [deckNames, modelNames] = await Promise.all([utilAnkiGetDeckNames(), utilAnkiGetModelNames()]);
+ deckNames.sort();
+ modelNames.sort();
+ termsDeck.values = deckNames;
+ kanjiDeck.values = deckNames;
+ termsModel.values = modelNames;
+ kanjiModel.values = modelNames;
+ _ankiSetError(null);
+ } catch (error) {
+ _ankiSetError(error);
+ } finally {
+ _ankiSpinnerShow(false);
+ }
+
+ for (const {value, values, selector} of [termsDeck, kanjiDeck, termsModel, kanjiModel]) {
+ const node = document.querySelector(selector);
+ _ankiSetDropdownOptions(node, Array.isArray(values) ? values : [value]);
+ node.value = value;
+ }
+}
+
+function _ankiCreateFieldTemplate(name, value, markers) {
+ const template = document.querySelector('#anki-field-template').content;
+ const content = document.importNode(template, true).firstChild;
+
+ content.querySelector('.anki-field-name').textContent = name;
+
+ const field = content.querySelector('.anki-field-value');
+ field.dataset.field = name;
+ field.value = value;
+
+ content.querySelector('.anki-field-marker-list').appendChild(ankiGetFieldMarkersHtml(markers));
+
+ return content;
+}
+
+async function _ankiFieldsPopulate(tabId, options) {
+ const tab = document.querySelector(`.tab-pane[data-anki-card-type=${tabId}]`);
+ const container = tab.querySelector('tbody');
+ const markers = ankiGetFieldMarkers(tabId);
+
+ const fragment = document.createDocumentFragment();
+ const fields = options.anki[tabId].fields;
+ for (const name of Object.keys(fields)) {
+ const value = fields[name];
+ const html = _ankiCreateFieldTemplate(name, value, markers);
+ fragment.appendChild(html);
+ }
+
+ container.textContent = '';
+ container.appendChild(fragment);
+
+ for (const node of container.querySelectorAll('.anki-field-value')) {
+ node.addEventListener('change', (e) => onFormOptionsChanged(e), false);
+ }
+ for (const node of container.querySelectorAll('.marker-link')) {
+ node.addEventListener('click', (e) => _onAnkiMarkerClicked(e), false);
+ }
+}
+
+function _onAnkiMarkerClicked(e) {
+ e.preventDefault();
+ const link = e.currentTarget;
+ const input = $(link).closest('.input-group').find('.anki-field-value')[0];
+ input.value = `{${link.textContent}}`;
+ input.dispatchEvent(new Event('change'));
+}
+
+async function _onAnkiModelChanged(e) {
+ const node = e.currentTarget;
+ let fieldNames;
+ try {
+ const modelName = node.value;
+ fieldNames = await utilAnkiGetModelFieldNames(modelName);
+ _ankiSetError(null);
+ } catch (error) {
+ _ankiSetError(error);
+ return;
+ } finally {
+ _ankiSpinnerShow(false);
+ }
+
+ const tabId = node.dataset.ankiCardType;
+ if (tabId !== 'terms' && tabId !== 'kanji') { return; }
+
+ const fields = {};
+ for (const name of fieldNames) {
+ fields[name] = '';
+ }
+
+ const optionsContext = getOptionsContext();
+ const options = await apiOptionsGet(optionsContext);
+ options.anki[tabId].fields = utilBackgroundIsolate(fields);
+ await settingsSaveOptions();
+
+ await _ankiFieldsPopulate(tabId, options);
+}
+
+
+// Public
+
+function ankiErrorShown() {
+ const node = document.querySelector('#anki-error');
+ return node && !node.hidden;
+}
+
+function ankiFieldsToDict(elements) {
+ const result = {};
+ for (const element of elements) {
+ result[element.dataset.field] = element.value;
+ }
+ return result;
+}
+
+
+function ankiGetFieldMarkersHtml(markers) {
+ const template = document.querySelector('#anki-field-marker-template').content;
+ const fragment = document.createDocumentFragment();
+ for (const marker of markers) {
+ const markerNode = document.importNode(template, true).firstChild;
+ markerNode.querySelector('.marker-link').textContent = marker;
+ fragment.appendChild(markerNode);
+ }
+ return fragment;
+}
+
+function ankiGetFieldMarkers(type) {
+ switch (type) {
+ case 'terms':
+ return [
+ 'audio',
+ 'cloze-body',
+ 'cloze-prefix',
+ 'cloze-suffix',
+ 'dictionary',
+ 'expression',
+ 'furigana',
+ 'furigana-plain',
+ 'glossary',
+ 'glossary-brief',
+ 'reading',
+ 'screenshot',
+ 'sentence',
+ 'tags',
+ 'url'
+ ];
+ case 'kanji':
+ return [
+ 'character',
+ 'dictionary',
+ 'glossary',
+ 'kunyomi',
+ 'onyomi',
+ 'screenshot',
+ 'sentence',
+ 'tags',
+ 'url'
+ ];
+ default:
+ return [];
+ }
+}
+
+
+function ankiInitialize() {
+ for (const node of document.querySelectorAll('#anki-terms-model,#anki-kanji-model')) {
+ node.addEventListener('change', (e) => _onAnkiModelChanged(e), false);
+ }
+}
+
+async function onAnkiOptionsChanged(options) {
+ if (!options.anki.enable) {
+ _ankiDataPopulated = false;
+ return;
+ }
+
+ if (_ankiDataPopulated) { return; }
+
+ await _ankiDeckAndModelPopulate(options);
+ _ankiDataPopulated = true;
+ await Promise.all([_ankiFieldsPopulate('terms', options), _ankiFieldsPopulate('kanji', options)]);
+}
diff --git a/ext/bg/js/settings/audio.js b/ext/bg/js/settings/audio.js
new file mode 100644
index 00000000..f63551ed
--- /dev/null
+++ b/ext/bg/js/settings/audio.js
@@ -0,0 +1,102 @@
+/*
+ * 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 audioSourceUI = null;
+
+async function audioSettingsInitialize() {
+ const optionsContext = getOptionsContext();
+ const options = await apiOptionsGet(optionsContext);
+ audioSourceUI = new AudioSourceUI.Container(options.audio.sources, $('.audio-source-list'), $('.audio-source-add'));
+ audioSourceUI.save = () => settingsSaveOptions();
+
+ textToSpeechInitialize();
+}
+
+function textToSpeechInitialize() {
+ if (typeof speechSynthesis === 'undefined') { return; }
+
+ speechSynthesis.addEventListener('voiceschanged', () => updateTextToSpeechVoices(), false);
+ updateTextToSpeechVoices();
+
+ $('#text-to-speech-voice-test').on('click', () => textToSpeechTest());
+}
+
+function updateTextToSpeechVoices() {
+ const voices = Array.prototype.map.call(speechSynthesis.getVoices(), (voice, index) => ({voice, index}));
+ voices.sort(textToSpeechVoiceCompare);
+ if (voices.length > 0) {
+ $('#text-to-speech-voice-container').css('display', '');
+ }
+
+ const select = $('#text-to-speech-voice');
+ select.empty();
+ select.append($('<option>').val('').text('None'));
+ for (const {voice} of voices) {
+ select.append($('<option>').val(voice.voiceURI).text(`${voice.name} (${voice.lang})`));
+ }
+
+ select.val(select.attr('data-value'));
+}
+
+function languageTagIsJapanese(languageTag) {
+ return (
+ languageTag.startsWith('ja-') ||
+ languageTag.startsWith('jpn-')
+ );
+}
+
+function textToSpeechVoiceCompare(a, b) {
+ const aIsJapanese = languageTagIsJapanese(a.voice.lang);
+ const bIsJapanese = languageTagIsJapanese(b.voice.lang);
+ if (aIsJapanese) {
+ if (!bIsJapanese) { return -1; }
+ } else {
+ if (bIsJapanese) { return 1; }
+ }
+
+ const aIsDefault = a.voice.default;
+ const bIsDefault = b.voice.default;
+ if (aIsDefault) {
+ if (!bIsDefault) { return -1; }
+ } else {
+ if (bIsDefault) { return 1; }
+ }
+
+ if (a.index < b.index) { return -1; }
+ if (a.index > b.index) { return 1; }
+ return 0;
+}
+
+function textToSpeechTest() {
+ try {
+ const text = $('#text-to-speech-voice-test').attr('data-speech-text') || '';
+ const voiceURI = $('#text-to-speech-voice').val();
+ const voice = audioGetTextToSpeechVoice(voiceURI);
+ if (voice === null) { return; }
+
+ const utterance = new SpeechSynthesisUtterance(text);
+ utterance.lang = 'ja-JP';
+ utterance.voice = voice;
+ utterance.volume = 1.0;
+
+ speechSynthesis.speak(utterance);
+ } catch (e) {
+ // NOP
+ }
+}
diff --git a/ext/bg/js/settings-dictionaries.js b/ext/bg/js/settings/dictionaries.js
index ebd380ac..065a8abc 100644
--- a/ext/bg/js/settings-dictionaries.js
+++ b/ext/bg/js/settings/dictionaries.js
@@ -62,8 +62,8 @@ class SettingsDictionaryListUI {
this.updateDictionaryOrder();
- const titles = this.dictionaryEntries.map(e => e.dictionaryInfo.title);
- const removeKeys = Object.keys(this.optionsDictionaries).filter(key => titles.indexOf(key) < 0);
+ 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 toIterable(removeKeys)) {
delete this.optionsDictionaries[key];
@@ -81,7 +81,7 @@ class SettingsDictionaryListUI {
let changed = false;
let optionsDictionary;
const optionsDictionaries = this.optionsDictionaries;
- if (optionsDictionaries.hasOwnProperty(title)) {
+ if (hasOwn(optionsDictionaries, title)) {
optionsDictionary = optionsDictionaries[title];
} else {
optionsDictionary = SettingsDictionaryListUI.createDictionaryOptions();
@@ -161,7 +161,7 @@ class SettingsDictionaryListUI {
delete n.dataset.dict;
$(n).modal('hide');
- const index = this.dictionaryEntries.findIndex(e => e.dictionaryInfo.title === title);
+ const index = this.dictionaryEntries.findIndex((e) => e.dictionaryInfo.title === title);
if (index >= 0) {
this.dictionaryEntries[index].deleteDictionary();
}
@@ -377,7 +377,7 @@ async function onDatabaseUpdated(options) {
updateMainDictionarySelect(options, dictionaries);
- const {counts, total} = await utilDatabaseGetDictionaryCounts(dictionaries.map(v => v.title), true);
+ const {counts, total} = await utilDatabaseGetDictionaryCounts(dictionaries.map((v) => v.title), true);
dictionaryUI.setCounts(counts, total);
} catch (e) {
dictionaryErrorsShow([e]);
@@ -466,7 +466,7 @@ function dictionaryErrorsShow(errors) {
for (let e of errors) {
console.error(e);
e = dictionaryErrorToString(e);
- uniqueErrors[e] = uniqueErrors.hasOwnProperty(e) ? uniqueErrors[e] + 1 : 1;
+ uniqueErrors[e] = hasOwn(uniqueErrors, e) ? uniqueErrors[e] + 1 : 1;
}
for (const e in uniqueErrors) {
@@ -564,7 +564,7 @@ async function onDictionaryImport(e) {
dictionaryErrorsShow(null);
dictionarySpinnerShow(true);
- const setProgress = percent => dictProgress.find('.progress-bar').css('width', `${percent}%`);
+ 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) {
diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js
new file mode 100644
index 00000000..7456e7a4
--- /dev/null
+++ b/ext/bg/js/settings/main.js
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2016-2017 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/>.
+ */
+
+async function getOptionsArray() {
+ const optionsFull = await apiOptionsGetFull();
+ return optionsFull.profiles.map((profile) => profile.options);
+}
+
+async function formRead(options) {
+ options.general.enable = $('#enable').prop('checked');
+ options.general.showGuide = $('#show-usage-guide').prop('checked');
+ options.general.compactTags = $('#compact-tags').prop('checked');
+ options.general.compactGlossaries = $('#compact-glossaries').prop('checked');
+ options.general.resultOutputMode = $('#result-output-mode').val();
+ options.general.debugInfo = $('#show-debug-info').prop('checked');
+ options.general.showAdvanced = $('#show-advanced-options').prop('checked');
+ options.general.maxResults = parseInt($('#max-displayed-results').val(), 10);
+ options.general.popupDisplayMode = $('#popup-display-mode').val();
+ options.general.popupHorizontalTextPosition = $('#popup-horizontal-text-position').val();
+ options.general.popupVerticalTextPosition = $('#popup-vertical-text-position').val();
+ options.general.popupWidth = parseInt($('#popup-width').val(), 10);
+ options.general.popupHeight = parseInt($('#popup-height').val(), 10);
+ options.general.popupHorizontalOffset = parseInt($('#popup-horizontal-offset').val(), 0);
+ options.general.popupVerticalOffset = parseInt($('#popup-vertical-offset').val(), 10);
+ options.general.popupHorizontalOffset2 = parseInt($('#popup-horizontal-offset2').val(), 0);
+ options.general.popupVerticalOffset2 = parseInt($('#popup-vertical-offset2').val(), 10);
+ options.general.popupTheme = $('#popup-theme').val();
+ options.general.popupOuterTheme = $('#popup-outer-theme').val();
+ options.general.customPopupCss = $('#custom-popup-css').val();
+ options.general.customPopupOuterCss = $('#custom-popup-outer-css').val();
+
+ options.audio.enabled = $('#audio-playback-enabled').prop('checked');
+ options.audio.autoPlay = $('#auto-play-audio').prop('checked');
+ options.audio.volume = parseFloat($('#audio-playback-volume').val());
+ options.audio.customSourceUrl = $('#audio-custom-source').val();
+ options.audio.textToSpeechVoice = $('#text-to-speech-voice').val();
+
+ options.scanning.middleMouse = $('#middle-mouse-button-scan').prop('checked');
+ options.scanning.touchInputEnabled = $('#touch-input-enabled').prop('checked');
+ options.scanning.selectText = $('#select-matched-text').prop('checked');
+ options.scanning.alphanumeric = $('#search-alphanumeric').prop('checked');
+ options.scanning.autoHideResults = $('#auto-hide-results').prop('checked');
+ options.scanning.deepDomScan = $('#deep-dom-scan').prop('checked');
+ options.scanning.enablePopupSearch = $('#enable-search-within-first-popup').prop('checked');
+ options.scanning.enableOnPopupExpressions = $('#enable-scanning-of-popup-expressions').prop('checked');
+ options.scanning.enableOnSearchPage = $('#enable-scanning-on-search-page').prop('checked');
+ options.scanning.delay = parseInt($('#scan-delay').val(), 10);
+ options.scanning.length = parseInt($('#scan-length').val(), 10);
+ options.scanning.modifier = $('#scan-modifier-key').val();
+ options.scanning.popupNestingMaxDepth = parseInt($('#popup-nesting-max-depth').val(), 10);
+
+ options.parsing.enableScanningParser = $('#parsing-scan-enable').prop('checked');
+ options.parsing.enableMecabParser = $('#parsing-mecab-enable').prop('checked');
+ options.parsing.readingMode = $('#parsing-reading-mode').val();
+
+ const optionsAnkiEnableOld = options.anki.enable;
+ options.anki.enable = $('#anki-enable').prop('checked');
+ options.anki.tags = utilBackgroundIsolate($('#card-tags').val().split(/[,; ]+/));
+ options.anki.sentenceExt = parseInt($('#sentence-detection-extent').val(), 10);
+ options.anki.server = $('#interface-server').val();
+ options.anki.screenshot.format = $('#screenshot-format').val();
+ options.anki.screenshot.quality = parseInt($('#screenshot-quality').val(), 10);
+ options.anki.fieldTemplates = $('#field-templates').val();
+
+ if (optionsAnkiEnableOld && !ankiErrorShown()) {
+ options.anki.terms.deck = $('#anki-terms-deck').val();
+ options.anki.terms.model = $('#anki-terms-model').val();
+ options.anki.terms.fields = utilBackgroundIsolate(ankiFieldsToDict(document.querySelectorAll('#terms .anki-field-value')));
+ options.anki.kanji.deck = $('#anki-kanji-deck').val();
+ options.anki.kanji.model = $('#anki-kanji-model').val();
+ options.anki.kanji.fields = utilBackgroundIsolate(ankiFieldsToDict(document.querySelectorAll('#kanji .anki-field-value')));
+ }
+}
+
+async function formWrite(options) {
+ $('#enable').prop('checked', options.general.enable);
+ $('#show-usage-guide').prop('checked', options.general.showGuide);
+ $('#compact-tags').prop('checked', options.general.compactTags);
+ $('#compact-glossaries').prop('checked', options.general.compactGlossaries);
+ $('#result-output-mode').val(options.general.resultOutputMode);
+ $('#show-debug-info').prop('checked', options.general.debugInfo);
+ $('#show-advanced-options').prop('checked', options.general.showAdvanced);
+ $('#max-displayed-results').val(options.general.maxResults);
+ $('#popup-display-mode').val(options.general.popupDisplayMode);
+ $('#popup-horizontal-text-position').val(options.general.popupHorizontalTextPosition);
+ $('#popup-vertical-text-position').val(options.general.popupVerticalTextPosition);
+ $('#popup-width').val(options.general.popupWidth);
+ $('#popup-height').val(options.general.popupHeight);
+ $('#popup-horizontal-offset').val(options.general.popupHorizontalOffset);
+ $('#popup-vertical-offset').val(options.general.popupVerticalOffset);
+ $('#popup-horizontal-offset2').val(options.general.popupHorizontalOffset2);
+ $('#popup-vertical-offset2').val(options.general.popupVerticalOffset2);
+ $('#popup-theme').val(options.general.popupTheme);
+ $('#popup-outer-theme').val(options.general.popupOuterTheme);
+ $('#custom-popup-css').val(options.general.customPopupCss);
+ $('#custom-popup-outer-css').val(options.general.customPopupOuterCss);
+
+ $('#audio-playback-enabled').prop('checked', options.audio.enabled);
+ $('#auto-play-audio').prop('checked', options.audio.autoPlay);
+ $('#audio-playback-volume').val(options.audio.volume);
+ $('#audio-custom-source').val(options.audio.customSourceUrl);
+ $('#text-to-speech-voice').val(options.audio.textToSpeechVoice).attr('data-value', options.audio.textToSpeechVoice);
+
+ $('#middle-mouse-button-scan').prop('checked', options.scanning.middleMouse);
+ $('#touch-input-enabled').prop('checked', options.scanning.touchInputEnabled);
+ $('#select-matched-text').prop('checked', options.scanning.selectText);
+ $('#search-alphanumeric').prop('checked', options.scanning.alphanumeric);
+ $('#auto-hide-results').prop('checked', options.scanning.autoHideResults);
+ $('#deep-dom-scan').prop('checked', options.scanning.deepDomScan);
+ $('#enable-search-within-first-popup').prop('checked', options.scanning.enablePopupSearch);
+ $('#enable-scanning-of-popup-expressions').prop('checked', options.scanning.enableOnPopupExpressions);
+ $('#enable-scanning-on-search-page').prop('checked', options.scanning.enableOnSearchPage);
+ $('#scan-delay').val(options.scanning.delay);
+ $('#scan-length').val(options.scanning.length);
+ $('#scan-modifier-key').val(options.scanning.modifier);
+ $('#popup-nesting-max-depth').val(options.scanning.popupNestingMaxDepth);
+
+ $('#parsing-scan-enable').prop('checked', options.parsing.enableScanningParser);
+ $('#parsing-mecab-enable').prop('checked', options.parsing.enableMecabParser);
+ $('#parsing-reading-mode').val(options.parsing.readingMode);
+
+ $('#anki-enable').prop('checked', options.anki.enable);
+ $('#card-tags').val(options.anki.tags.join(' '));
+ $('#sentence-detection-extent').val(options.anki.sentenceExt);
+ $('#interface-server').val(options.anki.server);
+ $('#screenshot-format').val(options.anki.screenshot.format);
+ $('#screenshot-quality').val(options.anki.screenshot.quality);
+ $('#field-templates').val(options.anki.fieldTemplates);
+
+ onAnkiTemplatesValidateCompile();
+ await onAnkiOptionsChanged(options);
+ await onDictionaryOptionsChanged(options);
+
+ formUpdateVisibility(options);
+}
+
+function formSetupEventListeners() {
+ $('input, select, textarea').not('.anki-model').not('.ignore-form-changes *').change((e) => onFormOptionsChanged(e));
+}
+
+function formUpdateVisibility(options) {
+ document.documentElement.dataset.optionsAnkiEnable = `${!!options.anki.enable}`;
+ document.documentElement.dataset.optionsGeneralDebugInfo = `${!!options.general.debugInfo}`;
+ document.documentElement.dataset.optionsGeneralShowAdvanced = `${!!options.general.showAdvanced}`;
+ document.documentElement.dataset.optionsGeneralResultOutputMode = `${options.general.resultOutputMode}`;
+
+ if (options.general.debugInfo) {
+ const temp = utilIsolate(options);
+ temp.anki.fieldTemplates = '...';
+ const text = JSON.stringify(temp, null, 4);
+ $('#debug').text(text);
+ }
+}
+
+async function onFormOptionsChanged() {
+ const optionsContext = getOptionsContext();
+ const options = await apiOptionsGet(optionsContext);
+
+ await formRead(options);
+ await settingsSaveOptions();
+ formUpdateVisibility(options);
+
+ await onAnkiOptionsChanged(options);
+}
+
+
+function settingsGetSource() {
+ return new Promise((resolve) => {
+ chrome.tabs.getCurrent((tab) => resolve(`settings${tab ? tab.id : ''}`));
+ });
+}
+
+async function settingsSaveOptions() {
+ const source = await settingsGetSource();
+ await apiOptionsSave(source);
+}
+
+async function onOptionsUpdate({source}) {
+ const thisSource = await settingsGetSource();
+ if (source === thisSource) { return; }
+
+ const optionsContext = getOptionsContext();
+ const options = await apiOptionsGet(optionsContext);
+ await formWrite(options);
+}
+
+function onMessage({action, params}, sender, callback) {
+ switch (action) {
+ case 'optionsUpdate':
+ onOptionsUpdate(params);
+ break;
+ case 'getUrl':
+ callback({url: window.location.href});
+ break;
+ }
+}
+
+
+function showExtensionInformation() {
+ const node = document.getElementById('extension-info');
+ if (node === null) { return; }
+
+ const manifest = chrome.runtime.getManifest();
+ node.textContent = `${manifest.name} v${manifest.version}`;
+}
+
+
+async function onReady() {
+ showExtensionInformation();
+
+ formSetupEventListeners();
+ appearanceInitialize();
+ await audioSettingsInitialize();
+ await profileOptionsSetup();
+ await dictSettingsInitialize();
+ ankiInitialize();
+ ankiTemplatesInitialize();
+
+ storageInfoInitialize();
+
+ chrome.runtime.onMessage.addListener(onMessage);
+}
+
+$(document).ready(() => onReady());
diff --git a/ext/bg/js/settings-popup-preview.js b/ext/bg/js/settings/popup-preview-frame.js
index 7d641c46..49409968 100644
--- a/ext/bg/js/settings-popup-preview.js
+++ b/ext/bg/js/settings/popup-preview-frame.js
@@ -106,7 +106,7 @@ class SettingsPopupPreview {
onMessage(e) {
const {action, params} = e.data;
const handlers = SettingsPopupPreview.messageHandlers;
- if (handlers.hasOwnProperty(action)) {
+ if (hasOwn(handlers, action)) {
const handler = handlers[action];
handler(this, params);
}
diff --git a/ext/bg/js/settings/popup-preview.js b/ext/bg/js/settings/popup-preview.js
new file mode 100644
index 00000000..d8579eb1
--- /dev/null
+++ b/ext/bg/js/settings/popup-preview.js
@@ -0,0 +1,62 @@
+/*
+ * 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/>.
+ */
+
+
+function appearanceInitialize() {
+ let previewVisible = false;
+ $('#settings-popup-preview-button').on('click', () => {
+ if (previewVisible) { return; }
+ showAppearancePreview();
+ previewVisible = true;
+ });
+}
+
+function showAppearancePreview() {
+ const container = $('#settings-popup-preview-container');
+ const buttonContainer = $('#settings-popup-preview-button-container');
+ const settings = $('#settings-popup-preview-settings');
+ const text = $('#settings-popup-preview-text');
+ const customCss = $('#custom-popup-css');
+ const customOuterCss = $('#custom-popup-outer-css');
+
+ const frame = document.createElement('iframe');
+ frame.src = '/bg/settings-popup-preview.html';
+ frame.id = 'settings-popup-preview-frame';
+
+ window.wanakana.bind(text[0]);
+
+ text.on('input', () => {
+ const action = 'setText';
+ const params = {text: text.val()};
+ frame.contentWindow.postMessage({action, params}, '*');
+ });
+ customCss.on('input', () => {
+ const action = 'setCustomCss';
+ const params = {css: customCss.val()};
+ frame.contentWindow.postMessage({action, params}, '*');
+ });
+ customOuterCss.on('input', () => {
+ const action = 'setCustomOuterCss';
+ const params = {css: customOuterCss.val()};
+ frame.contentWindow.postMessage({action, params}, '*');
+ });
+
+ container.append(frame);
+ buttonContainer.remove();
+ settings.css('display', '');
+}
diff --git a/ext/bg/js/settings-profiles.js b/ext/bg/js/settings/profiles.js
index 3ae9db14..8c218e97 100644
--- a/ext/bg/js/settings-profiles.js
+++ b/ext/bg/js/settings/profiles.js
@@ -35,16 +35,16 @@ async function profileOptionsSetup() {
}
function profileOptionsSetupEventListeners() {
- $('#profile-target').change(utilAsync(onTargetProfileChanged));
- $('#profile-name').change(onProfileNameChanged);
- $('#profile-add').click(utilAsync(onProfileAdd));
- $('#profile-remove').click(utilAsync(onProfileRemove));
- $('#profile-remove-confirm').click(utilAsync(onProfileRemoveConfirm));
- $('#profile-copy').click(utilAsync(onProfileCopy));
- $('#profile-copy-confirm').click(utilAsync(onProfileCopyConfirm));
+ $('#profile-target').change((e) => onTargetProfileChanged(e));
+ $('#profile-name').change((e) => onProfileNameChanged(e));
+ $('#profile-add').click((e) => onProfileAdd(e));
+ $('#profile-remove').click((e) => onProfileRemove(e));
+ $('#profile-remove-confirm').click((e) => onProfileRemoveConfirm(e));
+ $('#profile-copy').click((e) => onProfileCopy(e));
+ $('#profile-copy-confirm').click((e) => onProfileCopyConfirm(e));
$('#profile-move-up').click(() => onProfileMove(-1));
$('#profile-move-down').click(() => onProfileMove(1));
- $('.profile-form').find('input, select, textarea').not('.profile-form-manual').change(utilAsync(onProfileOptionsChanged));
+ $('.profile-form').find('input, select, textarea').not('.profile-form-manual').change((e) => onProfileOptionsChanged(e));
}
function tryGetIntegerValue(selector, min, max) {
@@ -147,7 +147,7 @@ function profileOptionsCreateCopyName(name, profiles, maxUniqueAttempts) {
let i = 0;
while (true) {
const newName = `${prefix}${space}${index}${suffix}`;
- if (i++ >= maxUniqueAttempts || profiles.findIndex(profile => profile.name === newName) < 0) {
+ if (i++ >= maxUniqueAttempts || profiles.findIndex((profile) => profile.name === newName) < 0) {
return newName;
}
if (typeof index !== 'number') {
diff --git a/ext/bg/js/settings/storage.js b/ext/bg/js/settings/storage.js
new file mode 100644
index 00000000..51ca6855
--- /dev/null
+++ b/ext/bg/js/settings/storage.js
@@ -0,0 +1,138 @@
+/*
+ * 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/>.
+ */
+
+
+function storageBytesToLabeledString(size) {
+ const base = 1000;
+ const labels = [' bytes', 'KB', 'MB', 'GB'];
+ let labelIndex = 0;
+ while (size >= base) {
+ size /= base;
+ ++labelIndex;
+ }
+ const label = labelIndex === 0 ? `${size}` : size.toFixed(1);
+ return `${label}${labels[labelIndex]}`;
+}
+
+async function storageEstimate() {
+ try {
+ return (storageEstimate.mostRecent = await navigator.storage.estimate());
+ } catch (e) {
+ // NOP
+ }
+ return null;
+}
+storageEstimate.mostRecent = null;
+
+async function isStoragePeristent() {
+ try {
+ return await navigator.storage.persisted();
+ } catch (e) {
+ // NOP
+ }
+ return false;
+}
+
+async function storageInfoInitialize() {
+ storagePersistInitialize();
+ const {browser, platform} = await apiGetEnvironmentInfo();
+ document.documentElement.dataset.browser = browser;
+ document.documentElement.dataset.operatingSystem = platform.os;
+
+ await storageShowInfo();
+
+ document.querySelector('#storage-refresh').addEventListener('click', () => storageShowInfo(), false);
+}
+
+async function storageUpdateStats() {
+ storageUpdateStats.isUpdating = true;
+
+ const estimate = await storageEstimate();
+ const valid = (estimate !== null);
+
+ if (valid) {
+ // Firefox reports usage as 0 when persistent storage is enabled.
+ const finite = (estimate.usage > 0 || !(await isStoragePeristent()));
+ if (finite) {
+ document.querySelector('#storage-usage').textContent = storageBytesToLabeledString(estimate.usage);
+ document.querySelector('#storage-quota').textContent = storageBytesToLabeledString(estimate.quota);
+ }
+ document.querySelector('#storage-use-finite').classList.toggle('storage-hidden', !finite);
+ document.querySelector('#storage-use-infinite').classList.toggle('storage-hidden', finite);
+ }
+
+ storageUpdateStats.isUpdating = false;
+ return valid;
+}
+storageUpdateStats.isUpdating = false;
+
+async function storageShowInfo() {
+ storageSpinnerShow(true);
+
+ const valid = await storageUpdateStats();
+ document.querySelector('#storage-use').classList.toggle('storage-hidden', !valid);
+ document.querySelector('#storage-error').classList.toggle('storage-hidden', valid);
+
+ storageSpinnerShow(false);
+}
+
+function storageSpinnerShow(show) {
+ const spinner = $('#storage-spinner');
+ if (show) {
+ spinner.show();
+ } else {
+ spinner.hide();
+ }
+}
+
+async function storagePersistInitialize() {
+ if (!(navigator.storage && navigator.storage.persist)) {
+ // Not supported
+ return;
+ }
+
+ const info = document.querySelector('#storage-persist-info');
+ const button = document.querySelector('#storage-persist-button');
+ const checkbox = document.querySelector('#storage-persist-button-checkbox');
+
+ info.classList.remove('storage-hidden');
+ button.classList.remove('storage-hidden');
+
+ let persisted = await isStoragePeristent();
+ checkbox.checked = persisted;
+
+ button.addEventListener('click', async () => {
+ if (persisted) {
+ return;
+ }
+ let result = false;
+ try {
+ result = await navigator.storage.persist();
+ } catch (e) {
+ // NOP
+ }
+
+ if (result) {
+ persisted = true;
+ checkbox.checked = true;
+ storageShowInfo();
+ } else {
+ $('.storage-persist-fail-warning').removeClass('storage-hidden');
+ }
+ }, false);
+}
diff --git a/ext/bg/js/templates.js b/ext/bg/js/templates.js
index 6e377957..9320477f 100644
--- a/ext/bg/js/templates.js
+++ b/ext/bg/js/templates.js
@@ -33,19 +33,18 @@ templates['kanji.html'] = template({"1":function(container,depth0,helpers,partia
return "<div class=\"entry\" data-type=\"kanji\">\n <div class=\"actions\">\n"
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.addable : depth0),{"name":"if","hash":{},"fn":container.program(11, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.source : depth0),{"name":"if","hash":{},"fn":container.program(13, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " <img src=\"/mixed/img/entry-current.svg\" class=\"current\" title=\"Current entry (Alt + Up/Down/Home/End/PgUp/PgDn)\" alt>\n </div>\n\n <div class=\"glyph expression-scan-toggle\">"
+ container.escapeExpression(((helper = (helper = helpers.character || (depth0 != null ? depth0.character : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"character","hash":{},"data":data}) : helper)))
+ "</div>\n\n"
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.frequencies : depth0),{"name":"if","hash":{},"fn":container.program(15, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.frequencies : depth0),{"name":"if","hash":{},"fn":container.program(13, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n"
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.tags : depth0),{"name":"if","hash":{},"fn":container.program(18, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.tags : depth0),{"name":"if","hash":{},"fn":container.program(16, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n <table class=\"table table-condensed glyph-data\">\n <tr>\n <th>Glossary</th>\n <th>Readings</th>\n <th>Statistics</th>\n </tr>\n <tr>\n <td class=\"glossary\">\n"
- + ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(21, data, 0),"inverse":container.program(24, data, 0),"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(19, data, 0),"inverse":container.program(22, data, 0),"data":data})) != null ? stack1 : "")
+ " </td>\n <td class=\"reading\">\n "
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.onyomi : depth0),{"name":"if","hash":{},"fn":container.program(26, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.onyomi : depth0),{"name":"if","hash":{},"fn":container.program(24, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n "
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.kunyomi : depth0),{"name":"if","hash":{},"fn":container.program(29, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.kunyomi : depth0),{"name":"if","hash":{},"fn":container.program(27, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n </td>\n <td>"
+ ((stack1 = container.invokePartial(partials.table,depth0,{"name":"table","hash":{"data":((stack1 = (depth0 != null ? depth0.stats : depth0)) != null ? stack1.misc : stack1)},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
+ "</td>\n </tr>\n <tr>\n <th colspan=\"3\">Classifications</th>\n </tr>\n <tr>\n <td colspan=\"3\">"
@@ -55,19 +54,17 @@ templates['kanji.html'] = template({"1":function(container,depth0,helpers,partia
+ "</td>\n </tr>\n <tr>\n <th colspan=\"3\">Dictionary Indices</th>\n </tr>\n <tr>\n <td colspan=\"3\">"
+ ((stack1 = container.invokePartial(partials.table,depth0,{"name":"table","hash":{"data":((stack1 = (depth0 != null ? depth0.stats : depth0)) != null ? stack1.index : stack1)},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
+ "</td>\n </tr>\n </table>\n\n"
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.debug : depth0),{"name":"if","hash":{},"fn":container.program(31, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.debug : depth0),{"name":"if","hash":{},"fn":container.program(29, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</div>\n";
},"11":function(container,depth0,helpers,partials,data) {
return " <a href=\"#\" class=\"action-view-note pending disabled\"><img src=\"/mixed/img/view-note.svg\" title=\"View added note (Alt + V)\" alt></a>\n <a href=\"#\" class=\"action-add-note pending disabled\" data-mode=\"kanji\"><img src=\"/mixed/img/add-term-kanji.svg\" title=\"Add Kanji (Alt + K)\" alt></a>\n";
},"13":function(container,depth0,helpers,partials,data) {
- return " <a href=\"#\" class=\"source-term\"><img src=\"/mixed/img/source-term.svg\" title=\"Source term (Alt + B)\" alt></a>\n";
-},"15":function(container,depth0,helpers,partials,data) {
var stack1;
return " <div>\n"
- + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.frequencies : depth0),{"name":"each","hash":{},"fn":container.program(16, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.frequencies : depth0),{"name":"each","hash":{},"fn":container.program(14, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " </div>\n";
-},"16":function(container,depth0,helpers,partials,data) {
+},"14":function(container,depth0,helpers,partials,data) {
var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
return " <span class=\"label label-default tag-frequency\">"
@@ -75,13 +72,13 @@ templates['kanji.html'] = template({"1":function(container,depth0,helpers,partia
+ ":"
+ alias4(((helper = (helper = helpers.frequency || (depth0 != null ? depth0.frequency : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"frequency","hash":{},"data":data}) : helper)))
+ "</span>\n";
-},"18":function(container,depth0,helpers,partials,data) {
+},"16":function(container,depth0,helpers,partials,data) {
var stack1;
return " <div>\n"
- + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.tags : depth0),{"name":"each","hash":{},"fn":container.program(19, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.tags : depth0),{"name":"each","hash":{},"fn":container.program(17, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " </div>\n";
-},"19":function(container,depth0,helpers,partials,data) {
+},"17":function(container,depth0,helpers,partials,data) {
var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
return " <span class=\"label label-default tag-"
@@ -91,68 +88,81 @@ templates['kanji.html'] = template({"1":function(container,depth0,helpers,partia
+ "\">"
+ alias4(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data}) : helper)))
+ "</span>\n";
-},"21":function(container,depth0,helpers,partials,data) {
+},"19":function(container,depth0,helpers,partials,data) {
var stack1;
return " <ol>"
- + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(22, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(20, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</ol>\n";
-},"22":function(container,depth0,helpers,partials,data) {
+},"20":function(container,depth0,helpers,partials,data) {
return "<li><span class=\"glossary-item\">"
+ container.escapeExpression(container.lambda(depth0, depth0))
+ "</span></li>";
-},"24":function(container,depth0,helpers,partials,data) {
+},"22":function(container,depth0,helpers,partials,data) {
var stack1;
return " <span class=\"glossary-item\">"
+ container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["0"] : stack1), depth0))
+ "</span>\n";
-},"26":function(container,depth0,helpers,partials,data) {
+},"24":function(container,depth0,helpers,partials,data) {
var stack1;
return "<dl>"
- + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.onyomi : depth0),{"name":"each","hash":{},"fn":container.program(27, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.onyomi : depth0),{"name":"each","hash":{},"fn":container.program(25, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</dl>";
-},"27":function(container,depth0,helpers,partials,data) {
+},"25":function(container,depth0,helpers,partials,data) {
return "<dd>"
+ container.escapeExpression(container.lambda(depth0, depth0))
+ "</dd>";
-},"29":function(container,depth0,helpers,partials,data) {
+},"27":function(container,depth0,helpers,partials,data) {
var stack1;
return "<dl>"
- + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.kunyomi : depth0),{"name":"each","hash":{},"fn":container.program(27, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.kunyomi : depth0),{"name":"each","hash":{},"fn":container.program(25, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</dl>";
-},"31":function(container,depth0,helpers,partials,data) {
+},"29":function(container,depth0,helpers,partials,data) {
var stack1, helper, options, buffer =
" <pre>";
- stack1 = ((helper = (helper = helpers.dumpObject || (depth0 != null ? depth0.dumpObject : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"dumpObject","hash":{},"fn":container.program(32, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
+ stack1 = ((helper = (helper = helpers.dumpObject || (depth0 != null ? depth0.dumpObject : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"dumpObject","hash":{},"fn":container.program(30, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
if (!helpers.dumpObject) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
if (stack1 != null) { buffer += stack1; }
return buffer + "</pre>\n";
-},"32":function(container,depth0,helpers,partials,data) {
+},"30":function(container,depth0,helpers,partials,data) {
var stack1;
return ((stack1 = container.lambda(depth0, depth0)) != null ? stack1 : "");
-},"34":function(container,depth0,helpers,partials,data,blockParams,depths) {
- var stack1;
+},"32":function(container,depth0,helpers,partials,data,blockParams,depths) {
+ var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
- return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(35, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
-},"35":function(container,depth0,helpers,partials,data,blockParams,depths) {
+ return "<div class=\"term-navigation\">\n <a href=\"#\" "
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.source : depth0),{"name":"if","hash":{},"fn":container.program(33, data, 0, blockParams, depths),"inverse":container.program(35, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "")
+ + "><img src=\"/mixed/img/source-term.svg\" title=\"Source term (Alt + B)\" alt></a>\n <a href=\"#\" "
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.next : depth0),{"name":"if","hash":{},"fn":container.program(37, data, 0, blockParams, depths),"inverse":container.program(39, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "")
+ + "><img src=\"/mixed/img/source-term.svg\" style=\"transform: scaleX(-1);\" title=\"Next term (Alt + F)\" alt></a>\n</div>\n"
+ + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(41, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+},"33":function(container,depth0,helpers,partials,data) {
+ return "class=\"source-term\"";
+},"35":function(container,depth0,helpers,partials,data) {
+ return "class=\"source-term term-button-fade\"";
+},"37":function(container,depth0,helpers,partials,data) {
+ return "class=\"next-term\"";
+},"39":function(container,depth0,helpers,partials,data) {
+ return "class=\"next-term term-button-fade\"";
+},"41":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1;
- return ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.first),{"name":"unless","hash":{},"fn":container.program(36, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ return ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.first),{"name":"unless","hash":{},"fn":container.program(42, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n"
- + ((stack1 = container.invokePartial(partials.kanji,depth0,{"name":"kanji","hash":{"root":(depths[1] != null ? depths[1].root : depths[1]),"source":(depths[1] != null ? depths[1].source : depths[1]),"addable":(depths[1] != null ? depths[1].addable : depths[1]),"debug":(depths[1] != null ? depths[1].debug : depths[1])},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
-},"36":function(container,depth0,helpers,partials,data) {
+ + ((stack1 = container.invokePartial(partials.kanji,depth0,{"name":"kanji","hash":{"root":(depths[1] != null ? depths[1].root : depths[1]),"addable":(depths[1] != null ? depths[1].addable : depths[1]),"debug":(depths[1] != null ? depths[1].debug : depths[1])},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
+},"42":function(container,depth0,helpers,partials,data) {
return "<hr>";
-},"38":function(container,depth0,helpers,partials,data) {
+},"44":function(container,depth0,helpers,partials,data) {
return "<p class=\"note\">No results found</p>\n";
},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1;
return "\n\n"
- + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"if","hash":{},"fn":container.program(34, data, 0, blockParams, depths),"inverse":container.program(38, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
+ + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"if","hash":{},"fn":container.program(32, data, 0, blockParams, depths),"inverse":container.program(44, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
},"main_d": function(fn, props, container, depth0, data, blockParams, depths) {
var decorators = container.decorators;
@@ -306,17 +316,16 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia
return "<div class=\"entry\" data-type=\"term\">\n <div class=\"actions\">\n"
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.addable : depth0),{"name":"if","hash":{},"fn":container.program(25, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.merged : depth0),{"name":"unless","hash":{},"fn":container.program(27, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.source : depth0),{"name":"if","hash":{},"fn":container.program(30, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " <img src=\"/mixed/img/entry-current.svg\" class=\"current\" title=\"Current entry (Alt + Up/Down/Home/End/PgUp/PgDn)\" alt>\n </div>\n\n"
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.merged : depth0),{"name":"if","hash":{},"fn":container.program(32, data, 0, blockParams, depths),"inverse":container.program(47, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.merged : depth0),{"name":"if","hash":{},"fn":container.program(30, data, 0, blockParams, depths),"inverse":container.program(45, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "")
+ "\n"
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.reasons : depth0),{"name":"if","hash":{},"fn":container.program(50, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.reasons : depth0),{"name":"if","hash":{},"fn":container.program(48, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n"
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.frequencies : depth0),{"name":"if","hash":{},"fn":container.program(54, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.frequencies : depth0),{"name":"if","hash":{},"fn":container.program(52, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n <div class=\"glossary\">\n"
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.grouped : depth0),{"name":"if","hash":{},"fn":container.program(57, data, 0, blockParams, depths),"inverse":container.program(63, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.grouped : depth0),{"name":"if","hash":{},"fn":container.program(55, data, 0, blockParams, depths),"inverse":container.program(61, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "")
+ " </div>\n\n"
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.debug : depth0),{"name":"if","hash":{},"fn":container.program(66, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.debug : depth0),{"name":"if","hash":{},"fn":container.program(64, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</div>\n";
},"25":function(container,depth0,helpers,partials,data) {
return " <a href=\"#\" class=\"action-view-note pending disabled\"><img src=\"/mixed/img/view-note.svg\" title=\"View added note (Alt + V)\" alt></a>\n <a href=\"#\" class=\"action-add-note pending disabled\" data-mode=\"term-kanji\"><img src=\"/mixed/img/add-term-kanji.svg\" title=\"Add expression (Alt + E)\" alt></a>\n <a href=\"#\" class=\"action-add-note pending disabled\" data-mode=\"term-kana\"><img src=\"/mixed/img/add-term-kana.svg\" title=\"Add reading (Alt + R)\" alt></a>\n";
@@ -326,47 +335,45 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia
return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.playback : depth0),{"name":"if","hash":{},"fn":container.program(28, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
},"28":function(container,depth0,helpers,partials,data) {
return " <a href=\"#\" class=\"action-play-audio\"><img src=\"/mixed/img/play-audio.svg\" title=\"Play audio (Alt + P)\" alt></a>\n";
-},"30":function(container,depth0,helpers,partials,data) {
- return " <a href=\"#\" class=\"source-term\"><img src=\"/mixed/img/source-term.svg\" title=\"Source term (Alt + B)\" alt></a>\n";
-},"32":function(container,depth0,helpers,partials,data,blockParams,depths) {
+},"30":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1;
- return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.expressions : depth0),{"name":"each","hash":{},"fn":container.program(33, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
-},"33":function(container,depth0,helpers,partials,data,blockParams,depths) {
+ return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.expressions : depth0),{"name":"each","hash":{},"fn":container.program(31, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+},"31":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1, helper, options, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", buffer =
"<div class=\"expression expression-scan-toggle\"><span class=\"expression-"
+ container.escapeExpression(((helper = (helper = helpers.termFrequency || (depth0 != null ? depth0.termFrequency : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"termFrequency","hash":{},"data":data}) : helper)))
+ "\">";
- stack1 = ((helper = (helper = helpers.kanjiLinks || (depth0 != null ? depth0.kanjiLinks : depth0)) != null ? helper : alias2),(options={"name":"kanjiLinks","hash":{},"fn":container.program(34, data, 0, blockParams, depths),"inverse":container.noop,"data":data}),(typeof helper === alias3 ? helper.call(alias1,options) : helper));
+ stack1 = ((helper = (helper = helpers.kanjiLinks || (depth0 != null ? depth0.kanjiLinks : depth0)) != null ? helper : alias2),(options={"name":"kanjiLinks","hash":{},"fn":container.program(32, data, 0, blockParams, depths),"inverse":container.noop,"data":data}),(typeof helper === alias3 ? helper.call(alias1,options) : helper));
if (!helpers.kanjiLinks) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
if (stack1 != null) { buffer += stack1; }
return buffer + "</span><div class=\"peek-wrapper\">"
- + ((stack1 = helpers["if"].call(alias1,(depths[1] != null ? depths[1].playback : depths[1]),{"name":"if","hash":{},"fn":container.program(37, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.termTags : depth0),{"name":"if","hash":{},"fn":container.program(39, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.frequencies : depth0),{"name":"if","hash":{},"fn":container.program(42, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers["if"].call(alias1,(depths[1] != null ? depths[1].playback : depths[1]),{"name":"if","hash":{},"fn":container.program(35, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.termTags : depth0),{"name":"if","hash":{},"fn":container.program(37, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.frequencies : depth0),{"name":"if","hash":{},"fn":container.program(40, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</div><span class=\""
- + ((stack1 = helpers["if"].call(alias1,(data && data.last),{"name":"if","hash":{},"fn":container.program(45, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers["if"].call(alias1,(data && data.last),{"name":"if","hash":{},"fn":container.program(43, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\">、</span></div>";
-},"34":function(container,depth0,helpers,partials,data) {
+},"32":function(container,depth0,helpers,partials,data) {
var stack1, helper, options;
- stack1 = ((helper = (helper = helpers.furigana || (depth0 != null ? depth0.furigana : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"furigana","hash":{},"fn":container.program(35, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
+ stack1 = ((helper = (helper = helpers.furigana || (depth0 != null ? depth0.furigana : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"furigana","hash":{},"fn":container.program(33, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
if (!helpers.furigana) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
if (stack1 != null) { return stack1; }
else { return ''; }
-},"35":function(container,depth0,helpers,partials,data) {
+},"33":function(container,depth0,helpers,partials,data) {
var stack1;
return ((stack1 = container.lambda(depth0, depth0)) != null ? stack1 : "");
-},"37":function(container,depth0,helpers,partials,data) {
+},"35":function(container,depth0,helpers,partials,data) {
return "<a href=\"#\" class=\"action-play-audio\"><img src=\"/mixed/img/play-audio.svg\" title=\"Play audio\" alt></a>";
-},"39":function(container,depth0,helpers,partials,data) {
+},"37":function(container,depth0,helpers,partials,data) {
var stack1;
return "<div class=\"tags\">"
- + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.termTags : depth0),{"name":"each","hash":{},"fn":container.program(40, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.termTags : depth0),{"name":"each","hash":{},"fn":container.program(38, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</div>";
-},"40":function(container,depth0,helpers,partials,data) {
+},"38":function(container,depth0,helpers,partials,data) {
var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
return " <span class=\"label label-default tag-"
@@ -376,13 +383,13 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia
+ "\">"
+ alias4(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data}) : helper)))
+ "</span>\n";
-},"42":function(container,depth0,helpers,partials,data) {
+},"40":function(container,depth0,helpers,partials,data) {
var stack1;
return "<div class=\"frequencies\">"
- + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.frequencies : depth0),{"name":"each","hash":{},"fn":container.program(43, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.frequencies : depth0),{"name":"each","hash":{},"fn":container.program(41, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</div>";
-},"43":function(container,depth0,helpers,partials,data) {
+},"41":function(container,depth0,helpers,partials,data) {
var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
return " <span class=\"label label-default tag-frequency\">"
@@ -390,45 +397,45 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia
+ ":"
+ alias4(((helper = (helper = helpers.frequency || (depth0 != null ? depth0.frequency : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"frequency","hash":{},"data":data}) : helper)))
+ "</span>\n";
-},"45":function(container,depth0,helpers,partials,data) {
+},"43":function(container,depth0,helpers,partials,data) {
return "invisible";
-},"47":function(container,depth0,helpers,partials,data) {
+},"45":function(container,depth0,helpers,partials,data) {
var stack1, helper, options, alias1=depth0 != null ? depth0 : (container.nullContext || {}), buffer =
" <div class=\"expression expression-scan-toggle\">";
- stack1 = ((helper = (helper = helpers.kanjiLinks || (depth0 != null ? depth0.kanjiLinks : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"kanjiLinks","hash":{},"fn":container.program(34, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(alias1,options) : helper));
+ stack1 = ((helper = (helper = helpers.kanjiLinks || (depth0 != null ? depth0.kanjiLinks : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"kanjiLinks","hash":{},"fn":container.program(32, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(alias1,options) : helper));
if (!helpers.kanjiLinks) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
if (stack1 != null) { buffer += stack1; }
return buffer + "</div>\n"
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.termTags : depth0),{"name":"if","hash":{},"fn":container.program(48, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
-},"48":function(container,depth0,helpers,partials,data) {
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.termTags : depth0),{"name":"if","hash":{},"fn":container.program(46, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+},"46":function(container,depth0,helpers,partials,data) {
var stack1;
return " <div style=\"display: inline-block;\">\n"
+ ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.termTags : depth0),{"name":"each","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " </div>\n";
-},"50":function(container,depth0,helpers,partials,data) {
+},"48":function(container,depth0,helpers,partials,data) {
var stack1;
return " <div class=\"reasons\">\n"
- + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.reasons : depth0),{"name":"each","hash":{},"fn":container.program(51, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.reasons : depth0),{"name":"each","hash":{},"fn":container.program(49, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " </div>\n";
-},"51":function(container,depth0,helpers,partials,data) {
+},"49":function(container,depth0,helpers,partials,data) {
var stack1;
return " <span class=\"reasons\">"
+ container.escapeExpression(container.lambda(depth0, depth0))
+ "</span> "
- + ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.last),{"name":"unless","hash":{},"fn":container.program(52, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.last),{"name":"unless","hash":{},"fn":container.program(50, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n";
-},"52":function(container,depth0,helpers,partials,data) {
+},"50":function(container,depth0,helpers,partials,data) {
return "&laquo;";
-},"54":function(container,depth0,helpers,partials,data) {
+},"52":function(container,depth0,helpers,partials,data) {
var stack1;
return " <div>\n"
- + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.frequencies : depth0),{"name":"each","hash":{},"fn":container.program(55, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.frequencies : depth0),{"name":"each","hash":{},"fn":container.program(53, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " </div>\n";
-},"55":function(container,depth0,helpers,partials,data) {
+},"53":function(container,depth0,helpers,partials,data) {
var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
return " <span class=\"label label-default tag-frequency\">"
@@ -436,61 +443,74 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia
+ ":"
+ alias4(((helper = (helper = helpers.frequency || (depth0 != null ? depth0.frequency : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"frequency","hash":{},"data":data}) : helper)))
+ "</span>\n";
-},"57":function(container,depth0,helpers,partials,data,blockParams,depths) {
+},"55":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1;
- return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.definitions : depth0)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(58, data, 0, blockParams, depths),"inverse":container.program(61, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
-},"58":function(container,depth0,helpers,partials,data,blockParams,depths) {
+ return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.definitions : depth0)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(56, data, 0, blockParams, depths),"inverse":container.program(59, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
+},"56":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1;
return " <ol>\n"
- + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(59, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(57, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " </ol>\n";
-},"59":function(container,depth0,helpers,partials,data,blockParams,depths) {
+},"57":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1;
return " <li>"
+ ((stack1 = container.invokePartial(partials.definition,depth0,{"name":"definition","hash":{"compactGlossaries":(depths[1] != null ? depths[1].compactGlossaries : depths[1])},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
+ "</li>\n";
-},"61":function(container,depth0,helpers,partials,data) {
+},"59":function(container,depth0,helpers,partials,data) {
var stack1;
return ((stack1 = container.invokePartial(partials.definition,((stack1 = (depth0 != null ? depth0.definitions : depth0)) != null ? stack1["0"] : stack1),{"name":"definition","hash":{"compactGlossaries":(depth0 != null ? depth0.compactGlossaries : depth0)},"data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
-},"63":function(container,depth0,helpers,partials,data,blockParams,depths) {
+},"61":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1;
- return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.merged : depth0),{"name":"if","hash":{},"fn":container.program(57, data, 0, blockParams, depths),"inverse":container.program(64, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
-},"64":function(container,depth0,helpers,partials,data) {
+ return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.merged : depth0),{"name":"if","hash":{},"fn":container.program(55, data, 0, blockParams, depths),"inverse":container.program(62, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
+},"62":function(container,depth0,helpers,partials,data) {
var stack1;
return ((stack1 = container.invokePartial(partials.definition,depth0,{"name":"definition","hash":{"compactGlossaries":(depth0 != null ? depth0.compactGlossaries : depth0)},"data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
+ " ";
-},"66":function(container,depth0,helpers,partials,data) {
+},"64":function(container,depth0,helpers,partials,data) {
var stack1, helper, options, buffer =
" <pre>";
- stack1 = ((helper = (helper = helpers.dumpObject || (depth0 != null ? depth0.dumpObject : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"dumpObject","hash":{},"fn":container.program(35, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
+ stack1 = ((helper = (helper = helpers.dumpObject || (depth0 != null ? depth0.dumpObject : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"dumpObject","hash":{},"fn":container.program(33, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
if (!helpers.dumpObject) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
if (stack1 != null) { buffer += stack1; }
return buffer + "</pre>\n";
-},"68":function(container,depth0,helpers,partials,data,blockParams,depths) {
- var stack1;
-
- return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(69, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
-},"69":function(container,depth0,helpers,partials,data,blockParams,depths) {
- var stack1;
+},"66":function(container,depth0,helpers,partials,data,blockParams,depths) {
+ var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
- return ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.first),{"name":"unless","hash":{},"fn":container.program(70, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ return "<div class=\"term-navigation\">\n <a href=\"#\" "
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.source : depth0),{"name":"if","hash":{},"fn":container.program(67, data, 0, blockParams, depths),"inverse":container.program(69, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "")
+ + "><img src=\"/mixed/img/source-term.svg\" title=\"Source term (Alt + B)\" alt></a>\n <a href=\"#\" "
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.next : depth0),{"name":"if","hash":{},"fn":container.program(71, data, 0, blockParams, depths),"inverse":container.program(73, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "")
+ + "><img src=\"/mixed/img/source-term.svg\" style=\"transform: scaleX(-1);\" title=\"Next term (Alt + F)\" alt></a>\n</div>\n"
+ + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(75, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+},"67":function(container,depth0,helpers,partials,data) {
+ return "class=\"source-term\"";
+},"69":function(container,depth0,helpers,partials,data) {
+ return "class=\"source-term term-button-fade\"";
+},"71":function(container,depth0,helpers,partials,data) {
+ return "class=\"next-term\"";
+},"73":function(container,depth0,helpers,partials,data) {
+ return "class=\"next-term term-button-fade\"";
+},"75":function(container,depth0,helpers,partials,data,blockParams,depths) {
+ var stack1;
+
+ return ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.first),{"name":"unless","hash":{},"fn":container.program(76, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n"
- + ((stack1 = container.invokePartial(partials.term,depth0,{"name":"term","hash":{"source":(depths[1] != null ? depths[1].source : depths[1]),"compactGlossaries":(depths[1] != null ? depths[1].compactGlossaries : depths[1]),"playback":(depths[1] != null ? depths[1].playback : depths[1]),"addable":(depths[1] != null ? depths[1].addable : depths[1]),"merged":(depths[1] != null ? depths[1].merged : depths[1]),"grouped":(depths[1] != null ? depths[1].grouped : depths[1]),"debug":(depths[1] != null ? depths[1].debug : depths[1])},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
-},"70":function(container,depth0,helpers,partials,data) {
+ + ((stack1 = container.invokePartial(partials.term,depth0,{"name":"term","hash":{"compactGlossaries":(depths[1] != null ? depths[1].compactGlossaries : depths[1]),"playback":(depths[1] != null ? depths[1].playback : depths[1]),"addable":(depths[1] != null ? depths[1].addable : depths[1]),"merged":(depths[1] != null ? depths[1].merged : depths[1]),"grouped":(depths[1] != null ? depths[1].grouped : depths[1]),"debug":(depths[1] != null ? depths[1].debug : depths[1])},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
+},"76":function(container,depth0,helpers,partials,data) {
return "<hr>";
-},"72":function(container,depth0,helpers,partials,data) {
+},"78":function(container,depth0,helpers,partials,data) {
return "<p class=\"note\">No results found.</p>\n";
},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1;
return "\n\n"
- + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"if","hash":{},"fn":container.program(68, data, 0, blockParams, depths),"inverse":container.program(72, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
+ + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"if","hash":{},"fn":container.program(66, data, 0, blockParams, depths),"inverse":container.program(78, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
},"main_d": function(fn, props, container, depth0, data, blockParams, depths) {
var decorators = container.decorators;
diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js
index e27cbdff..202014c9 100644
--- a/ext/bg/js/translator.js
+++ b/ext/bg/js/translator.js
@@ -51,7 +51,7 @@ class Translator {
const definitionsBySequence = dictTermsMergeBySequence(definitions, mainDictionary);
const defaultDefinitions = definitionsBySequence['-1'];
- const sequenceList = Object.keys(definitionsBySequence).map(v => Number(v)).filter(v => v >= 0);
+ const sequenceList = Object.keys(definitionsBySequence).map((v) => Number(v)).filter((v) => v >= 0);
const sequencedDefinitions = sequenceList.map((key) => ({
definitions: definitionsBySequence[key],
rawDefinitions: []
@@ -124,7 +124,7 @@ class Translator {
for (const expression of result.expressions.keys()) {
for (const reading of result.expressions.get(expression).keys()) {
const termTags = result.expressions.get(expression).get(reading);
- const score = termTags.map(tag => tag.score).reduce((p, v) => p + v, 0);
+ const score = termTags.map((tag) => tag.score).reduce((p, v) => p + v, 0);
expressions.push({
expression: expression,
reading: reading,
@@ -173,7 +173,7 @@ class Translator {
async findTermsMerged(text, details, options) {
const dictionaries = dictEnabledSet(options);
- const secondarySearchTitles = Object.keys(options.dictionaries).filter(dict => options.dictionaries[dict].allowSecondarySearches);
+ const secondarySearchTitles = Object.keys(options.dictionaries).filter((dict) => options.dictionaries[dict].allowSecondarySearches);
const titles = Object.keys(dictionaries);
const [definitions, length] = await this.findTermsInternal(text, dictionaries, options.scanning.alphanumeric, details);
const {sequencedDefinitions, defaultDefinitions} = await this.getSequencedDefinitions(definitions, options.general.mainDictionary);
@@ -297,7 +297,7 @@ class Translator {
for (const deinflection of deinflections) {
const term = deinflection.term;
let deinflectionArray;
- if (uniqueDeinflectionsMap.hasOwnProperty(term)) {
+ if (hasOwn(uniqueDeinflectionsMap, term)) {
deinflectionArray = uniqueDeinflectionsMap[term];
} else {
deinflectionArray = [];
@@ -320,7 +320,7 @@ class Translator {
}
}
- return deinflections.filter(e => e.definitions.length > 0);
+ return deinflections.filter((e) => e.definitions.length > 0);
}
getDeinflections(text) {
@@ -355,7 +355,7 @@ class Translator {
const kanjiUnique = {};
const kanjiList = [];
for (const c of text) {
- if (!kanjiUnique.hasOwnProperty(c)) {
+ if (!hasOwn(kanjiUnique, c)) {
kanjiList.push(c);
kanjiUnique[c] = true;
}
@@ -417,7 +417,7 @@ class Translator {
const expression = term.expression;
term.frequencies = [];
- if (termsUniqueMap.hasOwnProperty(expression)) {
+ if (hasOwn(termsUniqueMap, expression)) {
termsUniqueMap[expression].push(term);
} else {
const termList = [term];
@@ -464,7 +464,7 @@ class Translator {
const category = meta.category;
const group = (
- stats.hasOwnProperty(category) ?
+ hasOwn(stats, category) ?
stats[category] :
(stats[category] = [])
);
@@ -484,7 +484,7 @@ class Translator {
async getTagMetaList(names, title) {
const tagMetaList = [];
const cache = (
- this.tagCache.hasOwnProperty(title) ?
+ hasOwn(this.tagCache, title) ?
this.tagCache[title] :
(this.tagCache[title] = {})
);
@@ -492,7 +492,7 @@ class Translator {
for (const name of names) {
const base = Translator.getNameBase(name);
- if (cache.hasOwnProperty(base)) {
+ if (hasOwn(cache, base)) {
tagMetaList.push(cache[base]);
} else {
const tagMeta = await this.database.findTagForTitle(base, title);
diff --git a/ext/bg/js/util.js b/ext/bg/js/util.js
index a6126677..3dd5fd55 100644
--- a/ext/bg/js/util.js
+++ b/ext/bg/js/util.js
@@ -16,12 +16,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-function utilAsync(func) {
- return function(...args) {
- func.apply(this, args);
- };
-}
-
function utilIsolate(data) {
return JSON.parse(JSON.stringify(data));
}
@@ -47,13 +41,13 @@ function utilSetEqual(setA, setB) {
function utilSetIntersection(setA, setB) {
return new Set(
- [...setA].filter(value => setB.has(value))
+ [...setA].filter((value) => setB.has(value))
);
}
function utilSetDifference(setA, setB) {
return new Set(
- [...setA].filter(value => !setB.has(value))
+ [...setA].filter((value) => !setB.has(value))
);
}
@@ -117,7 +111,3 @@ function utilReadFile(file) {
reader.readAsBinaryString(file);
});
}
-
-function utilIsObject(value) {
- return typeof value === 'object' && value !== null && !Array.isArray(value);
-}
diff --git a/ext/bg/legal.html b/ext/bg/legal.html
index 3047ab3e..082239d7 100644
--- a/ext/bg/legal.html
+++ b/ext/bg/legal.html
@@ -44,7 +44,7 @@ and are used in conformance with the Group's <a href="https://www.edrdg.org/edrd
<li><a href="https://github.com/wycats/handlebars.js/blob/v4.0.6/LICENSE" target="_blank" rel="noopener">Handlebars v4.0.6</a></li>
<li><a href="https://github.com/jquery/jquery/blob/3.2.1/LICENSE.txt" target="_blank" rel="noopener">jQuery v3.2.1</a></li>
<li><a href="https://github.com/Stuk/jszip/blob/v3.1.3/LICENSE.markdown" target="_blank" rel="noopener">JSZip v3.1.3</a></li>
- <li><a href="https://github.com/WaniKani/WanaKana/blob/2.2.3/LICENSE" target="_blank" rel="noopener">WanaKana v2.2.3</a></li>
+ <li><a href="https://github.com/WaniKani/WanaKana/blob/4.0.2/LICENSE" target="_blank" rel="noopener">WanaKana v4.0.2</a></li>
</ul>
</div>
</div>
diff --git a/ext/bg/search.html b/ext/bg/search.html
index e819ebe6..fef30456 100644
--- a/ext/bg/search.html
+++ b/ext/bg/search.html
@@ -25,7 +25,7 @@
<p style="margin-bottom: 0;">Search your installed dictionaries by entering a Japanese expression into the field below.</p>
</div>
- <div class="input-group" style="padding-top: 10px;">
+ <div class="input-group" style="padding-top: 20px;">
<span title="Enable kana input method" class="input-group-text">
<input type="checkbox" id="wanakana-enable" class="icon-checkbox" />
<label for="wanakana-enable" class="scan-disable">あ</label>
@@ -60,7 +60,8 @@
<script src="/mixed/lib/handlebars.min.js"></script>
<script src="/mixed/lib/wanakana.min.js"></script>
- <script src="/mixed/js/extension.js"></script>
+ <script src="/mixed/js/core.js"></script>
+ <script src="/mixed/js/dom.js"></script>
<script src="/bg/js/dictionary.js"></script>
<script src="/bg/js/handlebars.js"></script>
@@ -70,6 +71,7 @@
<script src="/fg/js/source.js"></script>
<script src="/fg/js/util.js"></script>
<script src="/mixed/js/audio.js"></script>
+ <script src="/mixed/js/display-context.js"></script>
<script src="/mixed/js/display.js"></script>
<script src="/mixed/js/japanese.js"></script>
<script src="/mixed/js/scroll.js"></script>
diff --git a/ext/bg/settings-popup-preview.html b/ext/bg/settings-popup-preview.html
index d27a9a33..339467d4 100644
--- a/ext/bg/settings-popup-preview.html
+++ b/ext/bg/settings-popup-preview.html
@@ -117,7 +117,9 @@
</div>
</div></div></div>
- <script src="/mixed/js/extension.js"></script>
+ <script src="/mixed/js/core.js"></script>
+ <script src="/mixed/js/dom.js"></script>
+
<script src="/fg/js/api.js"></script>
<script src="/fg/js/document.js"></script>
<script src="/fg/js/frontend-api-receiver.js"></script>
@@ -126,6 +128,6 @@
<script src="/fg/js/util.js"></script>
<script src="/fg/js/popup-proxy-host.js"></script>
<script src="/fg/js/frontend.js"></script>
- <script src="/bg/js/settings-popup-preview.js"></script>
+ <script src="/bg/js/settings/popup-preview-frame.js"></script>
</body>
</html>
diff --git a/ext/bg/settings.html b/ext/bg/settings.html
index 262386e9..3c5494b8 100644
--- a/ext/bg/settings.html
+++ b/ext/bg/settings.html
@@ -650,7 +650,7 @@
</div>
</div>
- <div class="alert alert-danger" id="anki-error"></div>
+ <div class="alert alert-danger" id="anki-error" hidden></div>
<div class="form-group">
<label for="card-tags">Card tags <span class="label-light">(comma or space separated)</span></label>
@@ -694,16 +694,16 @@
</ul>
<div class="tab-content">
- <div id="terms" class="tab-pane fade in active">
+ <div id="terms" class="tab-pane fade in active" data-anki-card-type="terms">
<div class="row">
<div class="form-group col-xs-6">
<label for="anki-terms-deck">Deck</label>
- <select class="form-control anki-deck" id="anki-terms-deck"></select>
+ <select class="form-control anki-deck" id="anki-terms-deck" data-anki-card-type="terms"></select>
</div>
<div class="form-group col-xs-6">
<label for="anki-terms-model">Model</label>
- <select class="form-control anki-model" id="anki-terms-model"></select>
+ <select class="form-control anki-model" id="anki-terms-model" data-anki-card-type="terms"></select>
</div>
</div>
@@ -713,16 +713,16 @@
</table>
</div>
- <div id="kanji" class="tab-pane fade">
+ <div id="kanji" class="tab-pane fade" data-anki-card-type="kanji">
<div class="row">
<div class="form-group col-xs-6">
<label for="anki-kanji-deck">Deck</label>
- <select class="form-control anki-deck" id="anki-kanji-deck"></select>
+ <select class="form-control anki-deck" id="anki-kanji-deck" data-anki-card-type="kanji"></select>
</div>
<div class="form-group col-xs-6">
<label for="anki-kanji-model">Model</label>
- <select class="form-control anki-model" id="anki-kanji-model"></select>
+ <select class="form-control anki-model" id="anki-kanji-model" data-anki-card-type="kanji"></select>
</div>
</div>
@@ -864,7 +864,8 @@
<script src="/mixed/lib/handlebars.min.js"></script>
<script src="/mixed/lib/wanakana.min.js"></script>
- <script src="/mixed/js/extension.js"></script>
+ <script src="/mixed/js/core.js"></script>
+ <script src="/mixed/js/dom.js"></script>
<script src="/mixed/js/japanese.js"></script>
<script src="/bg/js/anki.js"></script>
@@ -875,13 +876,20 @@
<script src="/bg/js/dictionary.js"></script>
<script src="/bg/js/handlebars.js"></script>
<script src="/bg/js/options.js"></script>
+ <script src="/bg/js/page-exit-prevention.js"></script>
<script src="/bg/js/profile-conditions.js"></script>
<script src="/bg/js/templates.js"></script>
<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>
+ <script src="/bg/js/settings/anki.js"></script>
+ <script src="/bg/js/settings/anki-templates.js"></script>
+ <script src="/bg/js/settings/audio.js"></script>
+ <script src="/bg/js/settings/dictionaries.js"></script>
+ <script src="/bg/js/settings/popup-preview.js"></script>
+ <script src="/bg/js/settings/profiles.js"></script>
+ <script src="/bg/js/settings/storage.js"></script>
+
+ <script src="/bg/js/settings/main.js"></script>
</body>
</html>