diff options
| -rw-r--r-- | ext/bg/js/api.js | 8 | ||||
| -rw-r--r-- | ext/bg/js/audio.js | 9 | ||||
| -rw-r--r-- | ext/bg/js/database.js | 107 | ||||
| -rw-r--r-- | ext/bg/js/dictionary.js | 147 | ||||
| -rw-r--r-- | ext/bg/js/options.js | 115 | ||||
| -rw-r--r-- | ext/bg/js/settings.js | 61 | ||||
| -rw-r--r-- | ext/bg/js/templates.js | 246 | ||||
| -rw-r--r-- | ext/bg/js/translator.js | 144 | ||||
| -rw-r--r-- | ext/bg/js/util.js | 41 | ||||
| -rw-r--r-- | ext/bg/settings.html | 20 | ||||
| -rw-r--r-- | ext/mixed/css/display.css | 72 | ||||
| -rw-r--r-- | ext/mixed/js/display.js | 16 | ||||
| -rw-r--r-- | tmpl/dictionary.html | 3 | ||||
| -rw-r--r-- | tmpl/terms.html | 74 | 
14 files changed, 927 insertions, 136 deletions
| diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index 9f65bb07..de3ad64e 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -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, diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index ce47490c..549288f5 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -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}; diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js index 6ceb3ec8..fcf8ef3f 100644 --- a/ext/bg/js/database.js +++ b/ext/bg/js/database.js @@ -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, +                    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 +                    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;      }  } diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js index 57acbe5e..fea5f3e5 100644 --- a/ext/bg/js/dictionary.js +++ b/ext/bg/js/dictionary.js @@ -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); diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index 36ab7694..9f1414ad 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -19,12 +19,26 @@  function optionsFieldTemplates() {      return ` +<style> +.expression-popular { +    color: #0275d8; +} + +.expression-rare { +    color: #999; +} +</style>  {{#*inline "glossary-single"}}      {{~#unless brief~}} -        {{~#if tags~}}<i>({{#each tags}}{{name}}{{#unless @last}}, {{/unless}}{{/each}})</i> {{/if~}} +        {{~#if definitionTags~}}<i>({{#each definitionTags}}{{name}}{{#unless @last}}, {{/unless}}{{/each}})</i> {{/if~}} +        {{~#if only~}}({{#each only}}{{{.}}}{{#unless @last}}, {{/unless}}{{/each}} only) {{/if~}}      {{~/unless~}}      {{~#if glossary.[1]~}} -        <ul>{{#each glossary}}<li>{{#multiLine}}{{.}}{{/multiLine}}</li>{{/each}}</ul> +        {{~#if compactGlossaries~}} +            {{#each glossary}}{{#multiLine}}{{.}}{{/multiLine}}{{#unless @last}} | {{/unless}}{{/each}} +        {{~else~}} +            <ul>{{#each glossary}}<li>{{#multiLine}}{{.}}{{/multiLine}}</li>{{/each}}</ul> +        {{~/if~}}      {{~else~}}          {{~#multiLine}}{{glossary.[0]}}{{/multiLine~}}      {{~/if~}} @@ -41,23 +55,56 @@ function optionsFieldTemplates() {  {{/inline}}  {{#*inline "expression"}} -    {{~#if modeTermKana~}} -        {{~#if definition.reading~}} -            {{definition.reading}} +    {{~#if merge~}} +        {{~#if modeTermKana~}} +            {{~#each definition.reading~}} +                {{{.}}} +                {{~#unless @last}}、{{/unless~}} +            {{~else~}} +                {{~#each definition.expression~}} +                    {{{.}}} +                    {{~#unless @last}}、{{/unless~}} +                {{~/each~}} +            {{~/each~}}          {{~else~}} -            {{definition.expression}} +            {{~#each definition.expression~}} +                {{{.}}} +                {{~#unless @last}}、{{/unless~}} +            {{~/each~}}          {{~/if~}}      {{~else~}} -        {{definition.expression}} +        {{~#if modeTermKana~}} +            {{~#if definition.reading~}} +                {{definition.reading}} +            {{~else~}} +                {{definition.expression}} +            {{~/if~}} +        {{~else~}} +            {{definition.expression}} +        {{~/if~}}      {{~/if~}}  {{/inline}}  {{#*inline "furigana"}} -    {{#furigana}}{{{definition}}}{{/furigana}} +    {{~#if merge~}} +        {{~#each definition.expressions~}} +            <span class="expression-{{termFrequency}}">{{~#furigana}}{{{.}}}{{/furigana~}}</span> +            {{~#unless @last}}、{{/unless~}} +        {{~/each~}} +    {{~else~}} +        {{#furigana}}{{{definition}}}{{/furigana}} +    {{~/if~}}  {{/inline}}  {{#*inline "furigana-plain"}} -    {{#furiganaPlain}}{{{definition}}}{{/furiganaPlain}} +    {{~#if merge~}} +        {{~#each definition.expressions~}} +            <span class="expression-{{termFrequency}}">{{~#furiganaPlain}}{{{.}}}{{/furiganaPlain~}}</span> +            {{~#unless @last}}、{{/unless~}} +        {{~/each~}} +    {{~else~}} +        {{#furiganaPlain}}{{{definition}}}{{/furiganaPlain}} +    {{~/if~}}  {{/inline}}  {{#*inline "glossary"}} @@ -71,12 +118,18 @@ function optionsFieldTemplates() {      {{~else~}}          {{~#if group~}}              {{~#if definition.definitions.[1]~}} -                <ol>{{#each definition.definitions}}<li>{{> glossary-single brief=../brief}}</li>{{/each}}</ol> +                <ol>{{#each definition.definitions}}<li>{{> glossary-single brief=../brief compactGlossaries=../compactGlossaries}}</li>{{/each}}</ol> +            {{~else~}} +                {{~> glossary-single definition.definitions.[0] brief=brief 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~}} +                {{~> 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(); +            }          }      ]; diff --git a/ext/bg/js/settings.js b/ext/bg/js/settings.js index a2f22371..7a9ba4f9 100644 --- a/ext/bg/js/settings.js +++ b/ext/bg/js/settings.js @@ -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 { diff --git a/ext/bg/js/templates.js b/ext/bg/js/templates.js index b5d352fc..f3f680d5 100644 --- a/ext/bg/js/templates.js +++ b/ext/bg/js/templates.js @@ -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,192 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia      + "\">"      + alias4(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data}) : helper)))      + "</span>\n"; -},"5":function(container,depth0,helpers,partials,data) { +},"7":function(container,depth0,helpers,partials,data) { +    var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {}); + +  return "<div " +    + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.compactGlossaries : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") +    + ">\n    (" +    + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.only : depth0),{"name":"each","hash":{},"fn":container.program(8, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") +    + "    only)\n</div>\n"; +},"8":function(container,depth0,helpers,partials,data) {      var stack1; -  return "<ul>\n" -    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") +  return ((stack1 = container.lambda(depth0, depth0)) != null ? stack1 : "") +    + ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.last),{"name":"unless","hash":{},"fn":container.program(9, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") +    + "\n"; +},"9":function(container,depth0,helpers,partials,data) { +    return ", "; +},"11":function(container,depth0,helpers,partials,data) { +    var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {}); + +  return "<ul " +    + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.compactGlossaries : depth0),{"name":"if","hash":{},"fn":container.program(12, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") +    + ">\n" +    + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(14, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")      + "</ul>\n"; -},"6":function(container,depth0,helpers,partials,data) { +},"12":function(container,depth0,helpers,partials,data) { +    return "class=\"compact-glossary\""; +},"14":function(container,depth0,helpers,partials,data) {      var stack1, helper, options, buffer =     "    <li><span class=\"glossary-item\">"; -  stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper)); +  stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(15, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));    if (!helpers.multiLine) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}    if (stack1 != null) { buffer += stack1; }    return buffer + "</span></li>\n"; -},"7":function(container,depth0,helpers,partials,data) { +},"15":function(container,depth0,helpers,partials,data) {      return container.escapeExpression(container.lambda(depth0, depth0)); -},"9":function(container,depth0,helpers,partials,data) { -    var stack1, helper, options, buffer =  -  "<div class=\"glossary-item\">"; -  stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper)); +},"17":function(container,depth0,helpers,partials,data) { +    var stack1, helper, options, alias1=depth0 != null ? depth0 : (container.nullContext || {}), buffer =  +  "<div class=\"glossary-item " +    + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.compactGlossaries : depth0),{"name":"if","hash":{},"fn":container.program(18, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") +    + "\">"; +  stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(20, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(alias1,options) : helper));    if (!helpers.multiLine) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}    if (stack1 != null) { buffer += stack1; }    return buffer + "</div>\n"; -},"10":function(container,depth0,helpers,partials,data) { +},"18":function(container,depth0,helpers,partials,data) { +    return "compact-glossary"; +},"20":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 : "") +},"22":function(container,depth0,helpers,partials,data,blockParams,depths) { +    var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {}); + +  return "<div class=\"entry\" data-type=\"term\">\n    <div class=\"actions\">\n" +    + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.addable : depth0),{"name":"if","hash":{},"fn":container.program(23, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "") +    + ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.merged : depth0),{"name":"unless","hash":{},"fn":container.program(25, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "") +    + "        <img src=\"/mixed/img/entry-current.png\" class=\"current\" title=\"Current entry (Alt + Up/Down/Home/End/PgUp/PgDn)\" alt>\n    </div>\n\n" +    + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.merged : depth0),{"name":"if","hash":{},"fn":container.program(28, data, 0, blockParams, depths),"inverse":container.program(43, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "") +    + "\n" +    + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.reasons : depth0),{"name":"if","hash":{},"fn":container.program(47, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")      + "\n" -    + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.frequencies : depth0),{"name":"if","hash":{},"fn":container.program(24, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") +    + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.frequencies : depth0),{"name":"if","hash":{},"fn":container.program(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(27, data, 0),"inverse":container.program(33, data, 0),"data":data})) != null ? stack1 : "") +    + ((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(35, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") +    + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.debug : depth0),{"name":"if","hash":{},"fn":container.program(63, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")      + "</div>\n"; -},"13":function(container,depth0,helpers,partials,data) { +},"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"; -},"15":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"; -},"17":function(container,depth0,helpers,partials,data) { +},"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(18, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper)); +  stack1 = ((helper = (helper = helpers.furigana || (depth0 != null ? depth0.furigana : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"furigana","hash":{},"fn":container.program(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 ''; } -},"18":function(container,depth0,helpers,partials,data) { +},"31":function(container,depth0,helpers,partials,data) {      var stack1;    return ((stack1 = container.lambda(depth0, depth0)) != null ? stack1 : ""); -},"20":function(container,depth0,helpers,partials,data) { +},"33":function(container,depth0,helpers,partials,data) { +    return "<a href=\"#\" class=\"action-play-audio\"><img src=\"/mixed/img/play-audio.png\" title=\"Play audio\" alt></a>"; +},"35":function(container,depth0,helpers,partials,data) { +    var stack1; + +  return "<div class=\"tags\">" +    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.termTags : depth0),{"name":"each","hash":{},"fn":container.program(36, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") +    + "</div>"; +},"36":function(container,depth0,helpers,partials,data) { +    var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; + +  return "                <span class=\"label label-default tag-" +    + alias4(((helper = (helper = helpers.category || (depth0 != null ? depth0.category : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"category","hash":{},"data":data}) : helper))) +    + "\" title=\"" +    + alias4(((helper = (helper = helpers.notes || (depth0 != null ? depth0.notes : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"notes","hash":{},"data":data}) : helper))) +    + "\">" +    + alias4(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data}) : helper))) +    + "</span>\n"; +},"38":function(container,depth0,helpers,partials,data) { +    var stack1; + +  return "<div class=\"frequencies\">" +    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.frequencies : depth0),{"name":"each","hash":{},"fn":container.program(39, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") +    + "</div>"; +},"39":function(container,depth0,helpers,partials,data) { +    var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; + +  return "                <span class=\"label label-default tag-frequency\">" +    + alias4(((helper = (helper = helpers.dictionary || (depth0 != null ? depth0.dictionary : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"dictionary","hash":{},"data":data}) : helper))) +    + ":" +    + alias4(((helper = (helper = helpers.frequency || (depth0 != null ? depth0.frequency : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"frequency","hash":{},"data":data}) : helper))) +    + "</span>\n"; +},"41":function(container,depth0,helpers,partials,data) { +    return "invisible"; +},"43":function(container,depth0,helpers,partials,data) { +    var stack1, helper, options, alias1=depth0 != null ? depth0 : (container.nullContext || {}), buffer =  +  "    <div class=\"expression\">"; +  stack1 = ((helper = (helper = helpers.kanjiLinks || (depth0 != null ? depth0.kanjiLinks : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"kanjiLinks","hash":{},"fn":container.program(30, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(alias1,options) : helper)); +  if (!helpers.kanjiLinks) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)} +  if (stack1 != null) { buffer += stack1; } +  return buffer + "</div>\n" +    + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.termTags : depth0),{"name":"if","hash":{},"fn":container.program(44, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : ""); +},"44":function(container,depth0,helpers,partials,data) { +    var stack1; + +  return "    <div style=\"display: inline-block;\">\n" +    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.termTags : depth0),{"name":"each","hash":{},"fn":container.program(45, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") +    + "    </div>\n"; +},"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(21, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") +    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.reasons : depth0),{"name":"each","hash":{},"fn":container.program(48, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")      + "    </div>\n"; -},"21":function(container,depth0,helpers,partials,data) { +},"48":function(container,depth0,helpers,partials,data) {      var stack1;    return "        <span class=\"reasons\">"      + container.escapeExpression(container.lambda(depth0, depth0))      + "</span> " -    + ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.last),{"name":"unless","hash":{},"fn":container.program(22, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") +    + ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.last),{"name":"unless","hash":{},"fn":container.program(49, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")      + "\n"; -},"22":function(container,depth0,helpers,partials,data) { +},"49":function(container,depth0,helpers,partials,data) {      return "«"; -},"24":function(container,depth0,helpers,partials,data) { +},"51":function(container,depth0,helpers,partials,data) {      var stack1;    return "    <div>\n" -    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.frequencies : depth0),{"name":"each","hash":{},"fn":container.program(25, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") +    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.frequencies : depth0),{"name":"each","hash":{},"fn":container.program(52, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")      + "    </div>\n"; -},"25":function(container,depth0,helpers,partials,data) { +},"52":function(container,depth0,helpers,partials,data) {      var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;    return "        <span class=\"label label-default tag-frequency\">" @@ -311,62 +422,67 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia      + ":"      + alias4(((helper = (helper = helpers.frequency || (depth0 != null ? depth0.frequency : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"frequency","hash":{},"data":data}) : helper)))      + "</span>\n"; -},"27":function(container,depth0,helpers,partials,data) { +},"54":function(container,depth0,helpers,partials,data,blockParams,depths) {      var stack1; -  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.definitions : depth0)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(28, data, 0),"inverse":container.program(31, data, 0),"data":data})) != null ? stack1 : ""); -},"28":function(container,depth0,helpers,partials,data) { +  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.definitions : depth0)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(55, data, 0, blockParams, depths),"inverse":container.program(58, data, 0, blockParams, depths),"data":data})) != null ? stack1 : ""); +},"55":function(container,depth0,helpers,partials,data,blockParams,depths) {      var stack1;    return "        <ol>\n" -    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(29, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") +    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(56, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")      + "        </ol>\n"; -},"29":function(container,depth0,helpers,partials,data) { +},"56":function(container,depth0,helpers,partials,data,blockParams,depths) {      var stack1;    return "            <li>" -    + ((stack1 = container.invokePartial(partials.definition,depth0,{"name":"definition","data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "") +    + ((stack1 = container.invokePartial(partials.definition,depth0,{"name":"definition","hash":{"compactGlossaries":(depths[1] != null ? depths[1].compactGlossaries : depths[1])},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")      + "</li>\n"; -},"31":function(container,depth0,helpers,partials,data) { +},"58":function(container,depth0,helpers,partials,data) {      var stack1; -  return ((stack1 = container.invokePartial(partials.definition,((stack1 = (depth0 != null ? depth0.definitions : depth0)) != null ? stack1["0"] : stack1),{"name":"definition","data":data,"indent":"        ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : ""); -},"33":function(container,depth0,helpers,partials,data) { +  return ((stack1 = container.invokePartial(partials.definition,((stack1 = (depth0 != null ? depth0.definitions : depth0)) != null ? stack1["0"] : stack1),{"name":"definition","hash":{"compactGlossaries":(depth0 != null ? depth0.compactGlossaries : depth0)},"data":data,"indent":"        ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : ""); +},"60":function(container,depth0,helpers,partials,data,blockParams,depths) {      var stack1; -  return ((stack1 = container.invokePartial(partials.definition,depth0,{"name":"definition","data":data,"indent":"        ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : ""); -},"35":function(container,depth0,helpers,partials,data) { +  return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.merged : depth0),{"name":"if","hash":{},"fn":container.program(54, data, 0, blockParams, depths),"inverse":container.program(61, data, 0, blockParams, depths),"data":data})) != null ? stack1 : ""); +},"61":function(container,depth0,helpers,partials,data) { +    var stack1; + +  return ((stack1 = container.invokePartial(partials.definition,depth0,{"name":"definition","hash":{"compactGlossaries":(depth0 != null ? depth0.compactGlossaries : depth0)},"data":data,"indent":"        ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "") +    + "        "; +},"63":function(container,depth0,helpers,partials,data) {      var stack1, helper, options, buffer =     "    <pre>"; -  stack1 = ((helper = (helper = helpers.dumpObject || (depth0 != null ? depth0.dumpObject : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"dumpObject","hash":{},"fn":container.program(18, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper)); +  stack1 = ((helper = (helper = helpers.dumpObject || (depth0 != null ? depth0.dumpObject : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"dumpObject","hash":{},"fn":container.program(31, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));    if (!helpers.dumpObject) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}    if (stack1 != null) { buffer += stack1; }    return buffer + "</pre>\n"; -},"37":function(container,depth0,helpers,partials,data,blockParams,depths) { +},"65":function(container,depth0,helpers,partials,data,blockParams,depths) {      var stack1; -  return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(38, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : ""); -},"38":function(container,depth0,helpers,partials,data,blockParams,depths) { +  return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(66, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : ""); +},"66":function(container,depth0,helpers,partials,data,blockParams,depths) {      var stack1; -  return ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.first),{"name":"unless","hash":{},"fn":container.program(39, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "") +  return ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.first),{"name":"unless","hash":{},"fn":container.program(67, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")      + "\n" -    + ((stack1 = container.invokePartial(partials.term,depth0,{"name":"term","hash":{"playback":(depths[1] != null ? depths[1].playback : depths[1]),"addable":(depths[1] != null ? depths[1].addable : depths[1]),"grouped":(depths[1] != null ? depths[1].grouped : depths[1]),"debug":(depths[1] != null ? depths[1].debug : depths[1])},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : ""); -},"39":function(container,depth0,helpers,partials,data) { +    + ((stack1 = container.invokePartial(partials.term,depth0,{"name":"term","hash":{"compactGlossaries":(depths[1] != null ? depths[1].compactGlossaries : depths[1]),"playback":(depths[1] != null ? depths[1].playback : depths[1]),"addable":(depths[1] != null ? depths[1].addable : depths[1]),"merged":(depths[1] != null ? depths[1].merged : depths[1]),"grouped":(depths[1] != null ? depths[1].grouped : depths[1]),"debug":(depths[1] != null ? depths[1].debug : depths[1])},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : ""); +},"67":function(container,depth0,helpers,partials,data) {      return "<hr>"; -},"41":function(container,depth0,helpers,partials,data) { +},"69":function(container,depth0,helpers,partials,data) {      return "<p class=\"note\">No results found.</p>\n";  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) {      var stack1;    return "\n\n" -    + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"if","hash":{},"fn":container.program(37, data, 0, blockParams, depths),"inverse":container.program(41, data, 0, blockParams, depths),"data":data})) != null ? stack1 : ""); +    + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"if","hash":{},"fn":container.program(65, data, 0, blockParams, depths),"inverse":container.program(69, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");  },"main_d":  function(fn, props, container, depth0, data, blockParams, depths) {    var decorators = container.decorators;    fn = decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(1, data, 0, blockParams, depths),"inverse":container.noop,"args":["definition"],"data":data}) || fn; -  fn = decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(12, data, 0, blockParams, depths),"inverse":container.noop,"args":["term"],"data":data}) || fn; +  fn = decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(22, data, 0, blockParams, depths),"inverse":container.noop,"args":["term"],"data":data}) || fn;    return fn;    } diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js index c915dbc0..005dd5de 100644 --- a/ext/bg/js/translator.js +++ b/ext/bg/js/translator.js @@ -37,6 +37,7 @@ class Translator {      }      async findTermsGrouped(text, dictionaries, alphanumeric) { +        const options = await apiOptionsGet();          const titles = Object.keys(dictionaries);          const {length, definitions} = await this.findTerms(text, dictionaries, alphanumeric); @@ -45,9 +46,118 @@ class Translator {              await this.buildTermFrequencies(definition, titles);          } +        if (options.general.compactTags) { +            for (const definition of definitionsGrouped) { +                dictTermsCompressTags(definition.definitions); +            } +        } +          return {length, definitions: definitionsGrouped};      } +    async findTermsMerged(text, dictionaries, alphanumeric) { +        const options = await apiOptionsGet(); +        const secondarySearchTitles = Object.keys(options.dictionaries).filter(dict => options.dictionaries[dict].allowSecondarySearches); +        const titles = Object.keys(dictionaries); +        const {length, definitions} = await this.findTerms(text, dictionaries, alphanumeric); + +        const definitionsBySequence = dictTermsMergeBySequence(definitions, options.general.mainDictionary); + +        const definitionsMerged = []; +        const mergedByTermIndices = new Set(); +        for (const sequence in definitionsBySequence) { +            if (sequence < 0) { +                continue; +            } + +            const result = definitionsBySequence[sequence]; + +            const rawDefinitionsBySequence = await this.database.findTermsBySequence(Number(sequence), options.general.mainDictionary); + +            for (const definition of rawDefinitionsBySequence) { +                const tags = await this.expandTags(definition.definitionTags, definition.dictionary); +                tags.push(dictTagBuildSource(definition.dictionary)); +                definition.definitionTags = tags; +            } + +            const definitionsByGloss = dictTermsMergeByGloss(result, rawDefinitionsBySequence); + +            const secondarySearchResults = []; +            if (secondarySearchTitles.length > 0) { +                for (const expression of result.expressions.keys()) { +                    if (expression === text) { +                        continue; +                    } + +                    for (const reading of result.expressions.get(expression).keys()) { +                        for (const definition of await this.database.findTermsExact(expression, reading, secondarySearchTitles)) { +                            const tags = await this.expandTags(definition.definitionTags, definition.dictionary); +                            tags.push(dictTagBuildSource(definition.dictionary)); +                            definition.definitionTags = tags; +                            secondarySearchResults.push(definition); +                        } +                    } +                } +            } + +            dictTermsMergeByGloss(result, definitionsBySequence['-1'].concat(secondarySearchResults), definitionsByGloss, mergedByTermIndices); + +            for (const gloss in definitionsByGloss) { +                const definition = definitionsByGloss[gloss]; +                dictTagsSort(definition.definitionTags); +                result.definitions.push(definition); +            } + +            dictTermsSort(result.definitions, dictionaries); + +            const expressions = []; +            for (const expression of result.expressions.keys()) { +                for (const reading of result.expressions.get(expression).keys()) { +                    const tags = await this.expandTags(result.expressions.get(expression).get(reading), result.dictionary); +                    expressions.push({ +                        expression: expression, +                        reading: reading, +                        termTags: dictTagsSort(tags), +                        termFrequency: (score => { +                            if (score > 0) { +                                return 'popular'; +                            } else if (score < 0) { +                                return 'rare'; +                            } else { +                                return 'normal'; +                            } +                        })(tags.map(tag => tag.score).reduce((p, v) => p + v, 0)) +                    }); +                } +            } + +            result.expressions = expressions; + +            result.expression = Array.from(result.expression); +            result.reading = Array.from(result.reading); + +            definitionsMerged.push(result); +        } + +        const strayDefinitions = definitionsBySequence['-1'].filter((definition, index) => !mergedByTermIndices.has(index)); +        for (const groupedDefinition of dictTermsGroup(strayDefinitions, dictionaries)) { +            groupedDefinition.expressions = [{expression: groupedDefinition.expression, reading: groupedDefinition.reading}]; +            definitionsMerged.push(groupedDefinition); +        } + +        for (const definition of definitionsMerged) { +            await this.buildTermFrequencies(definition, titles); +        } + +        if (options.general.compactTags) { +            for (const definition of definitionsMerged) { +                dictTermsCompressTags(definition.definitions); +            } +        } + +        return {length, definitions: dictTermsSort(definitionsMerged)}; +    } +      async findTermsSplit(text, dictionaries, alphanumeric) {          const titles = Object.keys(dictionaries);          const {length, definitions} = await this.findTerms(text, dictionaries, alphanumeric); @@ -78,8 +188,9 @@ class Translator {          let definitions = [];          for (const deinflection of deinflections) {              for (const definition of deinflection.definitions) { -                const tags = await this.expandTags(definition.tags, definition.dictionary); -                tags.push(dictTagBuildSource(definition.dictionary)); +                const definitionTags = await this.expandTags(definition.definitionTags, definition.dictionary); +                definitionTags.push(dictTagBuildSource(definition.dictionary)); +                const termTags = await this.expandTags(definition.termTags, definition.dictionary);                  definitions.push({                      source: deinflection.source, @@ -90,7 +201,9 @@ class Translator {                      expression: definition.expression,                      reading: definition.reading,                      glossary: definition.glossary, -                    tags: dictTagsSort(tags) +                    definitionTags: dictTagsSort(definitionTags), +                    termTags: dictTagsSort(termTags), +                    sequence: definition.sequence                  });              }          } @@ -158,14 +271,23 @@ class Translator {      }      async buildTermFrequencies(definition, titles) { -        definition.frequencies = []; -        for (const meta of await this.database.findTermMeta(definition.expression, titles)) { -            if (meta.mode === 'freq') { -                definition.frequencies.push({ -                    expression: meta.expression, -                    frequency: meta.data, -                    dictionary: meta.dictionary -                }); +        let terms = []; +        if (definition.expressions) { +            terms = terms.concat(definition.expressions); +        } else { +            terms.push(definition); +        } + +        for (const term of terms) { +            term.frequencies = []; +            for (const meta of await this.database.findTermMeta(term.expression, titles)) { +                if (meta.mode === 'freq') { +                    term.frequencies.push({ +                        expression: meta.expression, +                        frequency: meta.data, +                        dictionary: meta.dictionary +                    }); +                }              }          }      } diff --git a/ext/bg/js/util.js b/ext/bg/js/util.js index f44582eb..091137ed 100644 --- a/ext/bg/js/util.js +++ b/ext/bg/js/util.js @@ -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();  } diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 4315d74b..f430d5e2 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -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"> @@ -48,6 +52,15 @@                  </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">                          <option value="disabled">Disabled</option> @@ -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. diff --git a/ext/mixed/css/display.css b/ext/mixed/css/display.css index cdc1be8c..eadb9dae 100644 --- a/ext/mixed/css/display.css +++ b/ext/mixed/css/display.css @@ -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; diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 302a6280..41fe85eb 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -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';              } diff --git a/tmpl/dictionary.html b/tmpl/dictionary.html index f2f7f687..1dc04f0f 100644 --- a/tmpl/dictionary.html +++ b/tmpl/dictionary.html @@ -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"> diff --git a/tmpl/terms.html b/tmpl/terms.html index a130e775..245a0ea1 100644 --- a/tmpl/terms.html +++ b/tmpl/terms.html @@ -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] compactGlossaries=compactGlossaries}} +        {{/if}} +        {{else if merged}} +        {{#if definitions.[1]}} +        <ol> +            {{#each definitions}} +            <li>{{> definition compactGlossaries=../compactGlossaries}}</li>              {{/each}}          </ol>          {{else}} -        {{> definition definitions.[0]}} +        {{> 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> |