diff options
Diffstat (limited to 'ext/mixed/js/display-generator.js')
-rw-r--r-- | ext/mixed/js/display-generator.js | 702 |
1 files changed, 0 insertions, 702 deletions
diff --git a/ext/mixed/js/display-generator.js b/ext/mixed/js/display-generator.js deleted file mode 100644 index 05376ee5..00000000 --- a/ext/mixed/js/display-generator.js +++ /dev/null @@ -1,702 +0,0 @@ -/* - * Copyright (C) 2019-2021 Yomichan Authors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -/* global - * DictionaryDataUtil - * HtmlTemplateCollection - * api - */ - -class DisplayGenerator { - constructor({japaneseUtil, mediaLoader, hotkeyHelpController=null}) { - this._japaneseUtil = japaneseUtil; - this._mediaLoader = mediaLoader; - this._hotkeyHelpController = hotkeyHelpController; - this._templates = null; - this._termPitchAccentStaticTemplateIsSetup = false; - } - - async prepare() { - const html = await api.getDisplayTemplatesHtml(); - this._templates = new HtmlTemplateCollection(html); - this.updateHotkeys(); - } - - updateHotkeys() { - const hotkeyHelpController = this._hotkeyHelpController; - if (hotkeyHelpController === null) { return; } - for (const template of this._templates.getAllTemplates()) { - hotkeyHelpController.setupNode(template.content); - } - } - - preparePitchAccents() { - if (this._termPitchAccentStaticTemplateIsSetup) { return; } - this._termPitchAccentStaticTemplateIsSetup = true; - const t = this._templates.instantiate('term-pitch-accent-static'); - document.head.appendChild(t); - } - - createTermEntry(details) { - const node = this._templates.instantiate('term-entry'); - - const expressionsContainer = node.querySelector('.term-expression-list'); - const reasonsContainer = node.querySelector('.term-reasons'); - const pitchesContainer = node.querySelector('.term-pitch-accent-group-list'); - const frequencyGroupListContainer = node.querySelector('.frequency-group-list'); - const definitionsContainer = node.querySelector('.term-definition-list'); - const termTagsContainer = node.querySelector('.term-tags'); - - const {expressions, type, reasons, frequencies} = details; - const definitions = (type === 'term' ? [details] : details.definitions); - const merged = (type === 'termMerged' || type === 'termMergedByGlossary'); - const pitches = DictionaryDataUtil.getPitchAccentInfos(details); - const pitchCount = pitches.reduce((i, v) => i + v.pitches.length, 0); - const groupedFrequencies = DictionaryDataUtil.groupTermFrequencies(frequencies); - const termTags = DictionaryDataUtil.groupTermTags(details); - - const uniqueExpressions = new Set(); - const uniqueReadings = new Set(); - for (const {expression, reading} of expressions) { - uniqueExpressions.add(expression); - uniqueReadings.add(reading); - } - - node.dataset.format = type; - node.dataset.expressionMulti = `${merged}`; - node.dataset.expressionCount = `${expressions.length}`; - node.dataset.definitionCount = `${definitions.length}`; - node.dataset.pitchAccentDictionaryCount = `${pitches.length}`; - node.dataset.pitchAccentCount = `${pitchCount}`; - node.dataset.uniqueExpressionCount = `${uniqueExpressions.size}`; - node.dataset.uniqueReadingCount = `${uniqueReadings.size}`; - node.dataset.frequencyCount = `${frequencies.length}`; - node.dataset.groupedFrequencyCount = `${groupedFrequencies.length}`; - - this._appendMultiple(expressionsContainer, this._createTermExpression.bind(this), expressions); - this._appendMultiple(reasonsContainer, this._createTermReason.bind(this), reasons); - this._appendMultiple(frequencyGroupListContainer, this._createFrequencyGroup.bind(this), groupedFrequencies, false); - this._appendMultiple(pitchesContainer, this._createPitches.bind(this), pitches); - this._appendMultiple(termTagsContainer, this._createTermTag.bind(this), termTags, expressions.length); - - // Add definitions - const dictionaryTag = this._createDictionaryTag(null); - for (let i = 0, ii = definitions.length; i < ii; ++i) { - const definition = definitions[i]; - const {dictionary} = definition; - - if (dictionaryTag.dictionary === dictionary) { - dictionaryTag.redundant = true; - } else { - dictionaryTag.redundant = false; - dictionaryTag.dictionary = dictionary; - dictionaryTag.name = dictionary; - } - - const node2 = this._createTermDefinitionItem(definition, dictionaryTag); - node2.dataset.index = `${i}`; - definitionsContainer.appendChild(node2); - } - definitionsContainer.dataset.count = `${definitions.length}`; - - return node; - } - - createKanjiEntry(details) { - const node = this._templates.instantiate('kanji-entry'); - - const glyphContainer = node.querySelector('.kanji-glyph'); - const frequencyGroupListContainer = node.querySelector('.frequency-group-list'); - const tagContainer = node.querySelector('.tags'); - const glossaryContainer = node.querySelector('.kanji-glossary-list'); - const chineseReadingsContainer = node.querySelector('.kanji-readings-chinese'); - const japaneseReadingsContainer = node.querySelector('.kanji-readings-japanese'); - const statisticsContainer = node.querySelector('.kanji-statistics'); - const classificationsContainer = node.querySelector('.kanji-classifications'); - const codepointsContainer = node.querySelector('.kanji-codepoints'); - const dictionaryIndicesContainer = node.querySelector('.kanji-dictionary-indices'); - - this._setTextContent(glyphContainer, details.character, 'ja'); - const groupedFrequencies = DictionaryDataUtil.groupKanjiFrequencies(details.frequencies); - - const dictionaryTag = this._createDictionaryTag(details.dictionary); - - this._appendMultiple(frequencyGroupListContainer, this._createFrequencyGroup.bind(this), groupedFrequencies, true); - this._appendMultiple(tagContainer, this._createTag.bind(this), [...details.tags, dictionaryTag]); - this._appendMultiple(glossaryContainer, this._createKanjiGlossaryItem.bind(this), details.glossary); - this._appendMultiple(chineseReadingsContainer, this._createKanjiReading.bind(this), details.onyomi); - this._appendMultiple(japaneseReadingsContainer, this._createKanjiReading.bind(this), details.kunyomi); - - statisticsContainer.appendChild(this._createKanjiInfoTable(details.stats.misc)); - classificationsContainer.appendChild(this._createKanjiInfoTable(details.stats.class)); - codepointsContainer.appendChild(this._createKanjiInfoTable(details.stats.code)); - dictionaryIndicesContainer.appendChild(this._createKanjiInfoTable(details.stats.index)); - - return node; - } - - createEmptyFooterNotification() { - return this._templates.instantiate('footer-notification'); - } - - createTagFooterNotificationDetails(tagNode) { - const node = this._templates.instantiateFragment('footer-notification-tag-details'); - - const details = tagNode.dataset.details; - this._setTextContent(node.querySelector('.tag-details'), details); - - let disambiguation = null; - try { - let a = tagNode.dataset.disambiguation; - if (typeof a !== 'undefined') { - a = JSON.parse(a); - if (Array.isArray(a)) { disambiguation = a; } - } - } catch (e) { - // NOP - } - - if (disambiguation !== null) { - const disambiguationContainer = node.querySelector('.tag-details-disambiguation-list'); - const copyAttributes = ['totalExpressionCount', 'matchedExpressionCount', 'unmatchedExpressionCount']; - for (const attribute of copyAttributes) { - const value = tagNode.dataset[attribute]; - if (typeof value === 'undefined') { continue; } - disambiguationContainer.dataset[attribute] = value; - } - for (const {expression, reading} of disambiguation) { - const segments = this._japaneseUtil.distributeFurigana(expression, reading); - const disambiguationItem = document.createElement('span'); - disambiguationItem.className = 'tag-details-disambiguation'; - disambiguationItem.lang = 'ja'; - this._appendFurigana(disambiguationItem, segments, (container, text) => { - container.appendChild(document.createTextNode(text)); - }); - disambiguationContainer.appendChild(disambiguationItem); - } - } - - return node; - } - - createAnkiNoteErrorsNotificationContent(errors) { - const content = this._templates.instantiate('footer-notification-anki-errors-content'); - - const header = content.querySelector('.anki-note-error-header'); - this._setTextContent(header, (errors.length === 1 ? 'An error occurred:' : `${errors.length} errors occurred:`), 'en'); - - const list = content.querySelector('.anki-note-error-list'); - for (const error of errors) { - const div = document.createElement('li'); - div.className = 'anki-note-error-message'; - this._setTextContent(div, isObject(error) && typeof error.message === 'string' ? error.message : `${error}`); - list.appendChild(div); - } - - return content; - } - - createProfileListItem() { - return this._templates.instantiate('profile-list-item'); - } - - createPopupMenu(name) { - return this._templates.instantiate(`${name}-popup-menu`); - } - - // Private - - _createTermExpression(details) { - const {termFrequency, furiganaSegments, expression, reading, termTags} = details; - - const searchQueries = []; - if (expression) { searchQueries.push(expression); } - if (reading) { searchQueries.push(reading); } - - const node = this._templates.instantiate('term-expression'); - - const expressionContainer = node.querySelector('.term-expression-text'); - const tagContainer = node.querySelector('.tags'); - - node.dataset.readingIsSame = `${!reading || reading === expression}`; - node.dataset.frequency = termFrequency; - - expressionContainer.lang = 'ja'; - - this._appendFurigana(expressionContainer, furiganaSegments, this._appendKanjiLinks.bind(this)); - this._appendMultiple(tagContainer, this._createTag.bind(this), termTags); - this._appendMultiple(tagContainer, this._createSearchTag.bind(this), searchQueries); - - return node; - } - - _createTermReason(reason) { - const fragment = this._templates.instantiateFragment('term-reason'); - const node = fragment.querySelector('.term-reason'); - this._setTextContent(node, reason); - node.dataset.reason = reason; - return fragment; - } - - _createTermDefinitionItem(details, dictionaryTag) { - const node = this._templates.instantiate('term-definition-item'); - - const tagListContainer = node.querySelector('.term-definition-tag-list'); - const onlyListContainer = node.querySelector('.term-definition-disambiguation-list'); - const glossaryContainer = node.querySelector('.term-glossary-list'); - - const {dictionary, definitionTags} = details; - node.dataset.dictionary = dictionary; - - this._appendMultiple(tagListContainer, this._createTag.bind(this), [...definitionTags, dictionaryTag]); - this._appendMultiple(onlyListContainer, this._createTermDisambiguation.bind(this), details.only); - this._appendMultiple(glossaryContainer, this._createTermGlossaryItem.bind(this), details.glossary, dictionary); - - return node; - } - - _createTermGlossaryItem(glossary, dictionary) { - if (typeof glossary === 'string') { - return this._createTermGlossaryItemText(glossary); - } else if (typeof glossary === 'object' && glossary !== null) { - switch (glossary.type) { - case 'image': - return this._createTermGlossaryItemImage(glossary, dictionary); - } - } - - return null; - } - - _createTermGlossaryItemText(glossary) { - const node = this._templates.instantiate('term-glossary-item'); - const container = node.querySelector('.term-glossary'); - this._setTextContent(container, glossary); - return node; - } - - _createTermGlossaryItemImage(data, dictionary) { - const {path, width, height, preferredWidth, preferredHeight, title, description, pixelated} = data; - - const usedWidth = ( - typeof preferredWidth === 'number' ? - preferredWidth : - width - ); - const aspectRatio = ( - typeof preferredWidth === 'number' && - typeof preferredHeight === 'number' ? - preferredWidth / preferredHeight : - width / height - ); - - const node = this._templates.instantiate('term-glossary-item-image'); - node.dataset.path = path; - node.dataset.dictionary = dictionary; - node.dataset.imageLoadState = 'not-loaded'; - - const imageContainer = node.querySelector('.term-glossary-image-container'); - imageContainer.style.width = `${usedWidth}em`; - if (typeof title === 'string') { - imageContainer.title = title; - } - - const aspectRatioSizer = node.querySelector('.term-glossary-image-aspect-ratio-sizer'); - aspectRatioSizer.style.paddingTop = `${aspectRatio * 100.0}%`; - - const image = node.querySelector('img.term-glossary-image'); - const imageLink = node.querySelector('.term-glossary-image-link'); - image.dataset.pixelated = `${pixelated === true}`; - - if (this._mediaLoader !== null) { - this._mediaLoader.loadMedia( - path, - dictionary, - (url) => this._setImageData(node, image, imageLink, url, false), - () => this._setImageData(node, image, imageLink, null, true) - ); - } - - if (typeof description === 'string') { - const container = node.querySelector('.term-glossary-image-description'); - this._setTextContent(container, description); - } - - return node; - } - - _setImageData(container, image, imageLink, url, unloaded) { - if (url !== null) { - image.src = url; - imageLink.href = url; - container.dataset.imageLoadState = 'loaded'; - } else { - image.removeAttribute('src'); - imageLink.removeAttribute('href'); - container.dataset.imageLoadState = unloaded ? 'unloaded' : 'load-error'; - } - } - - _createTermDisambiguation(disambiguation) { - const node = this._templates.instantiate('term-definition-disambiguation'); - node.dataset.term = disambiguation; - this._setTextContent(node, disambiguation, 'ja'); - return node; - } - - _createKanjiLink(character) { - const node = document.createElement('a'); - node.className = 'kanji-link'; - this._setTextContent(node, character, 'ja'); - return node; - } - - _createKanjiGlossaryItem(glossary) { - const node = this._templates.instantiate('kanji-glossary-item'); - const container = node.querySelector('.kanji-glossary'); - this._setTextContent(container, glossary); - return node; - } - - _createKanjiReading(reading) { - const node = this._templates.instantiate('kanji-reading'); - this._setTextContent(node, reading, 'ja'); - return node; - } - - _createKanjiInfoTable(details) { - const node = this._templates.instantiate('kanji-info-table'); - const container = node.querySelector('.kanji-info-table-body'); - - const count = this._appendMultiple(container, this._createKanjiInfoTableItem.bind(this), details); - if (count === 0) { - const n = this._createKanjiInfoTableItemEmpty(); - container.appendChild(n); - } - - return node; - } - - _createKanjiInfoTableItem(details) { - const node = this._templates.instantiate('kanji-info-table-item'); - const nameNode = node.querySelector('.kanji-info-table-item-header'); - const valueNode = node.querySelector('.kanji-info-table-item-value'); - this._setTextContent(nameNode, details.notes || details.name); - this._setTextContent(valueNode, details.value); - return node; - } - - _createKanjiInfoTableItemEmpty() { - return this._templates.instantiate('kanji-info-table-empty'); - } - - _createTag(details) { - const node = this._templates.instantiate('tag'); - - const inner = node.querySelector('.tag-inner'); - - node.title = details.notes; - this._setTextContent(inner, details.name); - node.dataset.details = details.notes || details.name; - node.dataset.category = details.category; - if (details.redundant) { node.dataset.redundant = 'true'; } - - return node; - } - - _createTermTag(details, totalExpressionCount) { - const {tag, expressions} = details; - const node = this._createTag(tag); - node.dataset.disambiguation = `${JSON.stringify(expressions)}`; - node.dataset.totalExpressionCount = `${totalExpressionCount}`; - node.dataset.matchedExpressionCount = `${expressions.length}`; - node.dataset.unmatchedExpressionCount = `${Math.max(0, totalExpressionCount - expressions.length)}`; - return node; - } - - _createSearchTag(text) { - return this._createTag({ - notes: '', - name: text, - category: 'search', - redundant: false - }); - } - - _createPitches(details) { - this.preparePitchAccents(); - - const {dictionary, pitches} = details; - - const node = this._templates.instantiate('term-pitch-accent-group'); - node.dataset.dictionary = dictionary; - node.dataset.pitchesMulti = 'true'; - node.dataset.pitchesCount = `${pitches.length}`; - - const tag = this._createTag({notes: '', name: dictionary, category: 'pitch-accent-dictionary'}); - node.querySelector('.term-pitch-accent-group-tag-list').appendChild(tag); - - let hasTags = false; - for (const {tags} of pitches) { - if (tags.length > 0) { - hasTags = true; - break; - } - } - - const n = node.querySelector('.term-pitch-accent-list'); - n.dataset.hasTags = `${hasTags}`; - this._appendMultiple(n, this._createPitch.bind(this), pitches); - - return node; - } - - _createPitch(details) { - const jp = this._japaneseUtil; - const {reading, position, tags, exclusiveExpressions, exclusiveReadings} = details; - const morae = jp.getKanaMorae(reading); - - const node = this._templates.instantiate('term-pitch-accent'); - - node.dataset.pitchAccentPosition = `${position}`; - node.dataset.tagCount = `${tags.length}`; - - let n = node.querySelector('.term-pitch-accent-position'); - this._setTextContent(n, `${position}`, ''); - - n = node.querySelector('.term-pitch-accent-tag-list'); - this._appendMultiple(n, this._createTag.bind(this), tags); - - n = node.querySelector('.term-pitch-accent-disambiguation-list'); - this._createPitchAccentDisambiguations(n, exclusiveExpressions, exclusiveReadings); - - n = node.querySelector('.term-pitch-accent-characters'); - for (let i = 0, ii = morae.length; i < ii; ++i) { - const mora = morae[i]; - const highPitch = jp.isMoraPitchHigh(i, position); - const highPitchNext = jp.isMoraPitchHigh(i + 1, position); - - const n1 = this._templates.instantiate('term-pitch-accent-character'); - const n2 = n1.querySelector('.term-pitch-accent-character-inner'); - - n1.dataset.position = `${i}`; - n1.dataset.pitch = highPitch ? 'high' : 'low'; - n1.dataset.pitchNext = highPitchNext ? 'high' : 'low'; - this._setTextContent(n2, mora, 'ja'); - - n.appendChild(n1); - } - - if (morae.length > 0) { - this._populatePitchGraph(node.querySelector('.term-pitch-accent-graph'), position, morae); - } - - return node; - } - - _createPitchAccentDisambiguations(container, exclusiveExpressions, exclusiveReadings) { - const templateName = 'term-pitch-accent-disambiguation'; - for (const exclusiveExpression of exclusiveExpressions) { - const node = this._templates.instantiate(templateName); - node.dataset.type = 'expression'; - this._setTextContent(node, exclusiveExpression, 'ja'); - container.appendChild(node); - } - - for (const exclusiveReading of exclusiveReadings) { - const node = this._templates.instantiate(templateName); - node.dataset.type = 'reading'; - this._setTextContent(node, exclusiveReading, 'ja'); - container.appendChild(node); - } - - container.dataset.count = `${exclusiveExpressions.length + exclusiveReadings.length}`; - container.dataset.expressionCount = `${exclusiveExpressions.length}`; - container.dataset.readingCount = `${exclusiveReadings.length}`; - } - - _populatePitchGraph(svg, position, morae) { - const jp = this._japaneseUtil; - const svgns = svg.getAttribute('xmlns'); - const ii = morae.length; - svg.setAttribute('viewBox', `0 0 ${50 * (ii + 1)} 100`); - - const pathPoints = []; - for (let i = 0; i < ii; ++i) { - const highPitch = jp.isMoraPitchHigh(i, position); - const highPitchNext = jp.isMoraPitchHigh(i + 1, position); - const graphic = (highPitch && !highPitchNext ? '#term-pitch-accent-graph-dot-downstep' : '#term-pitch-accent-graph-dot'); - const x = `${i * 50 + 25}`; - const y = highPitch ? '25' : '75'; - const use = document.createElementNS(svgns, 'use'); - use.setAttribute('href', graphic); - use.setAttribute('x', x); - use.setAttribute('y', y); - svg.appendChild(use); - pathPoints.push(`${x} ${y}`); - } - - let path = svg.querySelector('.term-pitch-accent-graph-line'); - path.setAttribute('d', `M${pathPoints.join(' L')}`); - - pathPoints.splice(0, ii - 1); - { - const highPitch = jp.isMoraPitchHigh(ii, position); - const x = `${ii * 50 + 25}`; - const y = highPitch ? '25' : '75'; - const use = document.createElementNS(svgns, 'use'); - use.setAttribute('href', '#term-pitch-accent-graph-triangle'); - use.setAttribute('x', x); - use.setAttribute('y', y); - svg.appendChild(use); - pathPoints.push(`${x} ${y}`); - } - - path = svg.querySelector('.term-pitch-accent-graph-line-tail'); - path.setAttribute('d', `M${pathPoints.join(' L')}`); - } - - _createFrequencyGroup(details, kanji) { - const {dictionary, frequencyData} = details; - const node = this._templates.instantiate('frequency-group-item'); - - const tagList = node.querySelector('.frequency-tag-list'); - const tag = this._createTag({notes: '', name: dictionary, category: 'frequency'}); - tagList.appendChild(tag); - - const frequencyListContainer = node.querySelector('.frequency-list'); - const createItem = (kanji ? this._createKanjiFrequency.bind(this) : this._createTermFrequency.bind(this)); - this._appendMultiple(frequencyListContainer, createItem, frequencyData, dictionary); - - node.dataset.count = `${frequencyData.length}`; - - return node; - } - - _createTermFrequency(details, dictionary) { - const {expression, reading, frequencies} = details; - const node = this._templates.instantiate('term-frequency-item'); - - const frequency = frequencies.join(', '); - - this._setTextContent(node.querySelector('.frequency-disambiguation-expression'), expression, 'ja'); - this._setTextContent(node.querySelector('.frequency-disambiguation-reading'), (reading !== null ? reading : ''), 'ja'); - this._setTextContent(node.querySelector('.frequency-value'), frequency, 'ja'); - - node.dataset.expression = expression; - node.dataset.reading = reading; - node.dataset.hasReading = `${reading !== null}`; - node.dataset.readingIsSame = `${reading === expression}`; - node.dataset.dictionary = dictionary; - node.dataset.frequency = `${frequency}`; - - return node; - } - - _createKanjiFrequency(details, dictionary) { - const {character, frequencies} = details; - const node = this._templates.instantiate('kanji-frequency-item'); - - const frequency = frequencies.join(', '); - - this._setTextContent(node.querySelector('.frequency-value'), frequency, 'ja'); - - node.dataset.character = character; - node.dataset.dictionary = dictionary; - node.dataset.frequency = `${frequency}`; - - return node; - } - - _appendKanjiLinks(container, text) { - container.lang = 'ja'; - const jp = this._japaneseUtil; - let part = ''; - for (const c of text) { - if (jp.isCodePointKanji(c.codePointAt(0))) { - if (part.length > 0) { - container.appendChild(document.createTextNode(part)); - part = ''; - } - - const link = this._createKanjiLink(c); - container.appendChild(link); - } else { - part += c; - } - } - if (part.length > 0) { - container.appendChild(document.createTextNode(part)); - } - } - - _appendMultiple(container, createItem, detailsArray, ...args) { - let count = 0; - const {ELEMENT_NODE} = Node; - if (Array.isArray(detailsArray)) { - for (const details of detailsArray) { - const item = createItem(details, ...args); - if (item === null) { continue; } - container.appendChild(item); - if (item.nodeType === ELEMENT_NODE) { - item.dataset.index = `${count}`; - } - ++count; - } - } - - container.dataset.count = `${count}`; - - return count; - } - - _appendFurigana(container, segments, addText) { - for (const {text, furigana} of segments) { - if (furigana) { - const ruby = document.createElement('ruby'); - const rt = document.createElement('rt'); - addText(ruby, text); - ruby.appendChild(rt); - rt.appendChild(document.createTextNode(furigana)); - container.appendChild(ruby); - } else { - addText(container, text); - } - } - } - - _createDictionaryTag(dictionary) { - return { - name: dictionary, - category: 'dictionary', - notes: '', - order: 100, - score: 0, - dictionary, - redundant: false - }; - } - - _setTextContent(node, value, language) { - node.textContent = value; - if (typeof language === 'string') { - node.lang = language; - } else if (this._japaneseUtil.isStringPartiallyJapanese(value)) { - node.lang = 'ja'; - } - } -} |