diff --git a/ext/js/dictionary/dictionary-data-util.js b/ext/js/dictionary/dictionary-data-util.js index 8ff1b625a8..72f145557b 100644 --- a/ext/js/dictionary/dictionary-data-util.js +++ b/ext/js/dictionary/dictionary-data-util.js @@ -346,18 +346,18 @@ export function compareRevisions(current, latest) { */ function findExistingGroupedPronunciation(reading, pronunciation, groupedPronunciationList) { const existingGroupedPronunciation = groupedPronunciationList.find((groupedPronunciation) => { - return groupedPronunciation.reading === reading && arePronunciationsEquivalent(groupedPronunciation, pronunciation); + return groupedPronunciation.reading === reading && arePronunciationsEquivalent(groupedPronunciation.pronunciation, pronunciation); }); return existingGroupedPronunciation || null; } /** - * @param {import('dictionary-data-util').GroupedPronunciationInternal} groupedPronunciation + * @param {import('dictionary').Pronunciation} pronunciation1 * @param {import('dictionary').Pronunciation} pronunciation2 * @returns {boolean} */ -function arePronunciationsEquivalent({pronunciation: pronunciation1}, pronunciation2) { +function arePronunciationsEquivalent(pronunciation1, pronunciation2) { if ( pronunciation1.type !== pronunciation2.type || !areTagListsEqual(pronunciation1.tags, pronunciation2.tags) @@ -463,3 +463,33 @@ function getSetIntersection(set1, set2) { function createMapKey(array) { return JSON.stringify(array); } + + +/** + * @param {import('dictionary-data-util').DictionaryGroupedPronunciations[]} groupedPronunciations + * @returns {import('dictionary-data-util').PronunciationsInSeveralDictionaries[]} + */ +export function groupByPronunciation(groupedPronunciations) { + const groupedList = []; + + for (const dictionaryGroup of groupedPronunciations) { + const {dictionary, pronunciations} = dictionaryGroup; + + for (const pronunciation of pronunciations) { + const existingEntry = groupedList.find((entry) => arePronunciationsEquivalent(entry.pronunciation.pronunciation, pronunciation.pronunciation)); + + if (existingEntry) { + if (!existingEntry.dictionaries.includes(dictionary)) { + existingEntry.dictionaries.push(dictionary); + } + } else { + groupedList.push({ + pronunciation, + dictionaries: [dictionary], + }); + } + } + } + + return groupedList; +} diff --git a/ext/js/display/display-generator.js b/ext/js/display/display-generator.js index fc9369d55a..151610bad3 100644 --- a/ext/js/display/display-generator.js +++ b/ext/js/display/display-generator.js @@ -17,11 +17,24 @@ */ import {ExtensionError} from '../core/extension-error.js'; -import {getDisambiguations, getGroupedPronunciations, getTermFrequency, groupKanjiFrequencies, groupTermFrequencies, groupTermTags, isNonNounVerbOrAdjective} from '../dictionary/dictionary-data-util.js'; +import { + getDisambiguations, + getGroupedPronunciations, + getTermFrequency, + groupByPronunciation, + groupKanjiFrequencies, + groupTermFrequencies, + groupTermTags, + isNonNounVerbOrAdjective, +} from '../dictionary/dictionary-data-util.js'; import {HtmlTemplateCollection} from '../dom/html-template-collection.js'; import {distributeFurigana, getKanaMorae, getPitchCategory, isCodePointKanji} from '../language/ja/japanese.js'; import {getLanguageFromText} from '../language/text-utilities.js'; -import {createPronunciationDownstepPosition, createPronunciationGraph, createPronunciationText} from './pronunciation-generator.js'; +import { + createPronunciationDownstepPosition, + createPronunciationGraph, + createPronunciationText, +} from './pronunciation-generator.js'; import {StructuredContentGenerator} from './structured-content-generator.js'; export class DisplayGenerator { @@ -58,7 +71,9 @@ export class DisplayGenerator { /** */ updateHotkeys() { const hotkeyHelpController = this._hotkeyHelpController; - if (hotkeyHelpController === null) { return; } + if (hotkeyHelpController === null) { + return; + } for (const template of this._templates.getAllTemplates()) { hotkeyHelpController.setupNode(template.content); } @@ -78,7 +93,14 @@ export class DisplayGenerator { const definitionsContainer = this._querySelector(node, '.definition-list'); const headwordTagsContainer = this._querySelector(node, '.headword-list-tag-list'); - const {headwords, type, inflectionRuleChainCandidates, definitions, frequencies, pronunciations} = dictionaryEntry; + const { + headwords, + type, + inflectionRuleChainCandidates, + definitions, + frequencies, + pronunciations, + } = dictionaryEntry; const groupedPronunciations = getGroupedPronunciations(dictionaryEntry); const pronunciationCount = groupedPronunciations.reduce((i, v) => i + v.pronunciations.length, 0); const groupedFrequencies = groupTermFrequencies(dictionaryEntry); @@ -94,7 +116,9 @@ export class DisplayGenerator { uniqueTerms.add(term); uniqueReadings.add(reading); for (const {matchType, isPrimary} of sources) { - if (!isPrimary) { continue; } + if (!isPrimary) { + continue; + } primaryMatchTypes.add(matchType); } } @@ -119,14 +143,17 @@ export class DisplayGenerator { this._appendMultiple(inflectionRuleChainsContainer, this._createInflectionRuleChain.bind(this), inflectionRuleChainCandidates); this._appendMultiple(frequencyGroupListContainer, this._createFrequencyGroup.bind(this), groupedFrequencies, false); - this._appendMultiple(groupedPronunciationsContainer, this._createGroupedPronunciation.bind(this), groupedPronunciations); + const dictionariesGroupedByPronunciation = groupByPronunciation(groupedPronunciations); + this._appendMultiple(groupedPronunciationsContainer, this._createGroupedPronunciationByPronunciation.bind(this), dictionariesGroupedByPronunciation); this._appendMultiple(headwordTagsContainer, this._createTermTag.bind(this), termTags, headwords.length); for (const term of uniqueTerms) { headwordTagsContainer.appendChild(this._createSearchTag(term)); } for (const reading of uniqueReadings) { - if (uniqueTerms.has(reading)) { continue; } + if (uniqueTerms.has(reading)) { + continue; + } headwordTagsContainer.appendChild(this._createSearchTag(reading)); } @@ -173,7 +200,9 @@ export class DisplayGenerator { const dictionaryIndicesContainer = this._querySelector(node, '.kanji-dictionary-indices'); this._setTextContent(glyphContainer, dictionaryEntry.character, this._language); - if (this._language === 'ja') { glyphContainer.style.fontFamily = 'kanji-stroke-orders, sans-serif'; } + if (this._language === 'ja') { + glyphContainer.style.fontFamily = 'kanji-stroke-orders, sans-serif'; + } const groupedFrequencies = groupKanjiFrequencies(dictionaryEntry.frequencies); const dictionaryTag = this._createDictionaryTag(''); @@ -235,7 +264,9 @@ export class DisplayGenerator { const copyAttributes = ['totalHeadwordCount', 'matchedHeadwordCount', 'unmatchedHeadwordCount']; for (const attribute of copyAttributes) { const value = tagNode.dataset[attribute]; - if (typeof value === 'undefined') { continue; } + if (typeof value === 'undefined') { + continue; + } disambiguationContainer.dataset[attribute] = value; } for (const {term, reading} of disambiguationHeadwords) { @@ -275,7 +306,9 @@ export class DisplayGenerator { const {referenceUrl} = /** @type {import('core').UnknownObject} */ (error.data); if (typeof referenceUrl === 'string') { message = message.trimEnd(); - if (!/[.!?]^/.test(message)) { message += '.'; } + if (!/[.!?]^/.test(message)) { + message += '.'; + } message += ' '; link = document.createElement('a'); link.href = referenceUrl; @@ -285,7 +318,9 @@ export class DisplayGenerator { } } this._setTextContent(div, message); - if (link !== null) { div.appendChild(link); } + if (link !== null) { + div.appendChild(link); + } } list.appendChild(div); } @@ -371,7 +406,9 @@ export class DisplayGenerator { */ _createInflectionRuleChain(inflectionRuleChain) { const {source, inflectionRules} = inflectionRuleChain; - if (!Array.isArray(inflectionRules) || inflectionRules.length === 0) { return null; } + if (!Array.isArray(inflectionRules) || inflectionRules.length === 0) { + return null; + } const fragment = this._instantiate('inflection-rule-chain'); const sourceIcon = this._getInflectionSourceIcon(source); @@ -412,7 +449,9 @@ export class DisplayGenerator { const fragment = this._templates.instantiateFragment('inflection'); const node = this._querySelector(fragment, '.inflection'); this._setTextContent(node, name); - if (description) { node.title = description; } + if (description) { + node.title = description; + } node.dataset.reason = name; return fragment; } @@ -613,7 +652,9 @@ export class DisplayGenerator { this._setTextContent(inner, name); node.dataset.details = contentString.length > 0 ? contentString : name; node.dataset.category = category; - if (redundant) { node.dataset.redundant = 'true'; } + if (redundant) { + node.dataset.redundant = 'true'; + } return node; } @@ -659,37 +700,32 @@ export class DisplayGenerator { } /** - * @param {import('dictionary-data-util').DictionaryGroupedPronunciations} details + * @param {import('dictionary-data-util').PronunciationsInSeveralDictionaries} details * @returns {HTMLElement} */ - _createGroupedPronunciation(details) { - const {dictionary, dictionaryAlias, pronunciations} = details; + _createGroupedPronunciationByPronunciation({pronunciation, dictionaries}) { + const container = document.createElement('div'); + container.classList.add('grouped-pronunciation-container'); const node = this._instantiate('pronunciation-group'); - node.dataset.dictionary = dictionary; - node.dataset.pronunciationsMulti = 'true'; - node.dataset.pronunciationsCount = `${pronunciations.length}`; - const n1 = this._querySelector(node, '.pronunciation-group-tag-list'); - const tag = this._createTag(this._createTagData(dictionaryAlias, 'pronunciation-dictionary')); - tag.dataset.details = dictionary; - n1.appendChild(tag); - - let hasTags = false; - for (const {pronunciation: {tags}} of pronunciations) { - if (tags.length > 0) { - hasTags = true; - break; - } + const tagListNode = this._querySelector(node, '.pronunciation-group-tag-list'); + for (const dictionary of dictionaries) { + const tag = this._createTag(this._createTagData(dictionary, 'pronunciation-dictionary')); + tag.dataset.details = dictionary; + tagListNode.appendChild(tag); } - const n = this._querySelector(node, '.pronunciation-list'); - n.dataset.hasTags = `${hasTags}`; - this._appendMultiple(n, this._createPronunciation.bind(this), pronunciations); + const pronunciationListNode = this._querySelector(node, '.pronunciation-list'); - return node; + this._appendMultiple(pronunciationListNode, this._createPronunciation.bind(this), [pronunciation]); + + container.appendChild(node); + + return container; } + /** * @param {import('dictionary-data-util').GroupedPronunciation} details * @returns {HTMLElement} @@ -746,8 +782,12 @@ export class DisplayGenerator { node.dataset.pitchAccentDownstepPosition = `${position}`; node.dataset.pronunciationType = pitchAccent.type; - if (nasalPositions.length > 0) { node.dataset.nasalMoraPosition = nasalPositions.join(' '); } - if (devoicePositions.length > 0) { node.dataset.devoiceMoraPosition = devoicePositions.join(' '); } + if (nasalPositions.length > 0) { + node.dataset.nasalMoraPosition = nasalPositions.join(' '); + } + if (devoicePositions.length > 0) { + node.dataset.devoiceMoraPosition = devoicePositions.join(' '); + } node.dataset.tagCount = `${tags.length}`; let n = this._querySelector(node, '.pronunciation-tag-list'); @@ -817,8 +857,8 @@ export class DisplayGenerator { const item = frequencies[i]; const itemNode = ( kanji ? - this._createKanjiFrequency(/** @type {import('dictionary-data-util').KanjiFrequency} */ (item), dictionary, dictionaryAlias) : - this._createTermFrequency(/** @type {import('dictionary-data-util').TermFrequency} */ (item), dictionary, dictionaryAlias) + this._createKanjiFrequency(/** @type {import('dictionary-data-util').KanjiFrequency} */ (item), dictionary, dictionaryAlias) : + this._createTermFrequency(/** @type {import('dictionary-data-util').TermFrequency} */ (item), dictionary, dictionaryAlias) ); itemNode.dataset.index = `${i}`; body.appendChild(itemNode); @@ -964,7 +1004,9 @@ export class DisplayGenerator { if (Array.isArray(detailsArray)) { for (const details of detailsArray) { const item = createItem(details, /** @type {TExtraArg} */ (arg)); - if (item === null) { continue; } + if (item === null) { + continue; + } container.appendChild(item); if (item.nodeType === ELEMENT_NODE) { /** @type {HTMLElement} */ (item).dataset.index = `${count}`; @@ -1032,7 +1074,9 @@ export class DisplayGenerator { let start = 0; while (true) { const end = value.indexOf('\n', start); - if (end < 0) { break; } + if (end < 0) { + break; + } node.appendChild(document.createTextNode(value.substring(start, end))); node.appendChild(document.createElement('br')); start = end + 1; @@ -1067,14 +1111,20 @@ export class DisplayGenerator { * @returns {?string} */ _getPronunciationCategories(reading, termPronunciations, wordClasses, headwordIndex) { - if (termPronunciations.length === 0) { return null; } + if (termPronunciations.length === 0) { + return null; + } const isVerbOrAdjective = isNonNounVerbOrAdjective(wordClasses); /** @type {Set} */ const categories = new Set(); for (const termPronunciation of termPronunciations) { - if (termPronunciation.headwordIndex !== headwordIndex) { continue; } + if (termPronunciation.headwordIndex !== headwordIndex) { + continue; + } for (const pronunciation of termPronunciation.pronunciations) { - if (pronunciation.type !== 'pitch-accent') { continue; } + if (pronunciation.type !== 'pitch-accent') { + continue; + } const category = getPitchCategory(reading, pronunciation.position, isVerbOrAdjective); if (category !== null) { categories.add(category); diff --git a/ext/templates-display.html b/ext/templates-display.html index dbd0eddbae..4c35baabe0 100644 --- a/ext/templates-display.html +++ b/ext/templates-display.html @@ -1,91 +1,127 @@ -Templates + + +Templates + - + - - - - + + + + - -