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,14 +258,34 @@ versions packaged.
## Frequently Asked Questions ## ## 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?**
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:
* 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.
**Why does the Kanji results page display "No data found" for several fields?**
You are using data from the KANJIDIC dictionary that was exported for an earlier version of Yomichan. It does not 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 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 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. your database and install the latest version of the KANJIDIC to see additional information about characters.
* **Can I still create cards without HTML formatting? The option for it is gone!** **Can I still create cards without HTML formatting? The option for it is gone!**
Developing Yomichan is a constant balance between including useful features and keeping complexity at a minimum. 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 With the new user-editable card template system, it is possible to create text-only cards without having to double
@ -274,32 +294,32 @@ versions packaged.
the Anki settings page (make sure you have the *Show advanced options* checkbox ticked), making sure to replace the the Anki settings page (make sure you have the *Show advanced options* checkbox ticked), making sure to replace the
existing values. existing values.
* **Will you add support for online dictionaries?** **Will you add support for online dictionaries?**
Online dictionaries will never be implemented because it is impossible to support them in a robust way. In order to 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 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 network latency and the fragility of web scraping, I do not believe that it is possible to realize a good user
experience. experience.
* **Is it possible to use Yomichan with files saved locally on my computer with Chrome?** **Is it possible to use Yomichan with files saved locally on my computer with Chrome?**
In order to use Yomichan with local files in Chrome, you must first tick the *Allow access to file URLs* checkbox 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 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 likely never be possible to use Yomichan with PDF files.
* **Is it possible to delete individual dictionaries without purging the database?** **Is it possible to delete individual dictionaries without purging the database?**
Although it is technically possible to purge specific dictionaries, due to the limitations of the underlying browser 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 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 moderately-sized term dictionary! Instead of including a borderline unusable feature in Yomichan, I have chosen to
disable dictionary deletion entirely. disable dictionary deletion entirely.
* **Why aren't EPWING dictionaries bundled with Yomichan?** **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 The vast majority of EPWING dictionaries are proprietary, so unfortunately I am unable to legally include them in
this extension for copyright reasons. this extension for copyright reasons.
* **When are you going to add support for $MYLANGUAGE?** **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 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 to invest in learning yet another language; therefore other languages will not be supported. I will also not accept

View File

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

View File

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

View File

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

View File

@ -89,7 +89,7 @@ function dictTermsSort(definitions, dictionaries=null) {
return 1; 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; 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) { function dictTermsGroup(definitions, dictionaries) {
const groups = {}; const groups = {};
for (const definition of definitions) { for (const definition of definitions) {
@ -136,6 +163,7 @@ function dictTermsGroup(definitions, dictionaries) {
expression: firstDef.expression, expression: firstDef.expression,
reading: firstDef.reading, reading: firstDef.reading,
reasons: firstDef.reasons, reasons: firstDef.reasons,
termTags: groupDefs[0].termTags,
score: groupDefs.reduce((p, v) => v.score > p ? v.score : p, Number.MIN_SAFE_INTEGER), score: groupDefs.reduce((p, v) => v.score > p ? v.score : p, Number.MIN_SAFE_INTEGER),
source: firstDef.source source: firstDef.source
}); });
@ -144,6 +172,116 @@ function dictTermsGroup(definitions, dictionaries) {
return dictTermsSort(results); 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) { function dictTagBuildSource(name) {
return dictTagSanitize({name, category: 'dictionary', order: 100}); return dictTagSanitize({name, category: 'dictionary', order: 100});
} }
@ -153,6 +291,7 @@ function dictTagSanitize(tag) {
tag.category = tag.category || 'default'; tag.category = tag.category || 'default';
tag.notes = tag.notes || ''; tag.notes = tag.notes || '';
tag.order = tag.order || 0; tag.order = tag.order || 0;
tag.score = tag.score || 0;
return tag; return tag;
} }
@ -207,10 +346,12 @@ async function dictFieldFormat(field, definition, mode, options) {
const data = { const data = {
marker, marker,
definition, definition,
group: options.general.groupResults, group: options.general.resultOutputMode === 'group',
merge: options.general.resultOutputMode === 'merge',
modeTermKanji: mode === 'term-kanji', modeTermKanji: mode === 'term-kanji',
modeTermKana: mode === 'term-kana', modeTermKana: mode === 'term-kana',
modeKanji: mode === 'kanji' modeKanji: mode === 'kanji',
compactGlossaries: options.general.compactGlossaries
}; };
const html = await apiTemplateRender(options.anki.fieldTemplates, data, true); const html = await apiTemplateRender(options.anki.fieldTemplates, data, true);

View File

@ -19,12 +19,26 @@
function optionsFieldTemplates() { function optionsFieldTemplates() {
return ` return `
<style>
.expression-popular {
color: #0275d8;
}
.expression-rare {
color: #999;
}
</style>
{{#*inline "glossary-single"}} {{#*inline "glossary-single"}}
{{~#unless brief~}} {{~#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~}} {{~/unless~}}
{{~#if glossary.[1]~}} {{~#if glossary.[1]~}}
{{~#if compactGlossaries~}}
{{#each glossary}}{{#multiLine}}{{.}}{{/multiLine}}{{#unless @last}} | {{/unless}}{{/each}}
{{~else~}}
<ul>{{#each glossary}}<li>{{#multiLine}}{{.}}{{/multiLine}}</li>{{/each}}</ul> <ul>{{#each glossary}}<li>{{#multiLine}}{{.}}{{/multiLine}}</li>{{/each}}</ul>
{{~/if~}}
{{~else~}} {{~else~}}
{{~#multiLine}}{{glossary.[0]}}{{/multiLine~}} {{~#multiLine}}{{glossary.[0]}}{{/multiLine~}}
{{~/if~}} {{~/if~}}
@ -41,6 +55,24 @@ function optionsFieldTemplates() {
{{/inline}} {{/inline}}
{{#*inline "expression"}} {{#*inline "expression"}}
{{~#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 modeTermKana~}}
{{~#if definition.reading~}} {{~#if definition.reading~}}
{{definition.reading}} {{definition.reading}}
@ -50,14 +82,29 @@ function optionsFieldTemplates() {
{{~else~}} {{~else~}}
{{definition.expression}} {{definition.expression}}
{{~/if~}} {{~/if~}}
{{~/if~}}
{{/inline}} {{/inline}}
{{#*inline "furigana"}} {{#*inline "furigana"}}
{{~#if merge~}}
{{~#each definition.expressions~}}
<span class="expression-{{termFrequency}}">{{~#furigana}}{{{.}}}{{/furigana~}}</span>
{{~#unless @last}}{{/unless~}}
{{~/each~}}
{{~else~}}
{{#furigana}}{{{definition}}}{{/furigana}} {{#furigana}}{{{definition}}}{{/furigana}}
{{~/if~}}
{{/inline}} {{/inline}}
{{#*inline "furigana-plain"}} {{#*inline "furigana-plain"}}
{{~#if merge~}}
{{~#each definition.expressions~}}
<span class="expression-{{termFrequency}}">{{~#furiganaPlain}}{{{.}}}{{/furiganaPlain~}}</span>
{{~#unless @last}}{{/unless~}}
{{~/each~}}
{{~else~}}
{{#furiganaPlain}}{{{definition}}}{{/furiganaPlain}} {{#furiganaPlain}}{{{definition}}}{{/furiganaPlain}}
{{~/if~}}
{{/inline}} {{/inline}}
{{#*inline "glossary"}} {{#*inline "glossary"}}
@ -71,12 +118,18 @@ function optionsFieldTemplates() {
{{~else~}} {{~else~}}
{{~#if group~}} {{~#if group~}}
{{~#if definition.definitions.[1]~}} {{~#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~}} {{~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~}} {{~/if~}}
{{~else~}} {{~else~}}
{{~> glossary-single definition brief=brief~}} {{~> glossary-single definition brief=brief compactGlossaries=compactGlossaries~}}
{{~/if~}} {{~/if~}}
{{~/if~}} {{~/if~}}
</div> </div>
@ -95,7 +148,16 @@ function optionsFieldTemplates() {
{{/inline}} {{/inline}}
{{#*inline "reading"}} {{#*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}}
{{#*inline "sentence"}} {{#*inline "sentence"}}
@ -115,7 +177,7 @@ function optionsFieldTemplates() {
{{/inline}} {{/inline}}
{{#*inline "tags"}} {{#*inline "tags"}}
{{~#each definition.tags}}{{name}}{{#unless @last}}, {{/unless}}{{/each~}} {{~#each definition.definitionTags}}{{name}}{{#unless @last}}, {{/unless}}{{/each~}}
{{/inline}} {{/inline}}
{{#*inline "url"}} {{#*inline "url"}}
@ -132,14 +194,17 @@ function optionsSetDefaults(options) {
enable: true, enable: true,
audioSource: 'jpod101', audioSource: 'jpod101',
audioVolume: 100, audioVolume: 100,
groupResults: true, resultOutputMode: 'group',
debugInfo: false, debugInfo: false,
maxResults: 32, maxResults: 32,
showAdvanced: false, showAdvanced: false,
popupWidth: 400, popupWidth: 400,
popupHeight: 250, popupHeight: 250,
popupOffset: 10, popupOffset: 10,
showGuide: true showGuide: true,
compactTags: false,
compactGlossaries: false,
mainDictionary: ''
}, },
scanning: { scanning: {
@ -205,6 +270,24 @@ function optionsVersion(options) {
} else { } else {
options.scanning.modifier = 'none'; 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); const optionsNew = $.extend(true, {}, optionsOld);
optionsNew.general.showGuide = $('#show-usage-guide').prop('checked'); 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.audioSource = $('#audio-playback-source').val();
optionsNew.general.audioVolume = parseFloat($('#audio-playback-volume').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.debugInfo = $('#show-debug-info').prop('checked');
optionsNew.general.showAdvanced = $('#show-advanced-options').prop('checked'); optionsNew.general.showAdvanced = $('#show-advanced-options').prop('checked');
optionsNew.general.maxResults = parseInt($('#max-displayed-results').val(), 10); 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.anki.kanji.fields = ankiFieldsToDict($('#kanji .anki-field-value'));
} }
optionsNew.general.mainDictionary = $('#dict-main').val();
$('.dict-group').each((index, element) => { $('.dict-group').each((index, element) => {
const dictionary = $(element); const dictionary = $(element);
const title = dictionary.data('title'); optionsNew.dictionaries[dictionary.data('title')] = {
const priority = parseInt(dictionary.find('.dict-priority').val(), 10); priority: parseInt(dictionary.find('.dict-priority').val(), 10),
const enabled = dictionary.find('.dict-enabled').prop('checked'); enabled: dictionary.find('.dict-enabled').prop('checked'),
optionsNew.dictionaries[title] = {priority, enabled}; allowSecondarySearches: dictionary.find('.dict-allow-secondary-searches').prop('checked')
};
}); });
return {optionsNew, optionsOld}; return {optionsNew, optionsOld};
@ -81,6 +85,13 @@ function formUpdateVisibility(options) {
advanced.hide(); advanced.hide();
} }
const mainGroup = $('#dict-main-group');
if (options.general.resultOutputMode === 'merge') {
mainGroup.show();
} else {
mainGroup.hide();
}
const debug = $('#debug'); const debug = $('#debug');
if (options.general.debugInfo) { if (options.general.debugInfo) {
const temp = utilIsolate(options); const temp = utilIsolate(options);
@ -93,17 +104,33 @@ function formUpdateVisibility(options) {
} }
} }
async function formMainDictionaryOptionsPopulate(options) {
const select = $('#dict-main').empty();
select.append($('<option class="text-muted" value="">Not selected</option>'));
let mainDictionary = '';
for (const dictRow of await utilDatabaseSummarize()) {
if (dictRow.sequenced) {
select.append($(`<option value="${dictRow.title}">${dictRow.title}</option>`));
if (dictRow.title === options.general.mainDictionary) {
mainDictionary = dictRow.title;
}
}
}
select.val(mainDictionary);
}
async function onFormOptionsChanged(e) { async function onFormOptionsChanged(e) {
try {
if (!e.originalEvent && !e.isTrigger) { if (!e.originalEvent && !e.isTrigger) {
return; return;
} }
const {optionsNew, optionsOld} = await formRead(); const {optionsNew, optionsOld} = await formRead();
await optionsSave(optionsNew); await optionsSave(optionsNew);
formUpdateVisibility(optionsNew); formUpdateVisibility(optionsNew);
try {
const ankiUpdated = const ankiUpdated =
optionsNew.anki.enable !== optionsOld.anki.enable || optionsNew.anki.enable !== optionsOld.anki.enable ||
optionsNew.anki.server !== optionsOld.anki.server; optionsNew.anki.server !== optionsOld.anki.server;
@ -124,9 +151,11 @@ async function onReady() {
const options = await optionsLoad(); const options = await optionsLoad();
$('#show-usage-guide').prop('checked', options.general.showGuide); $('#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-source').val(options.general.audioSource);
$('#audio-playback-volume').val(options.general.audioVolume); $('#audio-playback-volume').val(options.general.audioVolume);
$('#group-terms-results').prop('checked', options.general.groupResults);
$('#show-debug-info').prop('checked', options.general.debugInfo); $('#show-debug-info').prop('checked', options.general.debugInfo);
$('#show-advanced-options').prop('checked', options.general.showAdvanced); $('#show-advanced-options').prop('checked', options.general.showAdvanced);
$('#max-displayed-results').val(options.general.maxResults); $('#max-displayed-results').val(options.general.maxResults);
@ -156,6 +185,7 @@ async function onReady() {
try { try {
await dictionaryGroupsPopulate(options); await dictionaryGroupsPopulate(options);
await formMainDictionaryOptionsPopulate(options);
} catch (e) { } catch (e) {
dictionaryErrorShow(e); dictionaryErrorShow(e);
} }
@ -241,19 +271,26 @@ async function dictionaryGroupsPopulate(options) {
const dictGroups = $('#dict-groups').empty(); const dictGroups = $('#dict-groups').empty();
const dictWarning = $('#dict-warning').hide(); const dictWarning = $('#dict-warning').hide();
const dictRows = await utilDatabaseGetTitles(); const dictRows = await utilDatabaseSummarize();
if (dictRows.length === 0) { if (dictRows.length === 0) {
dictWarning.show(); dictWarning.show();
} }
for (const dictRow of dictRowsSort(dictRows, options)) { 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', { const dictHtml = await apiTemplateRender('dictionary.html', {
enabled: dictOptions.enabled,
priority: dictOptions.priority,
allowSecondarySearches: dictOptions.allowSecondarySearches,
title: dictRow.title, title: dictRow.title,
version: dictRow.version, version: dictRow.version,
revision: dictRow.revision, revision: dictRow.revision,
priority: dictOptions.priority, outdated: dictRow.version < 3
enabled: dictOptions.enabled
}); });
dictGroups.append($(dictHtml)); dictGroups.append($(dictHtml));
@ -261,7 +298,7 @@ async function dictionaryGroupsPopulate(options) {
formUpdateVisibility(options); formUpdateVisibility(options);
$('.dict-enabled, .dict-priority').change(e => { $('.dict-enabled, .dict-priority, .dict-allow-secondary-searches').change(e => {
dictionaryGroupsSort(); dictionaryGroupsSort();
onFormOptionsChanged(e); onFormOptionsChanged(e);
}); });
@ -270,7 +307,7 @@ async function dictionaryGroupsPopulate(options) {
async function onDictionaryPurge(e) { async function onDictionaryPurge(e) {
e.preventDefault(); e.preventDefault();
const dictControls = $('#dict-importer, #dict-groups').hide(); const dictControls = $('#dict-importer, #dict-groups, #dict-main-group').hide();
const dictProgress = $('#dict-purge').show(); const dictProgress = $('#dict-purge').show();
try { try {
@ -280,9 +317,11 @@ async function onDictionaryPurge(e) {
await utilDatabasePurge(); await utilDatabasePurge();
const options = await optionsLoad(); const options = await optionsLoad();
options.dictionaries = {}; options.dictionaries = {};
options.general.mainDictionary = '';
await optionsSave(options); await optionsSave(options);
await dictionaryGroupsPopulate(options); await dictionaryGroupsPopulate(options);
await formMainDictionaryOptionsPopulate(options);
} catch (e) { } catch (e) {
dictionaryErrorShow(e); dictionaryErrorShow(e);
} finally { } finally {
@ -308,10 +347,14 @@ async function onDictionaryImport(e) {
const options = await optionsLoad(); const options = await optionsLoad();
const summary = await utilDatabaseImport(e.target.files[0], updateProgress); 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 optionsSave(options);
await dictionaryGroupsPopulate(options); await dictionaryGroupsPopulate(options);
await formMainDictionaryOptionsPopulate(options);
} catch (e) { } catch (e) {
dictionaryErrorShow(e); dictionaryErrorShow(e);
} finally { } finally {

View File

@ -1,6 +1,8 @@
(function() { (function() {
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
templates['dictionary.html'] = template({"1":function(container,depth0,helpers,partials,data) { 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"; return "checked";
},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { },"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; 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))) + 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." + " <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))) + 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\" " + "</small></h4>\n"
+ ((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 : "") + ((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 : "")
+ "> Enable search</label>\n </div>\n <div class=\"form-group options-advanced\">\n <label for=\"dict-" + "\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))) + 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=\"" + "\">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))) + 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) { templates['terms.html'] = template({"1":function(container,depth0,helpers,partials,data) {
var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {}); 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 : "") 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,((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 : ""); + ((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) { },"2":function(container,depth0,helpers,partials,data) {
var stack1; var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
return "<div>\n" return "<div "
+ ((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 : "") + ((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"; + "</div>\n";
},"3":function(container,depth0,helpers,partials,data) { },"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; var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
return " <span class=\"label label-default tag-" return " <span class=\"label label-default tag-"
@ -222,88 +233,136 @@ 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))) + 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"; + "</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; var stack1;
return "<ul>\n" return ((stack1 = container.lambda(depth0, depth0)) != null ? stack1 : "")
+ ((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 : "") + ((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"; + "</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 = var stack1, helper, options, buffer =
" <li><span class=\"glossary-item\">"; " <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 (!helpers.multiLine) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
if (stack1 != null) { buffer += stack1; } if (stack1 != null) { buffer += stack1; }
return buffer + "</span></li>\n"; 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)); return container.escapeExpression(container.lambda(depth0, depth0));
},"9":function(container,depth0,helpers,partials,data) { },"17":function(container,depth0,helpers,partials,data) {
var stack1, helper, options, buffer = var stack1, helper, options, alias1=depth0 != null ? depth0 : (container.nullContext || {}), buffer =
"<div class=\"glossary-item\">"; "<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)); + ((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 (!helpers.multiLine) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
if (stack1 != null) { buffer += stack1; } if (stack1 != null) { buffer += stack1; }
return buffer + "</div>\n"; 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) { },"18":function(container,depth0,helpers,partials,data) {
var stack1; return "compact-glossary";
return ((stack1 = container.lambda(depth0, depth0)) != null ? stack1 : "");
},"20":function(container,depth0,helpers,partials,data) { },"20":function(container,depth0,helpers,partials,data) {
var stack1; var stack1;
return " <div class=\"reasons\">\n" return container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["0"] : stack1), depth0));
+ ((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 : "") },"22":function(container,depth0,helpers,partials,data,blockParams,depths) {
+ " </div>\n"; var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
},"21":function(container,depth0,helpers,partials,data) {
var stack1;
return " <span class=\"reasons\">" return "<div class=\"entry\" data-type=\"term\">\n <div class=\"actions\">\n"
+ container.escapeExpression(container.lambda(depth0, depth0)) + ((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 : "")
+ "</span> " + ((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 : "")
+ ((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 : "") + " <img src=\"/mixed/img/entry-current.png\" class=\"current\" title=\"Current entry (Alt + Up/Down/Home/End/PgUp/PgDn)\" alt>\n </div>\n\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 : "")
},"22":function(container,depth0,helpers,partials,data) { + "\n"
return "&laquo;"; + ((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 : "")
},"24":function(container,depth0,helpers,partials,data) { + "\n"
var stack1; + ((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"
return " <div>\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 : "")
+ ((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 : "") + " </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"; + "</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) { },"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; var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
return " <span class=\"label label-default tag-frequency\">" return " <span class=\"label label-default tag-frequency\">"
@ -311,62 +370,123 @@ 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))) + 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"; + "</span>\n";
},"27":function(container,depth0,helpers,partials,data) { },"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; 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 : ""); return " <div style=\"display: inline-block;\">\n"
},"28":function(container,depth0,helpers,partials,data) { + ((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";
},"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(49, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n";
},"49":function(container,depth0,helpers,partials,data) {
return "&laquo;";
},"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(52, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " </div>\n";
},"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\">"
+ 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";
},"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(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; var stack1;
return " <ol>\n" 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"; + " </ol>\n";
},"29":function(container,depth0,helpers,partials,data) { },"56":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1; var stack1;
return " <li>" 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"; + "</li>\n";
},"31":function(container,depth0,helpers,partials,data) { },"58":function(container,depth0,helpers,partials,data) {
var stack1; 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 : ""); 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 : "");
},"33":function(container,depth0,helpers,partials,data) { },"60":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1; var stack1;
return ((stack1 = container.invokePartial(partials.definition,depth0,{"name":"definition","data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : ""); 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 : "");
},"35":function(container,depth0,helpers,partials,data) { },"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 = var stack1, helper, options, buffer =
" <pre>"; " <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 (!helpers.dumpObject) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
if (stack1 != null) { buffer += stack1; } if (stack1 != null) { buffer += stack1; }
return buffer + "</pre>\n"; return buffer + "</pre>\n";
},"37":function(container,depth0,helpers,partials,data,blockParams,depths) { },"65":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1; 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 : ""); 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 : "");
},"38":function(container,depth0,helpers,partials,data,blockParams,depths) { },"66":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1; 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" + "\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 : ""); + ((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 : "");
},"39":function(container,depth0,helpers,partials,data) { },"67":function(container,depth0,helpers,partials,data) {
return "<hr>"; 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"; return "<p class=\"note\">No results found.</p>\n";
},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) { },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1; var stack1;
return "\n\n" 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) { },"main_d": function(fn, props, container, depth0, data, blockParams, depths) {
var decorators = container.decorators; 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(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; return fn;
} }

View File

@ -37,6 +37,7 @@ class Translator {
} }
async findTermsGrouped(text, dictionaries, alphanumeric) { async findTermsGrouped(text, dictionaries, alphanumeric) {
const options = await apiOptionsGet();
const titles = Object.keys(dictionaries); const titles = Object.keys(dictionaries);
const {length, definitions} = await this.findTerms(text, dictionaries, alphanumeric); const {length, definitions} = await this.findTerms(text, dictionaries, alphanumeric);
@ -45,9 +46,118 @@ class Translator {
await this.buildTermFrequencies(definition, titles); await this.buildTermFrequencies(definition, titles);
} }
if (options.general.compactTags) {
for (const definition of definitionsGrouped) {
dictTermsCompressTags(definition.definitions);
}
}
return {length, definitions: definitionsGrouped}; 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) { async findTermsSplit(text, dictionaries, alphanumeric) {
const titles = Object.keys(dictionaries); const titles = Object.keys(dictionaries);
const {length, definitions} = await this.findTerms(text, dictionaries, alphanumeric); const {length, definitions} = await this.findTerms(text, dictionaries, alphanumeric);
@ -78,8 +188,9 @@ class Translator {
let definitions = []; let definitions = [];
for (const deinflection of deinflections) { for (const deinflection of deinflections) {
for (const definition of deinflection.definitions) { for (const definition of deinflection.definitions) {
const tags = await this.expandTags(definition.tags, definition.dictionary); const definitionTags = await this.expandTags(definition.definitionTags, definition.dictionary);
tags.push(dictTagBuildSource(definition.dictionary)); definitionTags.push(dictTagBuildSource(definition.dictionary));
const termTags = await this.expandTags(definition.termTags, definition.dictionary);
definitions.push({ definitions.push({
source: deinflection.source, source: deinflection.source,
@ -90,7 +201,9 @@ class Translator {
expression: definition.expression, expression: definition.expression,
reading: definition.reading, reading: definition.reading,
glossary: definition.glossary, glossary: definition.glossary,
tags: dictTagsSort(tags) definitionTags: dictTagsSort(definitionTags),
termTags: dictTagsSort(termTags),
sequence: definition.sequence
}); });
} }
} }
@ -158,10 +271,18 @@ class Translator {
} }
async buildTermFrequencies(definition, titles) { async buildTermFrequencies(definition, titles) {
definition.frequencies = []; let terms = [];
for (const meta of await this.database.findTermMeta(definition.expression, titles)) { 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') { if (meta.mode === 'freq') {
definition.frequencies.push({ term.frequencies.push({
expression: meta.expression, expression: meta.expression,
frequency: meta.data, frequency: meta.data,
dictionary: meta.dictionary dictionary: meta.dictionary
@ -169,6 +290,7 @@ class Translator {
} }
} }
} }
}
async expandTags(names, title) { async expandTags(names, title) {
const tags = []; const tags = [];

View File

@ -26,6 +26,43 @@ function utilIsolate(data) {
return JSON.parse(JSON.stringify(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() { function utilBackend() {
return chrome.extension.getBackgroundPage().yomichan_backend; return chrome.extension.getBackgroundPage().yomichan_backend;
} }
@ -38,12 +75,12 @@ function utilAnkiGetDeckNames() {
return utilBackend().anki.getDeckNames(); return utilBackend().anki.getDeckNames();
} }
function utilAnkiGetModelFieldNames(modelName) { function utilDatabaseSummarize() {
return utilBackend().anki.getModelFieldNames(modelName); return utilBackend().translator.database.summarize();
} }
function utilDatabaseGetTitles() { function utilAnkiGetModelFieldNames(modelName) {
return utilBackend().translator.database.getTitles(); return utilBackend().anki.getModelFieldNames(modelName);
} }
function utilDatabasePurge() { function utilDatabasePurge() {

View File

@ -36,7 +36,11 @@
</div> </div>
<div class="checkbox"> <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>
<div class="checkbox"> <div class="checkbox">
@ -47,6 +51,15 @@
<label><input type="checkbox" id="show-debug-info"> Show debug information</label> <label><input type="checkbox" id="show-debug-info"> Show debug information</label>
</div> </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"> <div class="form-group">
<label for="audio-playback-source">Audio playback source</label> <label for="audio-playback-source">Audio playback source</label>
<select class="form-control" id="audio-playback-source"> <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. or you can simply <a href="#" id="dict-purge-link">purge the database</a> to delete everything.
</p> </p>
<p class="help-block"> <p class="help-block">
Please visit the <a href="https://foosoft.net/projects/yomichan" target="_blank">Yomichan</a> homepage to download free Deleting individual dictionaries is not currently feasible due to limitations of browser database technology.
dictionaries that you can use with this extension.
</p> </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="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-warning" id="dict-warning">No dictionaries have been installed</div>
<div class="alert alert-danger" id="dict-error"></div> <div class="alert alert-danger" id="dict-error"></div>
@ -150,6 +167,11 @@
</div> </div>
<div id="dict-importer"> <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"> <input type="file" id="dict-file">
</div> </div>
</div> </div>

View File

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

View File

@ -1,7 +1,7 @@
{ {
"manifest_version": 2, "manifest_version": 2,
"name": "Yomichan", "name": "Yomichan",
"version": "1.4.0", "version": "1.5.0",
"description": "Japanese dictionary with Anki integration", "description": "Japanese dictionary with Anki integration",
"icons": {"16": "mixed/img/icon16.png", "48": "mixed/img/icon48.png", "128": "mixed/img/icon128.png"}, "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; display: none;
} }
.invisible {
visibility: hidden;
}
/* /*
* Entries * Entries
@ -88,6 +92,10 @@ hr {
background-color: #5cb85c; background-color: #5cb85c;
} }
.tag-partOfSpeech {
background-color: #565656;
}
.actions .disabled { .actions .disabled {
pointer-events: none; pointer-events: none;
cursor: default; cursor: default;
@ -118,21 +126,79 @@ hr {
font-size: 24px; font-size: 24px;
} }
.expression a { .expression .kanji-link {
border-bottom: 1px #777 dashed; border-bottom: 1px #777 dashed;
color: #333; color: #333;
text-decoration: none; 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 { .reasons {
color: #777; color: #777;
display: inline-block; display: inline-block;
} }
.compact-info {
display: inline-block;
}
.glossary ol, .glossary ul { .glossary ol, .glossary ul {
padding-left: 1.4em; 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 { .glossary li {
color: #777; color: #777;
} }
@ -141,6 +207,10 @@ hr {
color: #000; color: #000;
} }
div.glossary-item.compact-glossary {
display: inline;
}
.glyph { .glyph {
font-family: kanji-stroke-orders; font-family: kanji-stroke-orders;
font-size: 120px; font-size: 120px;

View File

@ -29,6 +29,7 @@ class Display {
this.audioCache = {}; this.audioCache = {};
$(document).keydown(this.onKeyDown.bind(this)); $(document).keydown(this.onKeyDown.bind(this));
$(document).on('wheel', this.onWheel.bind(this));
} }
onError(error) { onError(error) {
@ -70,8 +71,10 @@ class Display {
onAudioPlay(e) { onAudioPlay(e) {
e.preventDefault(); e.preventDefault();
const index = Display.entryIndexFind($(e.currentTarget)); const link = $(e.currentTarget);
this.audioPlay(this.definitions[index]); 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) { onNoteAdd(e) {
@ -182,7 +185,8 @@ class Display {
80: /* p */ () => { 80: /* p */ () => {
if (e.altKey) { if (e.altKey) {
if ($('.entry').eq(this.index).data('type') === 'term') { 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; 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) { async termsShow(definitions, options, context) {
try { try {
window.focus(); window.focus();
@ -214,8 +237,10 @@ class Display {
const params = { const params = {
definitions, definitions,
addable: options.anki.enable, addable: options.anki.enable,
grouped: options.general.groupResults, grouped: options.general.resultOutputMode === 'group',
merged: options.general.resultOutputMode === 'merge',
playback: options.general.audioSource !== 'disabled', playback: options.general.audioSource !== 'disabled',
compactGlossaries: options.general.compactGlossaries,
debug: options.general.debugInfo debug: options.general.debugInfo
}; };
@ -359,11 +384,12 @@ class Display {
} }
} }
async audioPlay(definition) { async audioPlay(definition, expressionIndex) {
try { try {
this.spinner.show(); 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) { if (!url) {
url = '/mixed/mp3/button.mp3'; url = '/mixed/mp3/button.mp3';
} }

View File

@ -1,9 +1,15 @@
<div class="dict-group well well-sm" data-title="{{title}}"> <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> <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"> <div class="checkbox">
<label><input type="checkbox" class="dict-enabled" {{#if enabled}}checked{{/if}}> Enable search</label> <label><input type="checkbox" class="dict-enabled" {{#if enabled}}checked{{/if}}> Enable search</label>
</div> </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"> <div class="form-group options-advanced">
<label for="dict-{{title}}">Result priority</label> <label for="dict-{{title}}">Result priority</label>
<input type="number" value="{{priority}}" id="dict-{{title}}" class="form-control dict-priority"> <input type="number" value="{{priority}}" id="dict-{{title}}" class="form-control dict-priority">

View File

@ -1,19 +1,28 @@
{{#*inline "definition"}} {{#*inline "definition"}}
{{#if tags}} {{#if definitionTags}}
<div> <div {{#if compactGlossaries}}class="compact-info"{{/if}}>
{{#each tags}} {{#each definitionTags}}
<span class="label label-default tag-{{category}}" title="{{notes}}">{{name}}</span> <span class="label label-default tag-{{category}}" title="{{notes}}">{{name}}</span>
{{/each}} {{/each}}
</div> </div>
{{/if}} {{/if}}
{{#if only}}
<div {{#if compactGlossaries}}class="compact-info"{{/if}}>
(
{{~#each only~}}
{{{.}}}{{#unless @last}}, {{/unless}}
{{/each}}
only)
</div>
{{/if}}
{{#if glossary.[1]}} {{#if glossary.[1]}}
<ul> <ul {{#if compactGlossaries}}class="compact-glossary"{{/if}}>
{{#each glossary}} {{#each glossary}}
<li><span class="glossary-item">{{#multiLine}}{{.}}{{/multiLine}}</span></li> <li><span class="glossary-item">{{#multiLine}}{{.}}{{/multiLine}}</span></li>
{{/each}} {{/each}}
</ul> </ul>
{{else}} {{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}} {{/if}}
{{/inline}} {{/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-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> <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}} {{/if}}
{{#unless merged}}
{{#if playback}} {{#if playback}}
<a href="#" class="action-play-audio"><img src="/mixed/img/play-audio.png" title="Play audio (Alt + P)" alt></a> <a href="#" class="action-play-audio"><img src="/mixed/img/play-audio.png" title="Play audio (Alt + P)" alt></a>
{{/if}} {{/if}}
{{/unless}}
<img src="/mixed/img/entry-current.png" class="current" title="Current entry (Alt + Up/Down/Home/End/PgUp/PgDn)" alt> <img src="/mixed/img/entry-current.png" class="current" title="Current entry (Alt + Up/Down/Home/End/PgUp/PgDn)" alt>
</div> </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> <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}} {{#if reasons}}
<div class="reasons"> <div class="reasons">
@ -54,14 +100,24 @@
{{#if definitions.[1]}} {{#if definitions.[1]}}
<ol> <ol>
{{#each definitions}} {{#each definitions}}
<li>{{> definition}}</li> <li>{{> definition compactGlossaries=../compactGlossaries}}</li>
{{/each}} {{/each}}
</ol> </ol>
{{else}} {{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}} {{/if}}
{{else}} {{else}}
{{> definition}} {{> definition compactGlossaries=compactGlossaries}}
{{/if}} {{/if}}
</div> </div>
@ -74,7 +130,7 @@
{{#if definitions}} {{#if definitions}}
{{#each definitions}} {{#each definitions}}
{{#unless @first}}<hr>{{/unless}} {{#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}} {{/each}}
{{else}} {{else}}
<p class="note">No results found.</p> <p class="note">No results found.</p>