Merge branch 'dev'

This commit is contained in:
Alex Yatskov 2017-11-04 08:18:49 -07:00
commit d505edb94b
17 changed files with 1022 additions and 210 deletions

View File

@ -258,52 +258,72 @@ versions packaged.
## Frequently Asked Questions ##
* **Why does the Kanji results page display "No data found" for several fields?**
**I'm having problems importing dictionaries in Firefox, what do I do?**
You are using data from the KANJIDIC dictionary that was exported for an earlier version of Yomichan. It does not
contain the additional information which newer versions of Yofomichan expect. Unfortunately, since major browser
implementations of IndexedDB do not provide reliable means for selective bulk data deletion, you will need purge
your database and install the latest version of the KANJIDIC to see additional information about characters.
Yomichan uses the cross-browser IndexedDB system for storing imported dictionary data into your user profile. Although
everything "just works" in Chrome, depending on settings, Firefox users can run into problems due browser bugs.
Yomichan catches errors and tries to offer suggestions about how to work around Firefox issues, but in general at least
one of the following solutions should work for you:
* **Can I still create cards without HTML formatting? The option for it is gone!**
* Make sure you have cookies enabled. It appears that disabling them also disables IndexedDB for some reason. You
can still have cookies be disabled on other sites; just make sure to add the Yomichan extension to the whitelist of
whatever tool you are using to restrict cookies. You can get the extension "URL" by looking at the address bar when
you have the search page open.
* Make sure that you have sufficient disk space available on the drive Firefox uses to store your user profile.
Firefox limits the amount of space that can be used by IndexedDB to a small fraction of the disk space actually
available on your computer.
* Make sure that you have history set to "Remember history" enabled in your privacy settings. When this option is
set to "Never remember history", IndexedDB access is once again disabled for an inexplicable reason.
* As a last resort, try using the [Refresh Firefox](https://support.mozilla.org/en-US/kb/reset-preferences-fix-problems)
feature to reset your user profile. It appears that the Firefox profile system can corrupt itself preventing
IndexedDB from being accessible to Yomichan.
Developing Yomichan is a constant balance between including useful features and keeping complexity at a minimum.
With the new user-editable card template system, it is possible to create text-only cards without having to double
the number field of templates in the extension itself. If you would like to stop HTML tags from being added to your
cards, simply copy the contents of the <a href="dl/fields.txt">text-only field template</a> into the template box on
the Anki settings page (make sure you have the *Show advanced options* checkbox ticked), making sure to replace the
existing values.
**Why does the Kanji results page display "No data found" for several fields?**
* **Will you add support for online dictionaries?**
You are using data from the KANJIDIC dictionary that was exported for an earlier version of Yomichan. It does not
contain the additional information which newer versions of Yofomichan expect. Unfortunately, since major browser
implementations of IndexedDB do not provide reliable means for selective bulk data deletion, you will need purge
your database and install the latest version of the KANJIDIC to see additional information about characters.
Online dictionaries will never be implemented because it is impossible to support them in a robust way. In order to
perform Japanese deinflection, Yomichan must execute dozens of database queries per every single word. Factoring in
network latency and the fragility of web scraping, I do not believe that it is possible to realize a good user
experience.
**Can I still create cards without HTML formatting? The option for it is gone!**
* **Is it possible to use Yomichan with files saved locally on my computer with Chrome?**
Developing Yomichan is a constant balance between including useful features and keeping complexity at a minimum.
With the new user-editable card template system, it is possible to create text-only cards without having to double
the number field of templates in the extension itself. If you would like to stop HTML tags from being added to your
cards, simply copy the contents of the <a href="dl/fields.txt">text-only field template</a> into the template box on
the Anki settings page (make sure you have the *Show advanced options* checkbox ticked), making sure to replace the
existing values.
In order to use Yomichan with local files in Chrome, you must first tick the *Allow access to file URLs* checkbox
for Yomichan on the extensions page. Due to the restrictions placed on browser addons in the WebExtensions model, it
will likely never be possible to use Yomichan with PDF files.
**Will you add support for online dictionaries?**
* **Is it possible to delete individual dictionaries without purging the database?**
Online dictionaries will never be implemented because it is impossible to support them in a robust way. In order to
perform Japanese deinflection, Yomichan must execute dozens of database queries per every single word. Factoring in
network latency and the fragility of web scraping, I do not believe that it is possible to realize a good user
experience.
Although it is technically possible to purge specific dictionaries, due to the limitations of the underlying browser
IndexedDB system, this process is *extremely* slow. For example, it can take up to ten minutes to delete a single
moderately-sized term dictionary! Instead of including a borderline unusable feature in Yomichan, I have chosen to
disable dictionary deletion entirely.
**Is it possible to use Yomichan with files saved locally on my computer with Chrome?**
* **Why aren't EPWING dictionaries bundled with Yomichan?**
In order to use Yomichan with local files in Chrome, you must first tick the *Allow access to file URLs* checkbox
for Yomichan on the extensions page. Due to the restrictions placed on browser addons in the WebExtensions model, it
will likely never be possible to use Yomichan with PDF files.
The vast majority of EPWING dictionaries are proprietary, so unfortunately I am unable to legally include them in
this extension for copyright reasons.
**Is it possible to delete individual dictionaries without purging the database?**
* **When are you going to add support for $MYLANGUAGE?**
Although it is technically possible to purge specific dictionaries, due to the limitations of the underlying browser
IndexedDB system, this process is *extremely* slow. For example, it can take up to ten minutes to delete a single
moderately-sized term dictionary! Instead of including a borderline unusable feature in Yomichan, I have chosen to
disable dictionary deletion entirely.
Developing Yomichan requires a significant understanding of Japanese sentence structure and grammar. I have no time
to invest in learning yet another language; therefore other languages will not be supported. I will also not accept
pull request containing this functionality, as I will ultimately be the one maintaining your code.
**Why aren't EPWING dictionaries bundled with Yomichan?**
The vast majority of EPWING dictionaries are proprietary, so unfortunately I am unable to legally include them in
this extension for copyright reasons.
**When are you going to add support for $MYLANGUAGE?**
Developing Yomichan requires a significant understanding of Japanese sentence structure and grammar. I have no time
to invest in learning yet another language; therefore other languages will not be supported. I will also not accept
pull request containing this functionality, as I will ultimately be the one maintaining your code.
## Screenshots ##

View File

@ -29,9 +29,11 @@ async function apiTermsFind(text) {
const options = utilBackend().options;
const translator = utilBackend().translator;
const searcher = options.general.groupResults ?
translator.findTermsGrouped.bind(translator) :
translator.findTermsSplit.bind(translator);
const searcher = {
'merge': translator.findTermsMerged,
'split': translator.findTermsSplit,
'group': translator.findTermsGrouped
}[options.general.resultOutputMode].bind(translator);
const {definitions, length} = await searcher(
text,

View File

@ -140,8 +140,13 @@ async function audioInject(definition, fields, mode) {
}
try {
const url = await audioBuildUrl(definition, mode);
const filename = audioBuildFilename(definition);
let audioSourceDefinition = definition;
if (definition.hasOwnProperty('expressions')) {
audioSourceDefinition = definition.expressions[0];
}
const url = await audioBuildUrl(audioSourceDefinition, mode);
const filename = audioBuildFilename(audioSourceDefinition);
if (url && filename) {
definition.audio = {url, filename};

View File

@ -40,6 +40,9 @@ class Database {
kanjiMeta: '++,dictionary,character',
tagMeta: '++,dictionary,name'
});
this.db.version(4).stores({
terms: '++id,dictionary,expression,reading,sequence'
});
await this.db.open();
}
@ -68,12 +71,66 @@ class Database {
results.push({
expression: row.expression,
reading: row.reading,
tags: dictFieldSplit(row.tags),
definitionTags: dictFieldSplit(row.definitionTags || row.tags || ''),
termTags: dictFieldSplit(row.termTags || ''),
rules: dictFieldSplit(row.rules),
glossary: row.glossary,
score: row.score,
dictionary: row.dictionary,
id: row.id
id: row.id,
sequence: typeof row.sequence === 'undefined' ? -1 : row.sequence
});
}
});
return results;
}
async findTermsExact(term, reading, titles) {
if (!this.db) {
throw 'Database not initialized';
}
const results = [];
await this.db.terms.where('expression').equals(term).each(row => {
if (row.reading === reading && titles.includes(row.dictionary)) {
results.push({
expression: row.expression,
reading: row.reading,
definitionTags: dictFieldSplit(row.definitionTags || row.tags || ''),
termTags: dictFieldSplit(row.termTags || ''),
rules: dictFieldSplit(row.rules),
glossary: row.glossary,
score: row.score,
dictionary: row.dictionary,
id: row.id,
sequence: typeof row.sequence === 'undefined' ? -1 : row.sequence
});
}
});
return results;
}
async findTermsBySequence(sequence, mainDictionary) {
if (!this.db) {
throw 'Database not initialized';
}
const results = [];
await this.db.terms.where('sequence').equals(sequence).each(row => {
if (row.dictionary === mainDictionary) {
results.push({
expression: row.expression,
reading: row.reading,
definitionTags: dictFieldSplit(row.definitionTags || row.tags || ''),
termTags: dictFieldSplit(row.termTags || ''),
rules: dictFieldSplit(row.rules),
glossary: row.glossary,
score: row.score,
dictionary: row.dictionary,
id: row.id,
sequence: typeof row.sequence === 'undefined' ? -1 : row.sequence
});
}
});
@ -163,7 +220,7 @@ class Database {
return result;
}
async getTitles() {
async summarize() {
if (this.db) {
return this.db.dictionaries.toArray();
} else {
@ -177,7 +234,7 @@ class Database {
}
const indexDataLoaded = async summary => {
if (summary.version > 2) {
if (summary.version > 3) {
throw 'Unsupported dictionary version';
}
@ -196,11 +253,11 @@ class Database {
const rows = [];
if (summary.version === 1) {
for (const [expression, reading, tags, rules, score, ...glossary] of entries) {
for (const [expression, reading, definitionTags, rules, score, ...glossary] of entries) {
rows.push({
expression,
reading,
tags,
definitionTags,
rules,
score,
glossary,
@ -208,14 +265,16 @@ class Database {
});
}
} else {
for (const [expression, reading, tags, rules, score, glossary] of entries) {
for (const [expression, reading, definitionTags, rules, score, glossary, sequence, termTags] of entries) {
rows.push({
expression,
reading,
tags,
definitionTags,
rules,
score,
glossary,
sequence,
termTags,
dictionary: summary.title
});
}
@ -300,12 +359,13 @@ class Database {
}
const rows = [];
for (const [name, category, order, notes] of entries) {
for (const [name, category, order, notes, score] of entries) {
const row = dictTagSanitize({
name,
category,
order,
notes,
score,
dictionary: summary.title
});
@ -350,12 +410,11 @@ class Database {
const summary = {
title: index.title,
revision: index.revision,
sequenced: index.sequenced,
version: index.format || index.version
};
if (indexDataLoaded) {
await indexDataLoaded(summary);
}
await indexDataLoaded(summary);
const buildTermBankName = index => `term_bank_${index + 1}.json`;
const buildTermMetaBankName = index => `term_meta_bank_${index + 1}.json`;
@ -390,7 +449,7 @@ class Database {
const bank = [];
for (const name in index.tagMeta) {
const tag = index.tagMeta[name];
bank.push([name, tag.category, tag.order, tag.notes]);
bank.push([name, tag.category, tag.order, tag.notes, tag.score]);
}
tagDataLoaded(summary, bank, ++bankTotalCount, bankLoadedCount++);

View File

@ -89,7 +89,7 @@ function dictTermsSort(definitions, dictionaries=null) {
return 1;
}
return v2.expression.localeCompare(v1.expression);
return v2.expression.toString().localeCompare(v1.expression.toString());
});
}
@ -110,6 +110,33 @@ function dictTermsUndupe(definitions) {
return definitionsUnique;
}
function dictTermsCompressTags(definitions) {
let lastDictionary = '';
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 filterOutCategories = [];
if (lastDictionary === dictionary) {
filterOutCategories.push('dictionary');
} else {
lastDictionary = dictionary;
lastPartOfSpeech = '';
}
if (lastPartOfSpeech === partOfSpeech) {
filterOutCategories.push('partOfSpeech');
} else {
lastPartOfSpeech = partOfSpeech;
}
definition.definitionTags = definition.definitionTags.filter(tag => !filterOutCategories.includes(tag.category));
}
}
function dictTermsGroup(definitions, dictionaries) {
const groups = {};
for (const definition of definitions) {
@ -136,6 +163,7 @@ function dictTermsGroup(definitions, dictionaries) {
expression: firstDef.expression,
reading: firstDef.reading,
reasons: firstDef.reasons,
termTags: groupDefs[0].termTags,
score: groupDefs.reduce((p, v) => v.score > p ? v.score : p, Number.MIN_SAFE_INTEGER),
source: firstDef.source
});
@ -144,6 +172,116 @@ function dictTermsGroup(definitions, dictionaries) {
return dictTermsSort(results);
}
function dictTermsMergeBySequence(definitions, mainDictionary) {
const definitionsBySequence = {'-1': []};
for (const definition of definitions) {
if (mainDictionary === definition.dictionary && definition.sequence >= 0) {
if (!definitionsBySequence[definition.sequence]) {
definitionsBySequence[definition.sequence] = {
reasons: definition.reasons,
score: Number.MIN_SAFE_INTEGER,
expression: new Set(),
reading: new Set(),
expressions: new Map(),
source: definition.source,
dictionary: definition.dictionary,
definitions: []
};
}
const score = Math.max(definitionsBySequence[definition.sequence].score, definition.score);
definitionsBySequence[definition.sequence].score = score;
} else {
definitionsBySequence['-1'].push(definition);
}
}
return definitionsBySequence;
}
function dictTermsMergeByGloss(result, definitions, appendTo, mergedIndices) {
const definitionsByGloss = appendTo || {};
for (const [index, definition] of definitions.entries()) {
if (appendTo) {
let match = false;
for (const expression of result.expressions.keys()) {
if (definition.expression === expression) {
for (const reading of result.expressions.get(expression).keys()) {
if (definition.reading === reading) {
match = true;
break;
}
}
}
if (match) {
break;
}
}
if (!match) {
continue;
} else if (mergedIndices) {
mergedIndices.add(index);
}
}
const gloss = JSON.stringify(definition.glossary.concat(definition.dictionary));
if (!definitionsByGloss[gloss]) {
definitionsByGloss[gloss] = {
expression: new Set(),
reading: new Set(),
definitionTags: [],
glossary: definition.glossary,
source: result.source,
reasons: [],
score: definition.score,
id: definition.id,
dictionary: definition.dictionary
};
}
definitionsByGloss[gloss].expression.add(definition.expression);
definitionsByGloss[gloss].reading.add(definition.reading);
result.expression.add(definition.expression);
result.reading.add(definition.reading);
// result->expressions[ Expression1[ Reading1[ Tag1, Tag2 ] ], Expression2, ... ]
if (!result.expressions.has(definition.expression)) {
result.expressions.set(definition.expression, new Map());
}
if (!result.expressions.get(definition.expression).has(definition.reading)) {
result.expressions.get(definition.expression).set(definition.reading, new Set());
}
for (const tag of definition.definitionTags) {
if (!definitionsByGloss[gloss].definitionTags.find(existingTag => existingTag.name === tag.name)) {
definitionsByGloss[gloss].definitionTags.push(tag);
}
}
for (const tag of definition.termTags) {
result.expressions.get(definition.expression).get(definition.reading).add(tag);
}
}
for (const gloss in definitionsByGloss) {
const definition = definitionsByGloss[gloss];
definition.only = [];
if (!utilSetEqual(definition.expression, result.expression)) {
for (const expression of utilSetIntersection(definition.expression, result.expression)) {
definition.only.push(expression);
}
}
if (!utilSetEqual(definition.reading, result.reading)) {
for (const reading of utilSetIntersection(definition.reading, result.reading)) {
definition.only.push(reading);
}
}
}
return definitionsByGloss;
}
function dictTagBuildSource(name) {
return dictTagSanitize({name, category: 'dictionary', order: 100});
}
@ -153,6 +291,7 @@ function dictTagSanitize(tag) {
tag.category = tag.category || 'default';
tag.notes = tag.notes || '';
tag.order = tag.order || 0;
tag.score = tag.score || 0;
return tag;
}
@ -207,10 +346,12 @@ async function dictFieldFormat(field, definition, mode, options) {
const data = {
marker,
definition,
group: options.general.groupResults,
group: options.general.resultOutputMode === 'group',
merge: options.general.resultOutputMode === 'merge',
modeTermKanji: mode === 'term-kanji',
modeTermKana: mode === 'term-kana',
modeKanji: mode === 'kanji'
modeKanji: mode === 'kanji',
compactGlossaries: options.general.compactGlossaries
};
const html = await apiTemplateRender(options.anki.fieldTemplates, data, true);

View File

@ -19,12 +19,26 @@
function optionsFieldTemplates() {
return `
<style>
.expression-popular {
color: #0275d8;
}
.expression-rare {
color: #999;
}
</style>
{{#*inline "glossary-single"}}
{{~#unless brief~}}
{{~#if tags~}}<i>({{#each tags}}{{name}}{{#unless @last}}, {{/unless}}{{/each}})</i> {{/if~}}
{{~#if definitionTags~}}<i>({{#each definitionTags}}{{name}}{{#unless @last}}, {{/unless}}{{/each}})</i> {{/if~}}
{{~#if only~}}({{#each only}}{{{.}}}{{#unless @last}}, {{/unless}}{{/each}} only) {{/if~}}
{{~/unless~}}
{{~#if glossary.[1]~}}
<ul>{{#each glossary}}<li>{{#multiLine}}{{.}}{{/multiLine}}</li>{{/each}}</ul>
{{~#if compactGlossaries~}}
{{#each glossary}}{{#multiLine}}{{.}}{{/multiLine}}{{#unless @last}} | {{/unless}}{{/each}}
{{~else~}}
<ul>{{#each glossary}}<li>{{#multiLine}}{{.}}{{/multiLine}}</li>{{/each}}</ul>
{{~/if~}}
{{~else~}}
{{~#multiLine}}{{glossary.[0]}}{{/multiLine~}}
{{~/if~}}
@ -41,23 +55,56 @@ function optionsFieldTemplates() {
{{/inline}}
{{#*inline "expression"}}
{{~#if modeTermKana~}}
{{~#if definition.reading~}}
{{definition.reading}}
{{~#if merge~}}
{{~#if modeTermKana~}}
{{~#each definition.reading~}}
{{{.}}}
{{~#unless @last}}{{/unless~}}
{{~else~}}
{{~#each definition.expression~}}
{{{.}}}
{{~#unless @last}}{{/unless~}}
{{~/each~}}
{{~/each~}}
{{~else~}}
{{~#each definition.expression~}}
{{{.}}}
{{~#unless @last}}{{/unless~}}
{{~/each~}}
{{~/if~}}
{{~else~}}
{{~#if modeTermKana~}}
{{~#if definition.reading~}}
{{definition.reading}}
{{~else~}}
{{definition.expression}}
{{~/if~}}
{{~else~}}
{{definition.expression}}
{{~/if~}}
{{~else~}}
{{definition.expression}}
{{~/if~}}
{{/inline}}
{{#*inline "furigana"}}
{{#furigana}}{{{definition}}}{{/furigana}}
{{~#if merge~}}
{{~#each definition.expressions~}}
<span class="expression-{{termFrequency}}">{{~#furigana}}{{{.}}}{{/furigana~}}</span>
{{~#unless @last}}{{/unless~}}
{{~/each~}}
{{~else~}}
{{#furigana}}{{{definition}}}{{/furigana}}
{{~/if~}}
{{/inline}}
{{#*inline "furigana-plain"}}
{{#furiganaPlain}}{{{definition}}}{{/furiganaPlain}}
{{~#if merge~}}
{{~#each definition.expressions~}}
<span class="expression-{{termFrequency}}">{{~#furiganaPlain}}{{{.}}}{{/furiganaPlain~}}</span>
{{~#unless @last}}{{/unless~}}
{{~/each~}}
{{~else~}}
{{#furiganaPlain}}{{{definition}}}{{/furiganaPlain}}
{{~/if~}}
{{/inline}}
{{#*inline "glossary"}}
@ -71,12 +118,18 @@ function optionsFieldTemplates() {
{{~else~}}
{{~#if group~}}
{{~#if definition.definitions.[1]~}}
<ol>{{#each definition.definitions}}<li>{{> glossary-single brief=../brief}}</li>{{/each}}</ol>
<ol>{{#each definition.definitions}}<li>{{> glossary-single brief=../brief compactGlossaries=../compactGlossaries}}</li>{{/each}}</ol>
{{~else~}}
{{~> glossary-single definition.definitions.[0] brief=brief~}}
{{~> glossary-single definition.definitions.[0] brief=brief compactGlossaries=compactGlossaries~}}
{{~/if~}}
{{~else if merge~}}
{{~#if definition.definitions.[1]~}}
<ol>{{#each definition.definitions}}<li>{{> glossary-single brief=../brief compactGlossaries=../compactGlossaries}}</li>{{/each}}</ol>
{{~else~}}
{{~> glossary-single definition.definitions.[0] brief=brief compactGlossaries=compactGlossaries~}}
{{~/if~}}
{{~else~}}
{{~> glossary-single definition brief=brief~}}
{{~> glossary-single definition brief=brief compactGlossaries=compactGlossaries~}}
{{~/if~}}
{{~/if~}}
</div>
@ -95,7 +148,16 @@ function optionsFieldTemplates() {
{{/inline}}
{{#*inline "reading"}}
{{~#unless modeTermKana}}{{definition.reading}}{{/unless~}}
{{~#unless modeTermKana~}}
{{~#if merge~}}
{{~#each definition.reading~}}
{{{.}}}
{{~#unless @last}}{{/unless~}}
{{~/each~}}
{{~else~}}
{{~definition.reading~}}
{{~/if~}}
{{~/unless~}}
{{/inline}}
{{#*inline "sentence"}}
@ -115,7 +177,7 @@ function optionsFieldTemplates() {
{{/inline}}
{{#*inline "tags"}}
{{~#each definition.tags}}{{name}}{{#unless @last}}, {{/unless}}{{/each~}}
{{~#each definition.definitionTags}}{{name}}{{#unless @last}}, {{/unless}}{{/each~}}
{{/inline}}
{{#*inline "url"}}
@ -132,14 +194,17 @@ function optionsSetDefaults(options) {
enable: true,
audioSource: 'jpod101',
audioVolume: 100,
groupResults: true,
resultOutputMode: 'group',
debugInfo: false,
maxResults: 32,
showAdvanced: false,
popupWidth: 400,
popupHeight: 250,
popupOffset: 10,
showGuide: true
showGuide: true,
compactTags: false,
compactGlossaries: false,
mainDictionary: ''
},
scanning: {
@ -205,6 +270,24 @@ function optionsVersion(options) {
} else {
options.scanning.modifier = 'none';
}
},
() => {
if (options.general.groupResults) {
options.general.resultOutputMode = 'group';
} else {
options.general.resultOutputMode = 'split';
}
if (utilStringHashCode(options.anki.fieldTemplates) !== -805327496) { // a3c8508031a1073629803d0616a2ee416cd3cccc
options.anki.fieldTemplates = `
{{#if merge}}
${optionsFieldTemplates()}
{{else}}
${options.anki.fieldTemplates}
{{/if}}
`.trim();
} else {
options.anki.fieldTemplates = optionsFieldTemplates();
}
}
];

View File

@ -22,9 +22,11 @@ async function formRead() {
const optionsNew = $.extend(true, {}, optionsOld);
optionsNew.general.showGuide = $('#show-usage-guide').prop('checked');
optionsNew.general.compactTags = $('#compact-tags').prop('checked');
optionsNew.general.compactGlossaries = $('#compact-glossaries').prop('checked');
optionsNew.general.resultOutputMode = $('#result-output-mode').val();
optionsNew.general.audioSource = $('#audio-playback-source').val();
optionsNew.general.audioVolume = parseFloat($('#audio-playback-volume').val());
optionsNew.general.groupResults = $('#group-terms-results').prop('checked');
optionsNew.general.debugInfo = $('#show-debug-info').prop('checked');
optionsNew.general.showAdvanced = $('#show-advanced-options').prop('checked');
optionsNew.general.maxResults = parseInt($('#max-displayed-results').val(), 10);
@ -55,12 +57,14 @@ async function formRead() {
optionsNew.anki.kanji.fields = ankiFieldsToDict($('#kanji .anki-field-value'));
}
optionsNew.general.mainDictionary = $('#dict-main').val();
$('.dict-group').each((index, element) => {
const dictionary = $(element);
const title = dictionary.data('title');
const priority = parseInt(dictionary.find('.dict-priority').val(), 10);
const enabled = dictionary.find('.dict-enabled').prop('checked');
optionsNew.dictionaries[title] = {priority, enabled};
optionsNew.dictionaries[dictionary.data('title')] = {
priority: parseInt(dictionary.find('.dict-priority').val(), 10),
enabled: dictionary.find('.dict-enabled').prop('checked'),
allowSecondarySearches: dictionary.find('.dict-allow-secondary-searches').prop('checked')
};
});
return {optionsNew, optionsOld};
@ -81,6 +85,13 @@ function formUpdateVisibility(options) {
advanced.hide();
}
const mainGroup = $('#dict-main-group');
if (options.general.resultOutputMode === 'merge') {
mainGroup.show();
} else {
mainGroup.hide();
}
const debug = $('#debug');
if (options.general.debugInfo) {
const temp = utilIsolate(options);
@ -93,17 +104,33 @@ function formUpdateVisibility(options) {
}
}
async function onFormOptionsChanged(e) {
try {
if (!e.originalEvent && !e.isTrigger) {
return;
async function formMainDictionaryOptionsPopulate(options) {
const select = $('#dict-main').empty();
select.append($('<option class="text-muted" value="">Not selected</option>'));
let mainDictionary = '';
for (const dictRow of await utilDatabaseSummarize()) {
if (dictRow.sequenced) {
select.append($(`<option value="${dictRow.title}">${dictRow.title}</option>`));
if (dictRow.title === options.general.mainDictionary) {
mainDictionary = dictRow.title;
}
}
}
const {optionsNew, optionsOld} = await formRead();
await optionsSave(optionsNew);
select.val(mainDictionary);
}
formUpdateVisibility(optionsNew);
async function onFormOptionsChanged(e) {
if (!e.originalEvent && !e.isTrigger) {
return;
}
const {optionsNew, optionsOld} = await formRead();
await optionsSave(optionsNew);
formUpdateVisibility(optionsNew);
try {
const ankiUpdated =
optionsNew.anki.enable !== optionsOld.anki.enable ||
optionsNew.anki.server !== optionsOld.anki.server;
@ -124,9 +151,11 @@ async function onReady() {
const options = await optionsLoad();
$('#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);
$('#audio-playback-source').val(options.general.audioSource);
$('#audio-playback-volume').val(options.general.audioVolume);
$('#group-terms-results').prop('checked', options.general.groupResults);
$('#show-debug-info').prop('checked', options.general.debugInfo);
$('#show-advanced-options').prop('checked', options.general.showAdvanced);
$('#max-displayed-results').val(options.general.maxResults);
@ -156,6 +185,7 @@ async function onReady() {
try {
await dictionaryGroupsPopulate(options);
await formMainDictionaryOptionsPopulate(options);
} catch (e) {
dictionaryErrorShow(e);
}
@ -241,19 +271,26 @@ async function dictionaryGroupsPopulate(options) {
const dictGroups = $('#dict-groups').empty();
const dictWarning = $('#dict-warning').hide();
const dictRows = await utilDatabaseGetTitles();
const dictRows = await utilDatabaseSummarize();
if (dictRows.length === 0) {
dictWarning.show();
}
for (const dictRow of dictRowsSort(dictRows, options)) {
const dictOptions = options.dictionaries[dictRow.title] || {enabled: false, priority: 0};
const dictOptions = options.dictionaries[dictRow.title] || {
enabled: false,
priority: 0,
allowSecondarySearches: false
};
const dictHtml = await apiTemplateRender('dictionary.html', {
enabled: dictOptions.enabled,
priority: dictOptions.priority,
allowSecondarySearches: dictOptions.allowSecondarySearches,
title: dictRow.title,
version: dictRow.version,
revision: dictRow.revision,
priority: dictOptions.priority,
enabled: dictOptions.enabled
outdated: dictRow.version < 3
});
dictGroups.append($(dictHtml));
@ -261,7 +298,7 @@ async function dictionaryGroupsPopulate(options) {
formUpdateVisibility(options);
$('.dict-enabled, .dict-priority').change(e => {
$('.dict-enabled, .dict-priority, .dict-allow-secondary-searches').change(e => {
dictionaryGroupsSort();
onFormOptionsChanged(e);
});
@ -270,7 +307,7 @@ async function dictionaryGroupsPopulate(options) {
async function onDictionaryPurge(e) {
e.preventDefault();
const dictControls = $('#dict-importer, #dict-groups').hide();
const dictControls = $('#dict-importer, #dict-groups, #dict-main-group').hide();
const dictProgress = $('#dict-purge').show();
try {
@ -280,9 +317,11 @@ async function onDictionaryPurge(e) {
await utilDatabasePurge();
const options = await optionsLoad();
options.dictionaries = {};
options.general.mainDictionary = '';
await optionsSave(options);
await dictionaryGroupsPopulate(options);
await formMainDictionaryOptionsPopulate(options);
} catch (e) {
dictionaryErrorShow(e);
} finally {
@ -308,10 +347,14 @@ async function onDictionaryImport(e) {
const options = await optionsLoad();
const summary = await utilDatabaseImport(e.target.files[0], updateProgress);
options.dictionaries[summary.title] = {enabled: true, priority: 0};
options.dictionaries[summary.title] = {enabled: true, priority: 0, allowSecondarySearches: false};
if (summary.sequenced && options.general.mainDictionary === '') {
options.general.mainDictionary = summary.title;
}
await optionsSave(options);
await dictionaryGroupsPopulate(options);
await formMainDictionaryOptionsPopulate(options);
} catch (e) {
dictionaryErrorShow(e);
} finally {

View File

@ -1,6 +1,8 @@
(function() {
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
templates['dictionary.html'] = template({"1":function(container,depth0,helpers,partials,data) {
return " <p class=\"text-warning\">This dictionary is outdated and may not support new extension features; please import the latest version.</p>\n";
},"3":function(container,depth0,helpers,partials,data) {
return "checked";
},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
@ -11,9 +13,13 @@ templates['dictionary.html'] = template({"1":function(container,depth0,helpers,p
+ alias4(((helper = (helper = helpers.title || (depth0 != null ? depth0.title : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"title","hash":{},"data":data}) : helper)))
+ " <small>rev."
+ alias4(((helper = (helper = helpers.revision || (depth0 != null ? depth0.revision : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"revision","hash":{},"data":data}) : helper)))
+ "</small></h4>\n\n <div class=\"checkbox\">\n <label><input type=\"checkbox\" class=\"dict-enabled\" "
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.enabled : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "> Enable search</label>\n </div>\n <div class=\"form-group options-advanced\">\n <label for=\"dict-"
+ "</small></h4>\n"
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.outdated : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n <div class=\"checkbox\">\n <label><input type=\"checkbox\" class=\"dict-enabled\" "
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.enabled : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "> Enable search</label>\n </div>\n <div class=\"checkbox options-advanced\">\n <label><input type=\"checkbox\" class=\"dict-allow-secondary-searches\" "
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.allowSecondarySearches : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "> Allow secondary searches</label>\n </div>\n <div class=\"form-group options-advanced\">\n <label for=\"dict-"
+ alias4(((helper = (helper = helpers.title || (depth0 != null ? depth0.title : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"title","hash":{},"data":data}) : helper)))
+ "\">Result priority</label>\n <input type=\"number\" value=\""
+ alias4(((helper = (helper = helpers.priority || (depth0 != null ? depth0.priority : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"priority","hash":{},"data":data}) : helper)))
@ -204,15 +210,20 @@ templates['model.html'] = template({"1":function(container,depth0,helpers,partia
templates['terms.html'] = template({"1":function(container,depth0,helpers,partials,data) {
var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
return ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.tags : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"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(5, data, 0),"inverse":container.program(9, data, 0),"data":data})) != null ? stack1 : "");
return ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.definitionTags : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.only : depth0),{"name":"if","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"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(11, data, 0),"inverse":container.program(17, data, 0),"data":data})) != null ? stack1 : "");
},"2":function(container,depth0,helpers,partials,data) {
var stack1;
var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
return "<div>\n"
+ ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.tags : depth0),{"name":"each","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
return "<div "
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.compactGlossaries : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ">\n"
+ ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.definitionTags : depth0),{"name":"each","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</div>\n";
},"3":function(container,depth0,helpers,partials,data) {
return "class=\"compact-info\"";
},"5":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-"
@ -222,88 +233,192 @@ 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";
},"5":function(container,depth0,helpers,partials,data) {
},"7":function(container,depth0,helpers,partials,data) {
var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
return "<div "
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.compactGlossaries : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ">\n ("
+ ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.only : depth0),{"name":"each","hash":{},"fn":container.program(8, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " only)\n</div>\n";
},"8":function(container,depth0,helpers,partials,data) {
var stack1;
return "<ul>\n"
+ ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
return ((stack1 = container.lambda(depth0, depth0)) != null ? stack1 : "")
+ ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.last),{"name":"unless","hash":{},"fn":container.program(9, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n";
},"9":function(container,depth0,helpers,partials,data) {
return ", ";
},"11":function(container,depth0,helpers,partials,data) {
var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
return "<ul "
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.compactGlossaries : depth0),{"name":"if","hash":{},"fn":container.program(12, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ">\n"
+ ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(14, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</ul>\n";
},"6":function(container,depth0,helpers,partials,data) {
},"12":function(container,depth0,helpers,partials,data) {
return "class=\"compact-glossary\"";
},"14":function(container,depth0,helpers,partials,data) {
var stack1, helper, options, buffer =
" <li><span class=\"glossary-item\">";
stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(15, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
if (!helpers.multiLine) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
if (stack1 != null) { buffer += stack1; }
return buffer + "</span></li>\n";
},"7":function(container,depth0,helpers,partials,data) {
},"15":function(container,depth0,helpers,partials,data) {
return container.escapeExpression(container.lambda(depth0, depth0));
},"9":function(container,depth0,helpers,partials,data) {
var stack1, helper, options, buffer =
"<div class=\"glossary-item\">";
stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
},"17":function(container,depth0,helpers,partials,data) {
var stack1, helper, options, alias1=depth0 != null ? depth0 : (container.nullContext || {}), buffer =
"<div class=\"glossary-item "
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.compactGlossaries : depth0),{"name":"if","hash":{},"fn":container.program(18, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\">";
stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(20, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(alias1,options) : helper));
if (!helpers.multiLine) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
if (stack1 != null) { buffer += stack1; }
return buffer + "</div>\n";
},"10":function(container,depth0,helpers,partials,data) {
var stack1;
return container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["0"] : stack1), depth0));
},"12":function(container,depth0,helpers,partials,data) {
var stack1, helper, options, alias1=depth0 != null ? depth0 : (container.nullContext || {}), buffer =
"<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(13, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.playback : depth0),{"name":"if","hash":{},"fn":container.program(15, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " <img src=\"/mixed/img/entry-current.png\" class=\"current\" title=\"Current entry (Alt + Up/Down/Home/End/PgUp/PgDn)\" alt>\n </div>\n\n <div class=\"expression\">";
stack1 = ((helper = (helper = helpers.kanjiLinks || (depth0 != null ? depth0.kanjiLinks : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"kanjiLinks","hash":{},"fn":container.program(17, 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\n"
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.reasons : depth0),{"name":"if","hash":{},"fn":container.program(20, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n"
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.frequencies : depth0),{"name":"if","hash":{},"fn":container.program(24, data, 0),"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(27, data, 0),"inverse":container.program(33, data, 0),"data":data})) != null ? stack1 : "")
+ " </div>\n\n"
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.debug : depth0),{"name":"if","hash":{},"fn":container.program(35, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</div>\n";
},"13":function(container,depth0,helpers,partials,data) {
return " <a href=\"#\" class=\"action-view-note pending disabled\"><img src=\"/mixed/img/view-note.png\" 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.png\" 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.png\" title=\"Add reading (Alt + R)\" alt></a>\n";
},"15":function(container,depth0,helpers,partials,data) {
return " <a href=\"#\" class=\"action-play-audio\"><img src=\"/mixed/img/play-audio.png\" title=\"Play audio (Alt + P)\" alt></a>\n";
},"17":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(18, 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 ''; }
},"18":function(container,depth0,helpers,partials,data) {
var stack1;
return ((stack1 = container.lambda(depth0, depth0)) != null ? stack1 : "");
return "compact-glossary";
},"20":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(21, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
return container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["0"] : stack1), depth0));
},"22":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
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(23, 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(25, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " <img src=\"/mixed/img/entry-current.png\" 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(28, data, 0, blockParams, depths),"inverse":container.program(43, 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(47, 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(51, 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(54, data, 0, blockParams, depths),"inverse":container.program(60, 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(63, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</div>\n";
},"23":function(container,depth0,helpers,partials,data) {
return " <a href=\"#\" class=\"action-view-note pending disabled\"><img src=\"/mixed/img/view-note.png\" 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.png\" 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.png\" title=\"Add reading (Alt + R)\" alt></a>\n";
},"25":function(container,depth0,helpers,partials,data) {
var stack1;
return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.playback : depth0),{"name":"if","hash":{},"fn":container.program(26, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
},"26":function(container,depth0,helpers,partials,data) {
return " <a href=\"#\" class=\"action-play-audio\"><img src=\"/mixed/img/play-audio.png\" title=\"Play audio (Alt + P)\" alt></a>\n";
},"28":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(29, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
},"29":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\">\n <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(30, 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>\n <div class=\"peek-wrapper\">"
+ ((stack1 = helpers["if"].call(alias1,(depths[1] != null ? depths[1].playback : depths[1]),{"name":"if","hash":{},"fn":container.program(33, 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(35, 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(38, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</div>\n <span class=\""
+ ((stack1 = helpers["if"].call(alias1,(data && data.last),{"name":"if","hash":{},"fn":container.program(41, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\">、</span>\n </div>";
},"30":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(31, 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 ''; }
},"31":function(container,depth0,helpers,partials,data) {
var stack1;
return ((stack1 = container.lambda(depth0, depth0)) != null ? stack1 : "");
},"33":function(container,depth0,helpers,partials,data) {
return "<a href=\"#\" class=\"action-play-audio\"><img src=\"/mixed/img/play-audio.png\" title=\"Play audio\" alt></a>";
},"35":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(36, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</div>";
},"36":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-"
+ alias4(((helper = (helper = helpers.category || (depth0 != null ? depth0.category : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"category","hash":{},"data":data}) : helper)))
+ "\" title=\""
+ alias4(((helper = (helper = helpers.notes || (depth0 != null ? depth0.notes : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"notes","hash":{},"data":data}) : helper)))
+ "\">"
+ 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";
},"38":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(39, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</div>";
},"39":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\">"
+ alias4(((helper = (helper = helpers.dictionary || (depth0 != null ? depth0.dictionary : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"dictionary","hash":{},"data":data}) : helper)))
+ ":"
+ 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";
},"41":function(container,depth0,helpers,partials,data) {
return "invisible";
},"43":function(container,depth0,helpers,partials,data) {
var stack1, helper, options, alias1=depth0 != null ? depth0 : (container.nullContext || {}), buffer =
" <div class=\"expression\">";
stack1 = ((helper = (helper = helpers.kanjiLinks || (depth0 != null ? depth0.kanjiLinks : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"kanjiLinks","hash":{},"fn":container.program(30, 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(44, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
},"44":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(45, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " </div>\n";
},"21":function(container,depth0,helpers,partials,data) {
},"45":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-"
+ alias4(((helper = (helper = helpers.category || (depth0 != null ? depth0.category : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"category","hash":{},"data":data}) : helper)))
+ "\" title=\""
+ alias4(((helper = (helper = helpers.notes || (depth0 != null ? depth0.notes : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"notes","hash":{},"data":data}) : helper)))
+ "\">"
+ 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";
},"47":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(48, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " </div>\n";
},"48":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(22, 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(49, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n";
},"22":function(container,depth0,helpers,partials,data) {
},"49":function(container,depth0,helpers,partials,data) {
return "&laquo;";
},"24":function(container,depth0,helpers,partials,data) {
},"51":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(25, 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(52, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " </div>\n";
},"25":function(container,depth0,helpers,partials,data) {
},"52":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\">"
@ -311,62 +426,67 @@ 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";
},"27":function(container,depth0,helpers,partials,data) {
},"54":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(28, data, 0),"inverse":container.program(31, data, 0),"data":data})) != null ? stack1 : "");
},"28":function(container,depth0,helpers,partials,data) {
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(55, data, 0, blockParams, depths),"inverse":container.program(58, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
},"55":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(29, data, 0),"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(56, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " </ol>\n";
},"29":function(container,depth0,helpers,partials,data) {
},"56":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1;
return " <li>"
+ ((stack1 = container.invokePartial(partials.definition,depth0,{"name":"definition","data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
+ ((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";
},"31":function(container,depth0,helpers,partials,data) {
},"58":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","data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
},"33":function(container,depth0,helpers,partials,data) {
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 : "");
},"60":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1;
return ((stack1 = container.invokePartial(partials.definition,depth0,{"name":"definition","data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
},"35":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(54, data, 0, blockParams, depths),"inverse":container.program(61, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
},"61":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 : "")
+ " ";
},"63":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(18, 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(31, 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";
},"37":function(container,depth0,helpers,partials,data,blockParams,depths) {
},"65":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(38, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
},"38":function(container,depth0,helpers,partials,data,blockParams,depths) {
return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(66, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
},"66":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(39, 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(67, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n"
+ ((stack1 = container.invokePartial(partials.term,depth0,{"name":"term","hash":{"playback":(depths[1] != null ? depths[1].playback : depths[1]),"addable":(depths[1] != null ? depths[1].addable : 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 : "");
},"39":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 : "");
},"67":function(container,depth0,helpers,partials,data) {
return "<hr>";
},"41":function(container,depth0,helpers,partials,data) {
},"69":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(37, data, 0, blockParams, depths),"inverse":container.program(41, 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(65, data, 0, blockParams, depths),"inverse":container.program(69, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
},"main_d": function(fn, props, container, depth0, data, blockParams, depths) {
var decorators = container.decorators;
fn = decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(1, data, 0, blockParams, depths),"inverse":container.noop,"args":["definition"],"data":data}) || fn;
fn = decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(12, data, 0, blockParams, depths),"inverse":container.noop,"args":["term"],"data":data}) || fn;
fn = decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(22, data, 0, blockParams, depths),"inverse":container.noop,"args":["term"],"data":data}) || fn;
return fn;
}

View File

@ -37,6 +37,7 @@ class Translator {
}
async findTermsGrouped(text, dictionaries, alphanumeric) {
const options = await apiOptionsGet();
const titles = Object.keys(dictionaries);
const {length, definitions} = await this.findTerms(text, dictionaries, alphanumeric);
@ -45,9 +46,118 @@ class Translator {
await this.buildTermFrequencies(definition, titles);
}
if (options.general.compactTags) {
for (const definition of definitionsGrouped) {
dictTermsCompressTags(definition.definitions);
}
}
return {length, definitions: definitionsGrouped};
}
async findTermsMerged(text, dictionaries, alphanumeric) {
const options = await apiOptionsGet();
const secondarySearchTitles = Object.keys(options.dictionaries).filter(dict => options.dictionaries[dict].allowSecondarySearches);
const titles = Object.keys(dictionaries);
const {length, definitions} = await this.findTerms(text, dictionaries, alphanumeric);
const definitionsBySequence = dictTermsMergeBySequence(definitions, options.general.mainDictionary);
const definitionsMerged = [];
const mergedByTermIndices = new Set();
for (const sequence in definitionsBySequence) {
if (sequence < 0) {
continue;
}
const result = definitionsBySequence[sequence];
const rawDefinitionsBySequence = await this.database.findTermsBySequence(Number(sequence), options.general.mainDictionary);
for (const definition of rawDefinitionsBySequence) {
const tags = await this.expandTags(definition.definitionTags, definition.dictionary);
tags.push(dictTagBuildSource(definition.dictionary));
definition.definitionTags = tags;
}
const definitionsByGloss = dictTermsMergeByGloss(result, rawDefinitionsBySequence);
const secondarySearchResults = [];
if (secondarySearchTitles.length > 0) {
for (const expression of result.expressions.keys()) {
if (expression === text) {
continue;
}
for (const reading of result.expressions.get(expression).keys()) {
for (const definition of await this.database.findTermsExact(expression, reading, secondarySearchTitles)) {
const tags = await this.expandTags(definition.definitionTags, definition.dictionary);
tags.push(dictTagBuildSource(definition.dictionary));
definition.definitionTags = tags;
secondarySearchResults.push(definition);
}
}
}
}
dictTermsMergeByGloss(result, definitionsBySequence['-1'].concat(secondarySearchResults), definitionsByGloss, mergedByTermIndices);
for (const gloss in definitionsByGloss) {
const definition = definitionsByGloss[gloss];
dictTagsSort(definition.definitionTags);
result.definitions.push(definition);
}
dictTermsSort(result.definitions, dictionaries);
const expressions = [];
for (const expression of result.expressions.keys()) {
for (const reading of result.expressions.get(expression).keys()) {
const tags = await this.expandTags(result.expressions.get(expression).get(reading), result.dictionary);
expressions.push({
expression: expression,
reading: reading,
termTags: dictTagsSort(tags),
termFrequency: (score => {
if (score > 0) {
return 'popular';
} else if (score < 0) {
return 'rare';
} else {
return 'normal';
}
})(tags.map(tag => tag.score).reduce((p, v) => p + v, 0))
});
}
}
result.expressions = expressions;
result.expression = Array.from(result.expression);
result.reading = Array.from(result.reading);
definitionsMerged.push(result);
}
const strayDefinitions = definitionsBySequence['-1'].filter((definition, index) => !mergedByTermIndices.has(index));
for (const groupedDefinition of dictTermsGroup(strayDefinitions, dictionaries)) {
groupedDefinition.expressions = [{expression: groupedDefinition.expression, reading: groupedDefinition.reading}];
definitionsMerged.push(groupedDefinition);
}
for (const definition of definitionsMerged) {
await this.buildTermFrequencies(definition, titles);
}
if (options.general.compactTags) {
for (const definition of definitionsMerged) {
dictTermsCompressTags(definition.definitions);
}
}
return {length, definitions: dictTermsSort(definitionsMerged)};
}
async findTermsSplit(text, dictionaries, alphanumeric) {
const titles = Object.keys(dictionaries);
const {length, definitions} = await this.findTerms(text, dictionaries, alphanumeric);
@ -78,8 +188,9 @@ class Translator {
let definitions = [];
for (const deinflection of deinflections) {
for (const definition of deinflection.definitions) {
const tags = await this.expandTags(definition.tags, definition.dictionary);
tags.push(dictTagBuildSource(definition.dictionary));
const definitionTags = await this.expandTags(definition.definitionTags, definition.dictionary);
definitionTags.push(dictTagBuildSource(definition.dictionary));
const termTags = await this.expandTags(definition.termTags, definition.dictionary);
definitions.push({
source: deinflection.source,
@ -90,7 +201,9 @@ class Translator {
expression: definition.expression,
reading: definition.reading,
glossary: definition.glossary,
tags: dictTagsSort(tags)
definitionTags: dictTagsSort(definitionTags),
termTags: dictTagsSort(termTags),
sequence: definition.sequence
});
}
}
@ -158,14 +271,23 @@ class Translator {
}
async buildTermFrequencies(definition, titles) {
definition.frequencies = [];
for (const meta of await this.database.findTermMeta(definition.expression, titles)) {
if (meta.mode === 'freq') {
definition.frequencies.push({
expression: meta.expression,
frequency: meta.data,
dictionary: meta.dictionary
});
let terms = [];
if (definition.expressions) {
terms = terms.concat(definition.expressions);
} else {
terms.push(definition);
}
for (const term of terms) {
term.frequencies = [];
for (const meta of await this.database.findTermMeta(term.expression, titles)) {
if (meta.mode === 'freq') {
term.frequencies.push({
expression: meta.expression,
frequency: meta.data,
dictionary: meta.dictionary
});
}
}
}
}

View File

@ -26,6 +26,43 @@ function utilIsolate(data) {
return JSON.parse(JSON.stringify(data));
}
function utilSetEqual(setA, setB) {
if (setA.size !== setB.size) {
return false;
}
for (const value of setA) {
if (!setB.has(value)) {
return false;
}
}
return true;
}
function utilSetIntersection(setA, setB) {
return new Set(
[...setA].filter(value => setB.has(value))
);
}
function utilSetDifference(setA, setB) {
return new Set(
[...setA].filter(value => !setB.has(value))
);
}
function utilStringHashCode(string) {
let hashCode = 0;
for (let i = 0, charCode = string.charCodeAt(i); i < string.length; charCode = string.charCodeAt(++i)) {
hashCode = ((hashCode << 5) - hashCode) + charCode;
hashCode |= 0;
}
return hashCode;
}
function utilBackend() {
return chrome.extension.getBackgroundPage().yomichan_backend;
}
@ -38,12 +75,12 @@ function utilAnkiGetDeckNames() {
return utilBackend().anki.getDeckNames();
}
function utilAnkiGetModelFieldNames(modelName) {
return utilBackend().anki.getModelFieldNames(modelName);
function utilDatabaseSummarize() {
return utilBackend().translator.database.summarize();
}
function utilDatabaseGetTitles() {
return utilBackend().translator.database.getTitles();
function utilAnkiGetModelFieldNames(modelName) {
return utilBackend().anki.getModelFieldNames(modelName);
}
function utilDatabasePurge() {

View File

@ -36,7 +36,11 @@
</div>
<div class="checkbox">
<label><input type="checkbox" id="group-terms-results"> Group term results</label>
<label><input type="checkbox" id="compact-tags"> Compact tags</label>
</div>
<div class="checkbox">
<label><input type="checkbox" id="compact-glossaries"> Compact glossaries</label>
</div>
<div class="checkbox">
@ -47,6 +51,15 @@
<label><input type="checkbox" id="show-debug-info"> Show debug information</label>
</div>
<div class="form-group">
<label for="result-output-mode">Result grouping</label>
<select class="form-control" id="result-output-mode">
<option value="group">Group results by term-reading pairs</option>
<option value="merge">Group results by main dictionary entry</option>
<option value="split">Split definitions to their own results</option>
</select>
</div>
<div class="form-group">
<label for="audio-playback-source">Audio playback source</label>
<select class="form-control" id="audio-playback-source">
@ -132,10 +145,14 @@
or you can simply <a href="#" id="dict-purge-link">purge the database</a> to delete everything.
</p>
<p class="help-block">
Please visit the <a href="https://foosoft.net/projects/yomichan" target="_blank">Yomichan</a> homepage to download free
dictionaries that you can use with this extension.
Deleting individual dictionaries is not currently feasible due to limitations of browser database technology.
</p>
<div class="form-group" id="dict-main-group">
<label for="dict-main">Main dictionary for merged mode</label>
<select class="form-control" id="dict-main"></select>
</div>
<div class="text-danger" id="dict-purge">Dictionary data is being purged, please be patient...</div>
<div class="alert alert-warning" id="dict-warning">No dictionaries have been installed</div>
<div class="alert alert-danger" id="dict-error"></div>
@ -150,6 +167,11 @@
</div>
<div id="dict-importer">
<p class="help-block">
Select a dictionary to import for use below. Please visit the Yomichan homepage to
<a href="https://foosoft.net/projects/yomichan" target="_blank">download free dictionaries</a>
for use with this extension and to learn about importing proprietary EPWING dictionaries.
</p>
<input type="file" id="dict-file">
</div>
</div>

View File

@ -76,7 +76,7 @@ function docRangeFromPoint(point) {
if (!document.caretRangeFromPoint) {
document.caretRangeFromPoint = (x, y) => {
const position = document.caretPositionFromPoint(x,y);
if (position && position.offsetNode) {
if (position && position.offsetNode && position.offsetNode.nodeType === Node.TEXT_NODE) {
const range = document.createRange();
range.setStart(position.offsetNode, position.offset);
range.setEnd(position.offsetNode, position.offset);
@ -86,7 +86,7 @@ function docRangeFromPoint(point) {
}
const range = document.caretRangeFromPoint(point.x, point.y);
if (range && range.startContainer.nodeType === 3 && range.endContainer.nodeType === 3) {
if (range) {
return new TextSourceRange(range);
}
}

View File

@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "Yomichan",
"version": "1.4.0",
"version": "1.5.0",
"description": "Japanese dictionary with Anki integration",
"icons": {"16": "mixed/img/icon16.png", "48": "mixed/img/icon48.png", "128": "mixed/img/icon128.png"},

View File

@ -46,6 +46,10 @@ hr {
display: none;
}
.invisible {
visibility: hidden;
}
/*
* Entries
@ -88,6 +92,10 @@ hr {
background-color: #5cb85c;
}
.tag-partOfSpeech {
background-color: #565656;
}
.actions .disabled {
pointer-events: none;
cursor: default;
@ -118,21 +126,79 @@ hr {
font-size: 24px;
}
.expression a {
.expression .kanji-link {
border-bottom: 1px #777 dashed;
color: #333;
text-decoration: none;
}
.expression-popular, .expression-popular .kanji-link {
color: #0275d8;
}
.expression-rare, .expression-rare .kanji-link {
color: #999;
}
.expression .peek-wrapper {
font-size: 14px;
white-space: nowrap;
display: inline-block;
position: relative;
width: 0px;
height: 0px;
visibility: hidden;
}
.expression .peek-wrapper .action-play-audio {
position: absolute;
left: 0px;
bottom: 10px;
}
.expression .peek-wrapper .tags {
position: absolute;
left: 0px;
bottom: -10px;
}
.expression .peek-wrapper .frequencies {
position: absolute;
left: 0px;
bottom: -30px;
}
.expression:hover .peek-wrapper {
visibility: visible;
}
.reasons {
color: #777;
display: inline-block;
}
.compact-info {
display: inline-block;
}
.glossary ol, .glossary ul {
padding-left: 1.4em;
}
.glossary ul.compact-glossary {
display: inline;
list-style: none;
padding-left: 0px;
}
.glossary .compact-glossary li {
display: inline;
}
.glossary .compact-glossary li:not(:first-child):before {
content: " | ";
}
.glossary li {
color: #777;
}
@ -141,6 +207,10 @@ hr {
color: #000;
}
div.glossary-item.compact-glossary {
display: inline;
}
.glyph {
font-family: kanji-stroke-orders;
font-size: 120px;

View File

@ -29,6 +29,7 @@ class Display {
this.audioCache = {};
$(document).keydown(this.onKeyDown.bind(this));
$(document).on('wheel', this.onWheel.bind(this));
}
onError(error) {
@ -70,8 +71,10 @@ class Display {
onAudioPlay(e) {
e.preventDefault();
const index = Display.entryIndexFind($(e.currentTarget));
this.audioPlay(this.definitions[index]);
const link = $(e.currentTarget);
const definitionIndex = Display.entryIndexFind(link);
const expressionIndex = link.closest('.entry').find('.expression .action-play-audio').index(link);
this.audioPlay(this.definitions[definitionIndex], expressionIndex);
}
onNoteAdd(e) {
@ -182,7 +185,8 @@ class Display {
80: /* p */ () => {
if (e.altKey) {
if ($('.entry').eq(this.index).data('type') === 'term') {
this.audioPlay(this.definitions[this.index]);
const expressionIndex = this.options.general.resultOutputMode === 'merge' ? 0 : -1;
this.audioPlay(this.definitions[this.index], expressionIndex);
}
return true;
@ -202,6 +206,25 @@ class Display {
}
}
onWheel(e) {
const event = e.originalEvent;
const handler = () => {
if (event.altKey) {
if (event.deltaY < 0) { // scroll up
this.entryScrollIntoView(this.index - 1, true);
return true;
} else if (event.deltaY > 0) { // scroll down
this.entryScrollIntoView(this.index + 1, true);
return true;
}
}
};
if (handler()) {
event.preventDefault();
}
}
async termsShow(definitions, options, context) {
try {
window.focus();
@ -214,8 +237,10 @@ class Display {
const params = {
definitions,
addable: options.anki.enable,
grouped: options.general.groupResults,
grouped: options.general.resultOutputMode === 'group',
merged: options.general.resultOutputMode === 'merge',
playback: options.general.audioSource !== 'disabled',
compactGlossaries: options.general.compactGlossaries,
debug: options.general.debugInfo
};
@ -359,11 +384,12 @@ class Display {
}
}
async audioPlay(definition) {
async audioPlay(definition, expressionIndex) {
try {
this.spinner.show();
let url = await apiAudioGetUrl(definition, this.options.general.audioSource);
const expression = expressionIndex === -1 ? definition : definition.expressions[expressionIndex];
let url = await apiAudioGetUrl(expression, this.options.general.audioSource);
if (!url) {
url = '/mixed/mp3/button.mp3';
}

View File

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

View File

@ -1,19 +1,28 @@
{{#*inline "definition"}}
{{#if tags}}
<div>
{{#each tags}}
{{#if definitionTags}}
<div {{#if compactGlossaries}}class="compact-info"{{/if}}>
{{#each definitionTags}}
<span class="label label-default tag-{{category}}" title="{{notes}}">{{name}}</span>
{{/each}}
</div>
{{/if}}
{{#if only}}
<div {{#if compactGlossaries}}class="compact-info"{{/if}}>
(
{{~#each only~}}
{{{.}}}{{#unless @last}}, {{/unless}}
{{/each}}
only)
</div>
{{/if}}
{{#if glossary.[1]}}
<ul>
<ul {{#if compactGlossaries}}class="compact-glossary"{{/if}}>
{{#each glossary}}
<li><span class="glossary-item">{{#multiLine}}{{.}}{{/multiLine}}</span></li>
{{/each}}
</ul>
{{else}}
<div class="glossary-item">{{#multiLine}}{{glossary.[0]}}{{/multiLine}}</div>
<div class="glossary-item {{#if compactGlossaries}}compact-glossary{{/if}}">{{#multiLine}}{{glossary.[0]}}{{/multiLine}}</div>
{{/if}}
{{/inline}}
@ -25,13 +34,50 @@
<a href="#" class="action-add-note pending disabled" data-mode="term-kanji"><img src="/mixed/img/add-term-kanji.png" title="Add expression (Alt + E)" alt></a>
<a href="#" class="action-add-note pending disabled" data-mode="term-kana"><img src="/mixed/img/add-term-kana.png" title="Add reading (Alt + R)" alt></a>
{{/if}}
{{#unless merged}}
{{#if playback}}
<a href="#" class="action-play-audio"><img src="/mixed/img/play-audio.png" title="Play audio (Alt + P)" alt></a>
{{/if}}
{{/unless}}
<img src="/mixed/img/entry-current.png" class="current" title="Current entry (Alt + Up/Down/Home/End/PgUp/PgDn)" alt>
</div>
{{#if merged}}
{{~#each expressions~}}
<div class="expression">
<span class="expression-{{termFrequency}}">{{#kanjiLinks}}{{#furigana}}{{{.}}}{{/furigana}}{{/kanjiLinks}}</span>
<div class="peek-wrapper">
{{~#if ../playback~}}
<a href="#" class="action-play-audio"><img src="/mixed/img/play-audio.png" title="Play audio" alt></a>
{{~/if~}}
{{~#if termTags~}}
<div class="tags">
{{~#each termTags}}
<span class="label label-default tag-{{category}}" title="{{notes}}">{{name}}</span>
{{/each~}}
</div>
{{~/if~}}
{{~#if frequencies~}}
<div class="frequencies">
{{~#each frequencies}}
<span class="label label-default tag-frequency">{{dictionary}}:{{frequency}}</span>
{{/each~}}
</div>
{{~/if~}}
</div>
<span class="{{#if @last}}invisible{{/if}}"></span>
</div>
{{~/each~}}
{{else}}
<div class="expression">{{#kanjiLinks}}{{#furigana}}{{{.}}}{{/furigana}}{{/kanjiLinks}}</div>
{{#if termTags}}
<div style="display: inline-block;">
{{#each termTags}}
<span class="label label-default tag-{{category}}" title="{{notes}}">{{name}}</span>
{{/each}}
</div>
{{/if}}
{{/if}}
{{#if reasons}}
<div class="reasons">
@ -54,14 +100,24 @@
{{#if definitions.[1]}}
<ol>
{{#each definitions}}
<li>{{> definition}}</li>
<li>{{> definition compactGlossaries=../compactGlossaries}}</li>
{{/each}}
</ol>
{{else}}
{{> definition definitions.[0]}}
{{> definition definitions.[0] compactGlossaries=compactGlossaries}}
{{/if}}
{{else if merged}}
{{#if definitions.[1]}}
<ol>
{{#each definitions}}
<li>{{> definition compactGlossaries=../compactGlossaries}}</li>
{{/each}}
</ol>
{{else}}
{{> definition definitions.[0] compactGlossaries=compactGlossaries}}
{{/if}}
{{else}}
{{> definition}}
{{> definition compactGlossaries=compactGlossaries}}
{{/if}}
</div>
@ -74,7 +130,7 @@
{{#if definitions}}
{{#each definitions}}
{{#unless @first}}<hr>{{/unless}}
{{> term debug=../debug grouped=../grouped addable=../addable playback=../playback}}
{{> term debug=../debug grouped=../grouped merged=../merged addable=../addable playback=../playback compactGlossaries=../compactGlossaries}}
{{/each}}
{{else}}
<p class="note">No results found.</p>