From fd97f0bdba75eccadb94b748ec314d7bd4bfadad Mon Sep 17 00:00:00 2001 From: Khai Truong <56820749+khaitruong922@users.noreply.github.com> Date: Tue, 19 Nov 2024 15:16:00 +0700 Subject: [PATCH] Sort & collapse kanji dictionary entries (#1581) * sort kanji dict * update test * collapsible kanji dict * Refine CSS for kanji collapse + Add doc for custom CSS * upd css comment * remove sort by def count --- ext/css/display.css | 78 ++++++++++++------- ext/js/display/display-generator.js | 1 + ext/js/display/element-overflow-controller.js | 21 +++-- ext/js/language/translator.js | 33 +++++++- ext/templates-display.html | 45 ++++++----- ext/templates-modals.html | 34 +++++--- test/anki-template-renderer.test.js | 2 + test/data/translator-test-results.json | 4 + types/ext/dictionary.d.ts | 8 ++ 9 files changed, 161 insertions(+), 65 deletions(-) diff --git a/ext/css/display.css b/ext/css/display.css index 631c4811dd..950007c911 100644 --- a/ext/css/display.css +++ b/ext/css/display.css @@ -101,7 +101,10 @@ --animation-duration2: calc(2 * var(--animation-duration)); --collapsible-definition-line-count: 3; + --collapsible-kanji-glyph-data-line-count: 3; + --kanji-glyph-table-header-height: calc((20em / var(--font-size-no-units)) + var(--kanji-glyph-table-cell-padding) * 3); --collapsible-definition-test-offset: 0.2em; + --collpasible-kanji-glyph-data-test-offset: 0.2em; /* Colors */ --background-color: #ffffff; @@ -127,6 +130,8 @@ --headword-kanji-border-color-popular: var(--headword-kanji-border-color); --headword-kanji-border-color-rare: var(--headword-kanji-border-color); + --kanji-glyph-table-cell-padding: 0.36em; + --light-border-color: #eeeeee; --medium-border-color: #dddddd; --dark-border-color: #777777; @@ -1184,14 +1189,6 @@ button.action-button:active { display: list-item; position: relative; } -.definition-item-inner.collapsible.collapsed { - max-height: calc(1em * var(--collapsible-definition-line-count) * var(--line-height)); - overflow: hidden; -} -.definition-item-inner.collapse-test { - max-height: calc(1em * var(--collapsible-definition-line-count) * var(--line-height) + var(--collapsible-definition-test-offset)); - overflow: hidden; -} .definition-item-inner { display: flex; flex-flow: row nowrap; @@ -1202,7 +1199,9 @@ button.action-button:active { background-color: transparent; transition: background-color var(--animation-duration) ease-in-out; } -button.definition-item-expansion-button { + +/* Collapse & Expand */ +button.expansion-button { --button-content-color: var(--text-color-light4); --button-border-color: transparent; --button-background-color: transparent; @@ -1223,34 +1222,57 @@ button.definition-item-expansion-button { border-radius: 0; border: 0; } -.definition-item-inner:not(.collapsible)>button.definition-item-expansion-button { - display: none; -} -button.definition-item-expansion-button:hover+.definition-item-content, -button.definition-item-expansion-button:active+.definition-item-content, -button.definition-item-expansion-button:focus+.definition-item-content { + +button.expansion-button:hover+.definition-item-content, +button.expansion-button:active+.definition-item-content, +button.expansion-button:focus+.definition-item-content, +button.expansion-button:hover+.kanji-glyph-table, +button.expansion-button:active+.kanji-glyph-table, +button.expansion-button:focus+.kanji-glyph-table { background-color: var(--accent-color-transparent25); } -button.definition-item-expansion-button:focus:not(:focus-visible)+.definition-item-content { +button.expansion-button:focus:not(:focus-visible)+.definition-item-content, +button.expansion-button:focus:not(:focus-visible)+.kanji-glyph-table { background-color: transparent; } -button.definition-item-expansion-button:focus:hover+.definition-item-content, -button.definition-item-expansion-button:focus:active+.definition-item-content, -button.definition-item-expansion-button:focus:focus-visible+.definition-item-content { +button.expansion-button:focus:hover+.definition-item-content, +button.expansion-button:focus:active+.definition-item-content, +button.expansion-button:focus:focus-visible+.definition-item-content, +button.expansion-button:focus:hover+.kanji-glyph-table, +button.expansion-button:focus:active+.kanji-glyph-table, +button.expansion-button:focus:focus-visible+.kanji-glyph-table { background-color: var(--accent-color-transparent25); } -.definition-item-expansion-button-icon { +.definition-item-inner.collapsible.collapsed { + max-height: calc(1em * var(--collapsible-definition-line-count) * var(--line-height)); + overflow: hidden; +} +.definition-item-inner.collapse-test { + max-height: calc(1em * var(--collapsible-definition-line-count) * var(--line-height) + var(--collapsible-definition-test-offset)); + overflow: hidden; +} +.kanji-glyph-data.collapsible.collapsed { + max-height: calc(1em * var(--collapsible-kanji-glyph-data-line-count) * var(--line-height) + var(--kanji-glyph-table-header-height)); + overflow: hidden; +} +.kanji-glyph-data.collapse-test { + max-height: calc(1em * var(--collapsible-kanji-glyph-data-line-count) * var(--line-height) + var(--kanji-glyph-table-header-height) + var(--collpasible-kanji-glyph-data-test-offset)); + overflow: hidden; +} +:not(.collapsible)>button.expansion-button { + display: none; +} +.expansion-button-icon { transform: rotate(0deg); width: calc(16em / var(--font-size-no-units)); height: calc(16em / var(--font-size-no-units)); background-color: var(--button-current-content-color); transition: background-color var(--animation-duration) ease-in-out; } -.definition-item-inner.collapsible:not(.collapsed)>button.definition-item-expansion-button>.definition-item-expansion-button-icon { +.collapsible:not(.collapsed)>button.expansion-button>.expansion-button-icon { transform: rotate(180deg); } - /* Frequencies */ .frequency-group-item { display: inline; @@ -1515,14 +1537,18 @@ button.definition-item-expansion-button:focus:focus-visible+.definition-item-con } .kanji-glyph-data { margin-top: 0.75em; + display: flex; + flex-flow: row nowrap; +} +.kanji-glyph-table { border-spacing: 0; border-collapse: collapse; } -.kanji-glyph-data>tbody>tr>* { +.kanji-glyph-table>tbody>tr>* { border-top: var(--thin-border-size) solid var(--medium-border-color); text-align: left; vertical-align: top; - padding: 0.36em; + padding: var(--kanji-glyph-table-cell-padding); margin: 0; } .kanji-info-table { @@ -1538,11 +1564,11 @@ button.definition-item-expansion-button:focus:focus-visible+.definition-item-con .kanji-info-table>tbody>tr>td { text-align: right; } -.kanji-glyph-data dl { +.kanji-glyph-table dl { margin-top: 0; margin-bottom: 1.4em; } -.kanji-glyph-data dd { +.kanji-glyph-table dd { margin-left: 0; } .kanji-gloss-list { diff --git a/ext/js/display/display-generator.js b/ext/js/display/display-generator.js index fc9369d55a..6f4d7e6cbb 100644 --- a/ext/js/display/display-generator.js +++ b/ext/js/display/display-generator.js @@ -160,6 +160,7 @@ export class DisplayGenerator { */ createKanjiEntry(dictionaryEntry) { const node = this._instantiate('kanji-entry'); + node.dataset.dictionary = dictionaryEntry.dictionary; const glyphContainer = this._querySelector(node, '.kanji-glyph'); const frequencyGroupListContainer = this._querySelector(node, '.frequency-group-list'); diff --git a/ext/js/display/element-overflow-controller.js b/ext/js/display/element-overflow-controller.js index eb83dda438..21a01d9e11 100644 --- a/ext/js/display/element-overflow-controller.js +++ b/ext/js/display/element-overflow-controller.js @@ -76,7 +76,12 @@ export class ElementOverflowController { addElements(entry) { if (this._dictionaries.size === 0) { return; } - const elements = entry.querySelectorAll('.definition-item-inner'); + + /** @type {Element[]} */ + const elements = [ + ...entry.querySelectorAll('.definition-item-inner'), + ...entry.querySelectorAll('.kanji-glyph-data'), + ]; for (const element of elements) { const {parentNode} = element; if (parentNode === null) { continue; } @@ -96,7 +101,7 @@ export class ElementOverflowController { element.classList.add('collapsed'); } - const button = element.querySelector('.definition-item-expansion-button'); + const button = element.querySelector('.expansion-button'); if (button !== null) { this._eventListeners.addEventListener(button, 'click', this._onToggleButtonClickBind, false); } @@ -128,9 +133,15 @@ export class ElementOverflowController { */ _onToggleButtonClick(e) { const element = /** @type {Element} */ (e.currentTarget); - const container = element.closest('.definition-item-inner'); - if (container === null) { return; } - container.classList.toggle('collapsed'); + /** @type {(Element | null)[]} */ + const collapsedElements = [ + element.closest('.definition-item-inner'), + element.closest('.kanji-glyph-data'), + ]; + for (const collapsedElement of collapsedElements) { + if (collapsedElement === null) { continue; } + collapsedElement.classList.toggle('collapsed'); + } } /** */ diff --git a/ext/js/language/translator.js b/ext/js/language/translator.js index c5ce979074..698c782bc2 100644 --- a/ext/js/language/translator.js +++ b/ext/js/language/translator.js @@ -156,11 +156,15 @@ export class Translator { for (const {character, onyomi, kunyomi, tags, definitions, stats, dictionary} of databaseEntries) { const expandedStats = await this._expandKanjiStats(stats, dictionary); const dictionaryAlias = this._getDictionaryAlias(dictionary, enabledDictionaryMap); - const dictionaryEntry = this._createKanjiDictionaryEntry(character, dictionary, dictionaryAlias, onyomi, kunyomi, expandedStats, definitions); + const dictionaryEntry = this._createKanjiDictionaryEntry(character, dictionary, dictionaryAlias, onyomi, kunyomi, expandedStats, definitions, enabledDictionaryMap); dictionaryEntries.push(dictionaryEntry); tagAggregator.addTags(dictionaryEntry.tags, dictionary, tags); } + if (dictionaryEntries.length > 1) { + this._sortKanjiDictionaryEntries(dictionaryEntries); + } + await this._addKanjiMeta(dictionaryEntries, enabledDictionaryMap); await this._expandTagGroupsAndGroup(tagAggregator.getTagExpansionTargets()); @@ -1533,14 +1537,18 @@ export class Translator { * @param {string[]} kunyomi * @param {import('dictionary').KanjiStatGroups} stats * @param {string[]} definitions + * @param {import('translation').KanjiEnabledDictionaryMap} enabledDictionaryMap * @returns {import('dictionary').KanjiDictionaryEntry} */ - _createKanjiDictionaryEntry(character, dictionary, dictionaryAlias, onyomi, kunyomi, stats, definitions) { + _createKanjiDictionaryEntry(character, dictionary, dictionaryAlias, onyomi, kunyomi, stats, definitions, enabledDictionaryMap) { + const {index: dictionaryIndex, priority: dictionaryPriority} = this._getDictionaryOrder(dictionary, enabledDictionaryMap); return { type: 'kanji', character, dictionary, + dictionaryIndex, dictionaryAlias, + dictionaryPriority, onyomi, kunyomi, tags: [], @@ -2022,6 +2030,27 @@ export class Translator { databaseEntries.sort(compareFunction); } + /** + * @param {import('dictionary').KanjiDictionaryEntry[]} dictionaryEntries + */ + _sortKanjiDictionaryEntries(dictionaryEntries) { + /** + * @param {import('dictionary').KanjiDictionaryEntry} v1 + * @param {import('dictionary').KanjiDictionaryEntry} v2 + * @returns {number} + */ + const compareFunction = (v1, v2) => { + // Sort by dictionary priority + let i = v2.dictionaryPriority - v1.dictionaryPriority; + if (i !== 0) { return i; } + + // Sort by dictionary order + i = v1.dictionaryIndex - v2.dictionaryIndex; + return i; + }; + dictionaryEntries.sort(compareFunction); + } + /** * @param {import('translation-internal').TermDictionaryEntry[]} dictionaryEntries */ diff --git a/ext/templates-display.html b/ext/templates-display.html index dbd0eddbae..5ea7daeece 100644 --- a/ext/templates-display.html +++ b/ext/templates-display.html @@ -69,7 +69,7 @@