Merge pull request #95 from siikamiika/feature-merge-similar-results

Feature: result grouping by main dictionary sequence (along with some other changes)
This commit is contained in:
Alex Yatskov 2017-10-26 09:58:32 -07:00 committed by GitHub
commit 25ae2e475a
14 changed files with 943 additions and 152 deletions

View File

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

View File

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

View File

@ -40,6 +40,9 @@ class Database {
kanjiMeta: '++,dictionary,character',
tagMeta: '++,dictionary,name'
});
this.db.version(4).stores({
terms: '++id,dictionary,expression,reading,sequence'
});
await this.db.open();
}
@ -68,12 +71,66 @@ class Database {
results.push({
expression: row.expression,
reading: row.reading,
tags: dictFieldSplit(row.tags),
definitionTags: dictFieldSplit(row.definitionTags || row.tags || ''),
termTags: dictFieldSplit(row.termTags || ''),
rules: dictFieldSplit(row.rules),
glossary: row.glossary,
score: row.score,
dictionary: row.dictionary,
id: row.id
id: row.id,
sequence: typeof row.sequence === 'undefined' ? -1 : row.sequence
});
}
});
return results;
}
async findTermsExact(term, reading, titles) {
if (!this.db) {
throw 'Database not initialized';
}
const results = [];
await this.db.terms.where('expression').equals(term).each(row => {
if (row.reading === reading && titles.includes(row.dictionary)) {
results.push({
expression: row.expression,
reading: row.reading,
definitionTags: dictFieldSplit(row.definitionTags || row.tags || ''),
termTags: dictFieldSplit(row.termTags || ''),
rules: dictFieldSplit(row.rules),
glossary: row.glossary,
score: row.score,
dictionary: row.dictionary,
id: row.id,
sequence: typeof row.sequence === 'undefined' ? -1 : row.sequence
});
}
});
return results;
}
async findTermsBySequence(sequence, mainDictionary) {
if (!this.db) {
throw 'Database not initialized';
}
const results = [];
await this.db.terms.where('sequence').equals(sequence).each(row => {
if (row.dictionary === mainDictionary) {
results.push({
expression: row.expression,
reading: row.reading,
definitionTags: dictFieldSplit(row.definitionTags || row.tags || ''),
termTags: dictFieldSplit(row.termTags || ''),
rules: dictFieldSplit(row.rules),
glossary: row.glossary,
score: row.score,
dictionary: row.dictionary,
id: row.id,
sequence: typeof row.sequence === 'undefined' ? -1 : row.sequence
});
}
});
@ -171,12 +228,27 @@ class Database {
}
}
async getTitlesWithSequences() {
if (!this.db) {
throw 'Database not initialized';
}
const titles = [];
await this.db.dictionaries.each(row => {
if (row.hasSequences) {
titles.push(row.title);
}
});
return titles;
}
async importDictionary(archive, callback) {
if (!this.db) {
throw 'Database not initialized';
}
const indexDataLoaded = async summary => {
const indexDataValid = async summary => {
if (summary.version > 2) {
throw 'Unsupported dictionary version';
}
@ -185,7 +257,9 @@ class Database {
if (count > 0) {
throw 'Dictionary is already imported';
}
};
const indexDataLoaded = async summary => {
await this.db.dictionaries.add(summary);
};
@ -196,11 +270,11 @@ class Database {
const rows = [];
if (summary.version === 1) {
for (const [expression, reading, tags, rules, score, ...glossary] of entries) {
for (const [expression, reading, definitionTags, rules, score, ...glossary] of entries) {
rows.push({
expression,
reading,
tags,
definitionTags,
rules,
score,
glossary,
@ -208,19 +282,23 @@ class Database {
});
}
} else {
for (const [expression, reading, tags, rules, score, glossary] of entries) {
for (const [expression, reading, definitionTags, rules, score, glossary, sequence, termTags] of entries) {
rows.push({
expression,
reading,
tags,
definitionTags,
rules,
score,
glossary,
sequence,
termTags,
dictionary: summary.title
});
}
}
summary.hasSequences = rows.every(row => row.sequence >= 0);
await this.db.terms.bulkAdd(rows);
};
@ -300,12 +378,13 @@ class Database {
}
const rows = [];
for (const [name, category, order, notes] of entries) {
for (const [name, category, order, notes, score] of entries) {
const row = dictTagSanitize({
name,
category,
order,
notes,
score,
dictionary: summary.title
});
@ -317,6 +396,7 @@ class Database {
return await Database.importDictionaryZip(
archive,
indexDataValid,
indexDataLoaded,
termDataLoaded,
termMetaDataLoaded,
@ -328,6 +408,7 @@ class Database {
static async importDictionaryZip(
archive,
indexDataValid,
indexDataLoaded,
termDataLoaded,
termMetaDataLoaded,
@ -353,9 +434,7 @@ class Database {
version: index.format || index.version
};
if (indexDataLoaded) {
await indexDataLoaded(summary);
}
await indexDataValid(summary);
const buildTermBankName = index => `term_bank_${index + 1}.json`;
const buildTermMetaBankName = index => `term_meta_bank_${index + 1}.json`;
@ -390,7 +469,7 @@ class Database {
const bank = [];
for (const name in index.tagMeta) {
const tag = index.tagMeta[name];
bank.push([name, tag.category, tag.order, tag.notes]);
bank.push([name, tag.category, tag.order, tag.notes, tag.score]);
}
tagDataLoaded(summary, bank, ++bankTotalCount, bankLoadedCount++);
@ -412,6 +491,10 @@ class Database {
await loadBank(summary, buildKanjiMetaBankName, kanjiMetaBankCount, kanjiMetaDataLoaded);
await loadBank(summary, buildTagBankName, tagBankCount, tagDataLoaded);
if (indexDataLoaded) {
await indexDataLoaded(summary);
}
return summary;
}
}

View File

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

View File

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

View File

@ -22,9 +22,12 @@ async function formRead() {
const optionsNew = $.extend(true, {}, optionsOld);
optionsNew.general.showGuide = $('#show-usage-guide').prop('checked');
optionsNew.general.compactTags = $('#compact-tags').prop('checked');
optionsNew.general.compactGlossaries = $('#compact-glossaries').prop('checked');
optionsNew.general.resultOutputMode = $('#result-output-mode').val();
optionsNew.general.mainDictionary = $('#main-dictionary').val();
optionsNew.general.audioSource = $('#audio-playback-source').val();
optionsNew.general.audioVolume = parseFloat($('#audio-playback-volume').val());
optionsNew.general.groupResults = $('#group-terms-results').prop('checked');
optionsNew.general.debugInfo = $('#show-debug-info').prop('checked');
optionsNew.general.showAdvanced = $('#show-advanced-options').prop('checked');
optionsNew.general.maxResults = parseInt($('#max-displayed-results').val(), 10);
@ -60,7 +63,8 @@ async function formRead() {
const title = dictionary.data('title');
const priority = parseInt(dictionary.find('.dict-priority').val(), 10);
const enabled = dictionary.find('.dict-enabled').prop('checked');
optionsNew.dictionaries[title] = {priority, enabled};
const allowSecondarySearches = dictionary.find('.dict-allow-secondary-searches').prop('checked');
optionsNew.dictionaries[title] = {priority, enabled, allowSecondarySearches};
});
return {optionsNew, optionsOld};
@ -81,6 +85,13 @@ function formUpdateVisibility(options) {
advanced.hide();
}
const merge = $('.options-merge');
if (options.general.resultOutputMode === 'merge') {
merge.show();
} else {
merge.hide();
}
const debug = $('#debug');
if (options.general.debugInfo) {
const temp = utilIsolate(options);
@ -93,6 +104,29 @@ function formUpdateVisibility(options) {
}
}
async function formMainDictionaryOptionsPopulate(options) {
const select = $('#main-dictionary').empty();
let titles = await utilDatabaseGetTitlesWithSequences();
titles = titles.filter(title => options.dictionaries[title].enabled);
const formOptionsHtml = [];
let mainDictionarySelected = false;
for (const title of titles) {
if (options.general.mainDictionary === title) {
mainDictionarySelected = true;
}
formOptionsHtml.push(`<option value="${title}"${options.general.mainDictionary === title ? ' selected' : ''}>${title}</option>`);
}
if (!mainDictionarySelected) {
options.general.mainDictionary = '';
}
const notSelectedOptionHtml = `<option class="text-muted" value=""${!mainDictionarySelected ? ' selected' : ''}>Not selected</option>`;
select.append($([notSelectedOptionHtml].concat(formOptionsHtml).join('')));
}
async function onFormOptionsChanged(e) {
try {
if (!e.originalEvent && !e.isTrigger) {
@ -100,6 +134,7 @@ async function onFormOptionsChanged(e) {
}
const {optionsNew, optionsOld} = await formRead();
await formMainDictionaryOptionsPopulate(optionsNew);
await optionsSave(optionsNew);
formUpdateVisibility(optionsNew);
@ -124,9 +159,12 @@ async function onReady() {
const options = await optionsLoad();
$('#show-usage-guide').prop('checked', options.general.showGuide);
$('#compact-tags').prop('checked', options.general.compactTags);
$('#compact-glossaries').prop('checked', options.general.compactGlossaries);
$('#result-output-mode').val(options.general.resultOutputMode);
$('#main-dictionary').val(options.general.mainDictionary);
$('#audio-playback-source').val(options.general.audioSource);
$('#audio-playback-volume').val(options.general.audioVolume);
$('#group-terms-results').prop('checked', options.general.groupResults);
$('#show-debug-info').prop('checked', options.general.debugInfo);
$('#show-advanced-options').prop('checked', options.general.showAdvanced);
$('#max-displayed-results').val(options.general.maxResults);
@ -166,6 +204,8 @@ async function onReady() {
ankiErrorShow(e);
}
await formMainDictionaryOptionsPopulate(options);
formUpdateVisibility(options);
}
@ -247,13 +287,14 @@ async function dictionaryGroupsPopulate(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', {
title: dictRow.title,
version: dictRow.version,
revision: dictRow.revision,
priority: dictOptions.priority,
enabled: dictOptions.enabled
enabled: dictOptions.enabled,
allowSecondarySearches: dictOptions.allowSecondarySearches
});
dictGroups.append($(dictHtml));
@ -261,7 +302,7 @@ async function dictionaryGroupsPopulate(options) {
formUpdateVisibility(options);
$('.dict-enabled, .dict-priority').change(e => {
$('.dict-enabled, .dict-priority, .dict-allow-secondary-searches').change(e => {
dictionaryGroupsSort();
onFormOptionsChanged(e);
});
@ -280,9 +321,11 @@ async function onDictionaryPurge(e) {
await utilDatabasePurge();
const options = await optionsLoad();
options.dictionaries = {};
options.general.mainDictionary = '';
await optionsSave(options);
await dictionaryGroupsPopulate(options);
await formMainDictionaryOptionsPopulate(options);
} catch (e) {
dictionaryErrorShow(e);
} finally {
@ -308,10 +351,14 @@ async function onDictionaryImport(e) {
const options = await optionsLoad();
const summary = await utilDatabaseImport(e.target.files[0], updateProgress);
options.dictionaries[summary.title] = {enabled: true, priority: 0};
options.dictionaries[summary.title] = {enabled: true, priority: 0, allowSecondarySearches: false};
if (summary.hasSequences && !options.general.mainDictionary) {
options.general.mainDictionary = summary.title;
}
await optionsSave(options);
await dictionaryGroupsPopulate(options);
await formMainDictionaryOptionsPopulate(options);
} catch (e) {
dictionaryErrorShow(e);
} finally {

View File

@ -13,7 +13,9 @@ templates['dictionary.html'] = template({"1":function(container,depth0,helpers,p
+ alias4(((helper = (helper = helpers.revision || (depth0 != null ? depth0.revision : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"revision","hash":{},"data":data}) : helper)))
+ "</small></h4>\n\n <div class=\"checkbox\">\n <label><input type=\"checkbox\" class=\"dict-enabled\" "
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.enabled : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "> Enable search</label>\n </div>\n <div class=\"form-group options-advanced\">\n <label for=\"dict-"
+ "> 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(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "> Allow secondary searches</label>\n </div>\n <div class=\"form-group options-advanced\">\n <label for=\"dict-"
+ alias4(((helper = (helper = helpers.title || (depth0 != null ? depth0.title : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"title","hash":{},"data":data}) : helper)))
+ "\">Result priority</label>\n <input type=\"number\" value=\""
+ alias4(((helper = (helper = helpers.priority || (depth0 != null ? depth0.priority : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"priority","hash":{},"data":data}) : helper)))
@ -204,15 +206,20 @@ templates['model.html'] = template({"1":function(container,depth0,helpers,partia
templates['terms.html'] = template({"1":function(container,depth0,helpers,partials,data) {
var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
return ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.tags : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(5, data, 0),"inverse":container.program(9, data, 0),"data":data})) != null ? stack1 : "");
return ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.definitionTags : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.only : depth0),{"name":"if","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(11, data, 0),"inverse":container.program(17, data, 0),"data":data})) != null ? stack1 : "");
},"2":function(container,depth0,helpers,partials,data) {
var stack1;
var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
return "<div>\n"
+ ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.tags : depth0),{"name":"each","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
return "<div "
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.compactGlossaries : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ">\n"
+ ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.definitionTags : depth0),{"name":"each","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</div>\n";
},"3":function(container,depth0,helpers,partials,data) {
return "class=\"compact-info\"";
},"5":function(container,depth0,helpers,partials,data) {
var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
return " <span class=\"label label-default tag-"
@ -222,88 +229,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)))
+ "</span>\n";
},"5":function(container,depth0,helpers,partials,data) {
},"7":function(container,depth0,helpers,partials,data) {
var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
return "<div "
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.compactGlossaries : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ">\n ("
+ ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.only : depth0),{"name":"each","hash":{},"fn":container.program(8, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " only)\n</div>\n";
},"8":function(container,depth0,helpers,partials,data) {
var stack1;
return "<ul>\n"
+ ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
return ((stack1 = container.lambda(depth0, depth0)) != null ? stack1 : "")
+ ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.last),{"name":"unless","hash":{},"fn":container.program(9, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n";
},"9":function(container,depth0,helpers,partials,data) {
return ", ";
},"11":function(container,depth0,helpers,partials,data) {
var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
return "<ul "
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.compactGlossaries : depth0),{"name":"if","hash":{},"fn":container.program(12, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ">\n"
+ ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(14, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</ul>\n";
},"6":function(container,depth0,helpers,partials,data) {
},"12":function(container,depth0,helpers,partials,data) {
return "class=\"compact-glossary\"";
},"14":function(container,depth0,helpers,partials,data) {
var stack1, helper, options, buffer =
" <li><span class=\"glossary-item\">";
stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(15, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
if (!helpers.multiLine) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
if (stack1 != null) { buffer += stack1; }
return buffer + "</span></li>\n";
},"7":function(container,depth0,helpers,partials,data) {
},"15":function(container,depth0,helpers,partials,data) {
return container.escapeExpression(container.lambda(depth0, depth0));
},"9":function(container,depth0,helpers,partials,data) {
var stack1, helper, options, buffer =
"<div class=\"glossary-item\">";
stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
},"17":function(container,depth0,helpers,partials,data) {
var stack1, helper, options, alias1=depth0 != null ? depth0 : (container.nullContext || {}), buffer =
"<div class=\"glossary-item "
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.compactGlossaries : depth0),{"name":"if","hash":{},"fn":container.program(18, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\">";
stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(20, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(alias1,options) : helper));
if (!helpers.multiLine) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
if (stack1 != null) { buffer += stack1; }
return buffer + "</div>\n";
},"10":function(container,depth0,helpers,partials,data) {
var stack1;
return container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["0"] : stack1), depth0));
},"12":function(container,depth0,helpers,partials,data) {
var stack1, helper, options, alias1=depth0 != null ? depth0 : (container.nullContext || {}), buffer =
"<div class=\"entry\" data-type=\"term\">\n <div class=\"actions\">\n"
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.addable : depth0),{"name":"if","hash":{},"fn":container.program(13, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.playback : depth0),{"name":"if","hash":{},"fn":container.program(15, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " <img src=\"/mixed/img/entry-current.png\" class=\"current\" title=\"Current entry (Alt + Up/Down/Home/End/PgUp/PgDn)\" alt>\n </div>\n\n <div class=\"expression\">";
stack1 = ((helper = (helper = helpers.kanjiLinks || (depth0 != null ? depth0.kanjiLinks : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"kanjiLinks","hash":{},"fn":container.program(17, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(alias1,options) : helper));
if (!helpers.kanjiLinks) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
if (stack1 != null) { buffer += stack1; }
return buffer + "</div>\n\n"
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.reasons : depth0),{"name":"if","hash":{},"fn":container.program(20, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n"
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.frequencies : depth0),{"name":"if","hash":{},"fn":container.program(24, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n <div class=\"glossary\">\n"
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.grouped : depth0),{"name":"if","hash":{},"fn":container.program(27, data, 0),"inverse":container.program(33, data, 0),"data":data})) != null ? stack1 : "")
+ " </div>\n\n"
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.debug : depth0),{"name":"if","hash":{},"fn":container.program(35, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</div>\n";
},"13":function(container,depth0,helpers,partials,data) {
return " <a href=\"#\" class=\"action-view-note pending disabled\"><img src=\"/mixed/img/view-note.png\" title=\"View added note (Alt + V)\" alt></a>\n <a href=\"#\" class=\"action-add-note pending disabled\" data-mode=\"term-kanji\"><img src=\"/mixed/img/add-term-kanji.png\" title=\"Add expression (Alt + E)\" alt></a>\n <a href=\"#\" class=\"action-add-note pending disabled\" data-mode=\"term-kana\"><img src=\"/mixed/img/add-term-kana.png\" title=\"Add reading (Alt + R)\" alt></a>\n";
},"15":function(container,depth0,helpers,partials,data) {
return " <a href=\"#\" class=\"action-play-audio\"><img src=\"/mixed/img/play-audio.png\" title=\"Play audio (Alt + P)\" alt></a>\n";
},"17":function(container,depth0,helpers,partials,data) {
var stack1, helper, options;
stack1 = ((helper = (helper = helpers.furigana || (depth0 != null ? depth0.furigana : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"furigana","hash":{},"fn":container.program(18, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
if (!helpers.furigana) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
if (stack1 != null) { return stack1; }
else { return ''; }
},"18":function(container,depth0,helpers,partials,data) {
var stack1;
return ((stack1 = container.lambda(depth0, depth0)) != null ? stack1 : "");
return "compact-glossary";
},"20":function(container,depth0,helpers,partials,data) {
var stack1;
return " <div class=\"reasons\">\n"
+ ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.reasons : depth0),{"name":"each","hash":{},"fn":container.program(21, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " </div>\n";
},"21":function(container,depth0,helpers,partials,data) {
var stack1;
return container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["0"] : stack1), depth0));
},"22":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
return " <span class=\"reasons\">"
+ container.escapeExpression(container.lambda(depth0, depth0))
+ "</span> "
+ ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.last),{"name":"unless","hash":{},"fn":container.program(22, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n";
},"22":function(container,depth0,helpers,partials,data) {
return "&laquo;";
},"24":function(container,depth0,helpers,partials,data) {
var stack1;
return " <div>\n"
+ ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.frequencies : depth0),{"name":"each","hash":{},"fn":container.program(25, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " </div>\n";
return "<div class=\"entry\" data-type=\"term\">\n <div class=\"actions\">\n"
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.addable : depth0),{"name":"if","hash":{},"fn":container.program(23, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.merged : depth0),{"name":"unless","hash":{},"fn":container.program(25, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " <img src=\"/mixed/img/entry-current.png\" class=\"current\" title=\"Current entry (Alt + Up/Down/Home/End/PgUp/PgDn)\" alt>\n </div>\n\n"
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.merged : depth0),{"name":"if","hash":{},"fn":container.program(28, data, 0, blockParams, depths),"inverse":container.program(43, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "")
+ "\n"
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.reasons : depth0),{"name":"if","hash":{},"fn":container.program(47, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n"
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.frequencies : depth0),{"name":"if","hash":{},"fn":container.program(51, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n <div class=\"glossary\">\n"
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.grouped : depth0),{"name":"if","hash":{},"fn":container.program(54, data, 0, blockParams, depths),"inverse":container.program(60, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "")
+ " </div>\n\n"
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.debug : depth0),{"name":"if","hash":{},"fn":container.program(63, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</div>\n";
},"23":function(container,depth0,helpers,partials,data) {
return " <a href=\"#\" class=\"action-view-note pending disabled\"><img src=\"/mixed/img/view-note.png\" title=\"View added note (Alt + V)\" alt></a>\n <a href=\"#\" class=\"action-add-note pending disabled\" data-mode=\"term-kanji\"><img src=\"/mixed/img/add-term-kanji.png\" title=\"Add expression (Alt + E)\" alt></a>\n <a href=\"#\" class=\"action-add-note pending disabled\" data-mode=\"term-kana\"><img src=\"/mixed/img/add-term-kana.png\" title=\"Add reading (Alt + R)\" alt></a>\n";
},"25":function(container,depth0,helpers,partials,data) {
var stack1;
return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.playback : depth0),{"name":"if","hash":{},"fn":container.program(26, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
},"26":function(container,depth0,helpers,partials,data) {
return " <a href=\"#\" class=\"action-play-audio\"><img src=\"/mixed/img/play-audio.png\" title=\"Play audio (Alt + P)\" alt></a>\n";
},"28":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1;
return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.expressions : depth0),{"name":"each","hash":{},"fn":container.program(29, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
},"29":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1, helper, options, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", buffer =
"<div class=\"expression\"><!--\n --><span class=\"expression-"
+ container.escapeExpression(((helper = (helper = helpers.termFrequency || (depth0 != null ? depth0.termFrequency : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"termFrequency","hash":{},"data":data}) : helper)))
+ "\">";
stack1 = ((helper = (helper = helpers.kanjiLinks || (depth0 != null ? depth0.kanjiLinks : depth0)) != null ? helper : alias2),(options={"name":"kanjiLinks","hash":{},"fn":container.program(30, data, 0, blockParams, depths),"inverse":container.noop,"data":data}),(typeof helper === alias3 ? helper.call(alias1,options) : helper));
if (!helpers.kanjiLinks) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
if (stack1 != null) { buffer += stack1; }
return buffer + "</span><!--\n --><div class=\"peek-wrapper\">"
+ ((stack1 = helpers["if"].call(alias1,(depths[1] != null ? depths[1].playback : depths[1]),{"name":"if","hash":{},"fn":container.program(33, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.termTags : depth0),{"name":"if","hash":{},"fn":container.program(35, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.frequencies : depth0),{"name":"if","hash":{},"fn":container.program(38, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</div><!--\n --><span class=\""
+ ((stack1 = helpers["if"].call(alias1,(data && data.last),{"name":"if","hash":{},"fn":container.program(41, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\">、</span><!--\n --></div>";
},"30":function(container,depth0,helpers,partials,data) {
var stack1, helper, options;
stack1 = ((helper = (helper = helpers.furigana || (depth0 != null ? depth0.furigana : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"furigana","hash":{},"fn":container.program(31, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
if (!helpers.furigana) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
if (stack1 != null) { return stack1; }
else { return ''; }
},"31":function(container,depth0,helpers,partials,data) {
var stack1;
return ((stack1 = container.lambda(depth0, depth0)) != null ? stack1 : "");
},"33":function(container,depth0,helpers,partials,data) {
return "<a href=\"#\" class=\"action-play-audio\"><img src=\"/mixed/img/play-audio.png\" title=\"Play audio\" alt></a>";
},"35":function(container,depth0,helpers,partials,data) {
var stack1;
return "<div class=\"tags\">"
+ ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.termTags : depth0),{"name":"each","hash":{},"fn":container.program(36, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</div>";
},"36":function(container,depth0,helpers,partials,data) {
var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
return " <span class=\"label label-default tag-"
+ alias4(((helper = (helper = helpers.category || (depth0 != null ? depth0.category : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"category","hash":{},"data":data}) : helper)))
+ "\" title=\""
+ alias4(((helper = (helper = helpers.notes || (depth0 != null ? depth0.notes : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"notes","hash":{},"data":data}) : helper)))
+ "\">"
+ alias4(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data}) : helper)))
+ "</span>\n";
},"38":function(container,depth0,helpers,partials,data) {
var stack1;
return "<div class=\"frequencies\">"
+ ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.frequencies : depth0),{"name":"each","hash":{},"fn":container.program(39, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</div>";
},"39":function(container,depth0,helpers,partials,data) {
var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
return " <span class=\"label label-default tag-frequency\">"
@ -311,62 +366,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)))
+ "</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;
return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.definitions : depth0)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(28, data, 0),"inverse":container.program(31, data, 0),"data":data})) != null ? stack1 : "");
},"28":function(container,depth0,helpers,partials,data) {
return " <div style=\"display: inline-block;\">\n"
+ ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.termTags : depth0),{"name":"each","hash":{},"fn":container.program(45, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " </div>\n";
},"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;
return " <ol>\n"
+ ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(29, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(56, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " </ol>\n";
},"29":function(container,depth0,helpers,partials,data) {
},"56":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1;
return " <li>"
+ ((stack1 = container.invokePartial(partials.definition,depth0,{"name":"definition","data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
+ ((stack1 = container.invokePartial(partials.definition,depth0,{"name":"definition","hash":{"compactGlossaries":(depths[1] != null ? depths[1].compactGlossaries : depths[1])},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
+ "</li>\n";
},"31":function(container,depth0,helpers,partials,data) {
},"58":function(container,depth0,helpers,partials,data) {
var stack1;
return ((stack1 = container.invokePartial(partials.definition,((stack1 = (depth0 != null ? depth0.definitions : depth0)) != null ? stack1["0"] : stack1),{"name":"definition","data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
},"33":function(container,depth0,helpers,partials,data) {
return ((stack1 = container.invokePartial(partials.definition,((stack1 = (depth0 != null ? depth0.definitions : depth0)) != null ? stack1["0"] : stack1),{"name":"definition","hash":{"compactGlossaries":(depth0 != null ? depth0.compactGlossaries : depth0)},"data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
},"60":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1;
return ((stack1 = container.invokePartial(partials.definition,depth0,{"name":"definition","data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
},"35":function(container,depth0,helpers,partials,data) {
return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.merged : depth0),{"name":"if","hash":{},"fn":container.program(54, data, 0, blockParams, depths),"inverse":container.program(61, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
},"61":function(container,depth0,helpers,partials,data) {
var stack1;
return ((stack1 = container.invokePartial(partials.definition,depth0,{"name":"definition","hash":{"compactGlossaries":(depth0 != null ? depth0.compactGlossaries : depth0)},"data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
+ " ";
},"63":function(container,depth0,helpers,partials,data) {
var stack1, helper, options, buffer =
" <pre>";
stack1 = ((helper = (helper = helpers.dumpObject || (depth0 != null ? depth0.dumpObject : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"dumpObject","hash":{},"fn":container.program(18, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
stack1 = ((helper = (helper = helpers.dumpObject || (depth0 != null ? depth0.dumpObject : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"dumpObject","hash":{},"fn":container.program(31, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
if (!helpers.dumpObject) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
if (stack1 != null) { buffer += stack1; }
return buffer + "</pre>\n";
},"37":function(container,depth0,helpers,partials,data,blockParams,depths) {
},"65":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1;
return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(38, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
},"38":function(container,depth0,helpers,partials,data,blockParams,depths) {
return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(66, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
},"66":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1;
return ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.first),{"name":"unless","hash":{},"fn":container.program(39, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
return ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.first),{"name":"unless","hash":{},"fn":container.program(67, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n"
+ ((stack1 = container.invokePartial(partials.term,depth0,{"name":"term","hash":{"playback":(depths[1] != null ? depths[1].playback : depths[1]),"addable":(depths[1] != null ? depths[1].addable : depths[1]),"grouped":(depths[1] != null ? depths[1].grouped : depths[1]),"debug":(depths[1] != null ? depths[1].debug : depths[1])},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
},"39":function(container,depth0,helpers,partials,data) {
+ ((stack1 = container.invokePartial(partials.term,depth0,{"name":"term","hash":{"compactGlossaries":(depths[1] != null ? depths[1].compactGlossaries : depths[1]),"playback":(depths[1] != null ? depths[1].playback : depths[1]),"addable":(depths[1] != null ? depths[1].addable : depths[1]),"merged":(depths[1] != null ? depths[1].merged : depths[1]),"grouped":(depths[1] != null ? depths[1].grouped : depths[1]),"debug":(depths[1] != null ? depths[1].debug : depths[1])},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
},"67":function(container,depth0,helpers,partials,data) {
return "<hr>";
},"41":function(container,depth0,helpers,partials,data) {
},"69":function(container,depth0,helpers,partials,data) {
return "<p class=\"note\">No results found.</p>\n";
},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1;
return "\n\n"
+ ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"if","hash":{},"fn":container.program(37, data, 0, blockParams, depths),"inverse":container.program(41, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
+ ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"if","hash":{},"fn":container.program(65, data, 0, blockParams, depths),"inverse":container.program(69, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
},"main_d": function(fn, props, container, depth0, data, blockParams, depths) {
var decorators = container.decorators;
fn = decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(1, data, 0, blockParams, depths),"inverse":container.noop,"args":["definition"],"data":data}) || fn;
fn = decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(12, data, 0, blockParams, depths),"inverse":container.noop,"args":["term"],"data":data}) || fn;
fn = decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(22, data, 0, blockParams, depths),"inverse":container.noop,"args":["term"],"data":data}) || fn;
return fn;
}

View File

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

View File

@ -26,6 +26,43 @@ function utilIsolate(data) {
return JSON.parse(JSON.stringify(data));
}
function utilSetEqual(setA, setB) {
if (setA.size !== setB.size) {
return false;
}
for (const value of setA) {
if (!setB.has(value)) {
return false;
}
}
return true;
}
function utilSetIntersection(setA, setB) {
return new Set(
[...setA].filter(value => setB.has(value))
);
}
function utilSetDifference(setA, setB) {
return new Set(
[...setA].filter(value => !setB.has(value))
);
}
function utilStringHashCode(string) {
let hashCode = 0;
for (let i = 0, charCode = string.charCodeAt(i); i < string.length; charCode = string.charCodeAt(++i)) {
hashCode = ((hashCode << 5) - hashCode) + charCode;
hashCode |= 0;
}
return hashCode;
}
function utilBackend() {
return chrome.extension.getBackgroundPage().yomichan_backend;
}
@ -46,6 +83,10 @@ function utilDatabaseGetTitles() {
return utilBackend().translator.database.getTitles();
}
function utilDatabaseGetTitlesWithSequences() {
return utilBackend().translator.database.getTitlesWithSequences();
}
function utilDatabasePurge() {
return utilBackend().translator.database.purge();
}

View File

@ -36,7 +36,11 @@
</div>
<div class="checkbox">
<label><input type="checkbox" id="group-terms-results"> Group term results</label>
<label><input type="checkbox" id="compact-tags"> Compact tags</label>
</div>
<div class="checkbox">
<label><input type="checkbox" id="compact-glossaries"> Compact glossaries</label>
</div>
<div class="checkbox">
@ -47,6 +51,15 @@
<label><input type="checkbox" id="show-debug-info"> Show debug information</label>
</div>
<div class="form-group">
<label for="result-output-mode">Result grouping</label>
<select class="form-control" id="result-output-mode">
<option value="group">Group results by term-reading pairs</option>
<option value="merge">Group results by main dictionary entry ID (experimental)</option>
<option value="split">Split definitions to their own results</option>
</select>
</div>
<div class="form-group">
<label for="audio-playback-source">Audio playback source</label>
<select class="form-control" id="audio-playback-source">
@ -127,6 +140,11 @@
<h3>Dictionaries</h3>
</div>
<div class="form-group options-merge">
<label for="main-dictionary">Main dictionary</label>
<select class="form-control" id="main-dictionary"></select>
</div>
<p class="help-block">
Yomichan can import and use a variety of dictionary formats. Unneeded dictionaries can be disabled,
or you can simply <a href="#" id="dict-purge-link">purge the database</a> to delete everything.

View File

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

View File

@ -71,8 +71,10 @@ class Display {
onAudioPlay(e) {
e.preventDefault();
const index = Display.entryIndexFind($(e.currentTarget));
this.audioPlay(this.definitions[index]);
const link = $(e.currentTarget);
const definitionIndex = Display.entryIndexFind(link);
const expressionIndex = link.closest('.entry').find('.expression .action-play-audio').index(link);
this.audioPlay(this.definitions[definitionIndex], expressionIndex);
}
onNoteAdd(e) {
@ -183,7 +185,7 @@ class Display {
80: /* p */ () => {
if (e.altKey) {
if ($('.entry').eq(this.index).data('type') === 'term') {
this.audioPlay(this.definitions[this.index]);
this.audioPlay(this.definitions[this.index], this.options.general.resultOutputMode === 'merge' ? 0 : -1);
}
return true;
@ -234,8 +236,10 @@ class Display {
const params = {
definitions,
addable: options.anki.enable,
grouped: options.general.groupResults,
grouped: options.general.resultOutputMode === 'group',
merged: options.general.resultOutputMode === 'merge',
playback: options.general.audioSource !== 'disabled',
compactGlossaries: options.general.compactGlossaries,
debug: options.general.debugInfo
};
@ -379,11 +383,11 @@ class Display {
}
}
async audioPlay(definition) {
async audioPlay(definition, expressionIndex) {
try {
this.spinner.show();
let url = await apiAudioGetUrl(definition, this.options.general.audioSource);
let url = await apiAudioGetUrl(expressionIndex === -1 ? definition : definition.expressions[expressionIndex], this.options.general.audioSource);
if (!url) {
url = '/mixed/mp3/button.mp3';
}

View File

@ -4,6 +4,9 @@
<div class="checkbox">
<label><input type="checkbox" class="dict-enabled" {{#if enabled}}checked{{/if}}> Enable search</label>
</div>
<div class="checkbox options-advanced">
<label><input type="checkbox" class="dict-allow-secondary-searches" {{#if allowSecondarySearches}}checked{{/if}}> Allow secondary searches</label>
</div>
<div class="form-group options-advanced">
<label for="dict-{{title}}">Result priority</label>
<input type="number" value="{{priority}}" id="dict-{{title}}" class="form-control dict-priority">

View File

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