diff options
37 files changed, 577 insertions, 541 deletions
diff --git a/.eslintrc.json b/.eslintrc.json index fcc6995b..4ee1f982 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -22,6 +22,7 @@ "dot-notation": "error", "eqeqeq": "error", "func-names": ["error", "always"], + "guard-for-in": "error", "no-case-declarations": "error", "no-const-assign": "error", "no-constant-condition": "off", @@ -73,7 +74,7 @@ }, "overrides": [ { - "files": ["*.js"], + "files": ["ext/**/*.js"], "excludedFiles": ["ext/mixed/js/core.js"], "globals": { "yomichan": "readonly", diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index 0ecfd5c4..972e2b8b 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -52,7 +52,7 @@ const audioUrlBuilders = new Map([ for (const row of dom.getElementsByClassName('dc-result-row')) { try { const url = row.querySelector('audio>source[src]').getAttribute('src'); - const reading = row.getElementsByClassName('dc-vocab_kana').item(0).innerText; + const reading = row.getElementsByClassName('dc-vocab_kana').item(0).textContent; if (url && reading && (!definition.reading || definition.reading === reading)) { return audioUrlNormalize(url, 'https://www.japanesepod101.com', '/learningcenter/reference/'); } @@ -156,8 +156,8 @@ function audioBuildFilename(definition) { async function audioInject(definition, fields, sources, optionsContext) { let usesAudio = false; - for (const name in fields) { - if (fields[name].includes('{audio}')) { + for (const fieldValue of Object.values(fields)) { + if (fieldValue.includes('{audio}')) { usesAudio = true; break; } diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 2691b7d9..238ac52c 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -48,6 +48,41 @@ class Backend { this.apiForwarder = new BackendApiForwarder(); this.messageToken = yomichan.generateId(16); + + this._messageHandlers = new Map([ + ['optionsSchemaGet', this._onApiOptionsSchemaGet.bind(this)], + ['optionsGet', this._onApiOptionsGet.bind(this)], + ['optionsGetFull', this._onApiOptionsGetFull.bind(this)], + ['optionsSet', this._onApiOptionsSet.bind(this)], + ['optionsSave', this._onApiOptionsSave.bind(this)], + ['kanjiFind', this._onApiKanjiFind.bind(this)], + ['termsFind', this._onApiTermsFind.bind(this)], + ['textParse', this._onApiTextParse.bind(this)], + ['textParseMecab', this._onApiTextParseMecab.bind(this)], + ['definitionAdd', this._onApiDefinitionAdd.bind(this)], + ['definitionsAddable', this._onApiDefinitionsAddable.bind(this)], + ['noteView', this._onApiNoteView.bind(this)], + ['templateRender', this._onApiTemplateRender.bind(this)], + ['commandExec', this._onApiCommandExec.bind(this)], + ['audioGetUrl', this._onApiAudioGetUrl.bind(this)], + ['screenshotGet', this._onApiScreenshotGet.bind(this)], + ['forward', this._onApiForward.bind(this)], + ['frameInformationGet', this._onApiFrameInformationGet.bind(this)], + ['injectStylesheet', this._onApiInjectStylesheet.bind(this)], + ['getEnvironmentInfo', this._onApiGetEnvironmentInfo.bind(this)], + ['clipboardGet', this._onApiClipboardGet.bind(this)], + ['getDisplayTemplatesHtml', this._onApiGetDisplayTemplatesHtml.bind(this)], + ['getQueryParserTemplatesHtml', this._onApiGetQueryParserTemplatesHtml.bind(this)], + ['getZoom', this._onApiGetZoom.bind(this)], + ['getMessageToken', this._onApiGetMessageToken.bind(this)] + ]); + + this._commandHandlers = new Map([ + ['search', this._onCommandSearch.bind(this)], + ['help', this._onCommandHelp.bind(this)], + ['options', this._onCommandOptions.bind(this)], + ['toggle', this._onCommandToggle.bind(this)] + ]); } async prepare() { @@ -65,10 +100,10 @@ class Backend { this.onOptionsUpdated('background'); if (isObject(chrome.commands) && isObject(chrome.commands.onCommand)) { - chrome.commands.onCommand.addListener((command) => this._runCommand(command)); + chrome.commands.onCommand.addListener(this._runCommand.bind(this)); } if (isObject(chrome.tabs) && isObject(chrome.tabs.onZoomChange)) { - chrome.tabs.onZoomChange.addListener((info) => this._onZoomChange(info)); + chrome.tabs.onZoomChange.addListener(this._onZoomChange.bind(this)); } chrome.runtime.onMessage.addListener(this.onMessage.bind(this)); @@ -81,7 +116,7 @@ class Backend { this.isPreparedResolve = null; this.isPreparedPromise = null; - this.clipboardMonitor.onClipboardText = (text) => this._onClipboardText(text); + this.clipboardMonitor.onClipboardText = this._onClipboardText.bind(this); } onOptionsUpdated(source) { @@ -96,11 +131,11 @@ class Backend { } onMessage({action, params}, sender, callback) { - const handler = Backend._messageHandlers.get(action); + const handler = this._messageHandlers.get(action); if (typeof handler !== 'function') { return false; } try { - const promise = handler(this, params, sender); + const promise = handler(params, sender); promise.then( (result) => callback({result}), (error) => callback({error: errorToJson(error)}) @@ -243,10 +278,10 @@ class Backend { } _runCommand(command, params) { - const handler = Backend._commandHandlers.get(command); + const handler = this._commandHandlers.get(command); if (typeof handler !== 'function') { return false; } - handler(this, params); + handler(params); return true; } @@ -357,11 +392,11 @@ class Backend { async _onApiTextParseMecab({text, optionsContext}) { const options = await this.getOptions(optionsContext); - const results = {}; + const results = []; const rawResults = await this.mecab.parseText(text); - for (const mecabName in rawResults) { + for (const [mecabName, parsedLines] of Object.entries(rawResults)) { const result = []; - for (const parsedLine of rawResults[mecabName]) { + for (const parsedLine of parsedLines) { for (const {expression, reading, source} of parsedLine) { const term = []; if (expression !== null && reading !== null) { @@ -381,7 +416,7 @@ class Backend { } result.push([{text: '\n'}]); } - results[mecabName] = result; + results.push([mecabName, result]); } return results; } @@ -693,9 +728,10 @@ class Backend { } _onCommandOptions(params) { - if (!(params && params.newTab)) { + const {mode='existingOrNewTab'} = params || {}; + if (mode === 'existingOrNewTab') { chrome.runtime.openOptionsPage(); - } else { + } else if (mode === 'newTab') { const manifest = chrome.runtime.getManifest(); const url = chrome.runtime.getURL(manifest.options_ui.page); chrome.tabs.create({url}); @@ -718,8 +754,8 @@ class Backend { async _injectScreenshot(definition, fields, screenshot) { let usesScreenshot = false; - for (const name in fields) { - if (fields[name].includes('{screenshot}')) { + for (const fieldValue of Object.values(fields)) { + if (fieldValue.includes('{screenshot}')) { usesScreenshot = true; break; } @@ -802,7 +838,7 @@ class Backend { chrome.tabs.update(tab.id, {active: true}, () => { const e = chrome.runtime.lastError; if (e) { - reject(e); + reject(new Error(e.message)); } else { resolve(); } @@ -819,7 +855,7 @@ class Backend { chrome.windows.get(tab.windowId, {}, (value) => { const e = chrome.runtime.lastError; if (e) { - reject(e); + reject(new Error(e.message)); } else { resolve(value); } @@ -830,7 +866,7 @@ class Backend { chrome.windows.update(tab.windowId, {focused: true}, () => { const e = chrome.runtime.lastError; if (e) { - reject(e); + reject(new Error(e.message)); } else { resolve(); } @@ -867,40 +903,5 @@ class Backend { } } -Backend._messageHandlers = new Map([ - ['optionsSchemaGet', (self, ...args) => self._onApiOptionsSchemaGet(...args)], - ['optionsGet', (self, ...args) => self._onApiOptionsGet(...args)], - ['optionsGetFull', (self, ...args) => self._onApiOptionsGetFull(...args)], - ['optionsSet', (self, ...args) => self._onApiOptionsSet(...args)], - ['optionsSave', (self, ...args) => self._onApiOptionsSave(...args)], - ['kanjiFind', (self, ...args) => self._onApiKanjiFind(...args)], - ['termsFind', (self, ...args) => self._onApiTermsFind(...args)], - ['textParse', (self, ...args) => self._onApiTextParse(...args)], - ['textParseMecab', (self, ...args) => self._onApiTextParseMecab(...args)], - ['definitionAdd', (self, ...args) => self._onApiDefinitionAdd(...args)], - ['definitionsAddable', (self, ...args) => self._onApiDefinitionsAddable(...args)], - ['noteView', (self, ...args) => self._onApiNoteView(...args)], - ['templateRender', (self, ...args) => self._onApiTemplateRender(...args)], - ['commandExec', (self, ...args) => self._onApiCommandExec(...args)], - ['audioGetUrl', (self, ...args) => self._onApiAudioGetUrl(...args)], - ['screenshotGet', (self, ...args) => self._onApiScreenshotGet(...args)], - ['forward', (self, ...args) => self._onApiForward(...args)], - ['frameInformationGet', (self, ...args) => self._onApiFrameInformationGet(...args)], - ['injectStylesheet', (self, ...args) => self._onApiInjectStylesheet(...args)], - ['getEnvironmentInfo', (self, ...args) => self._onApiGetEnvironmentInfo(...args)], - ['clipboardGet', (self, ...args) => self._onApiClipboardGet(...args)], - ['getDisplayTemplatesHtml', (self, ...args) => self._onApiGetDisplayTemplatesHtml(...args)], - ['getQueryParserTemplatesHtml', (self, ...args) => self._onApiGetQueryParserTemplatesHtml(...args)], - ['getZoom', (self, ...args) => self._onApiGetZoom(...args)], - ['getMessageToken', (self, ...args) => self._onApiGetMessageToken(...args)] -]); - -Backend._commandHandlers = new Map([ - ['search', (self, ...args) => self._onCommandSearch(...args)], - ['help', (self, ...args) => self._onCommandHelp(...args)], - ['options', (self, ...args) => self._onCommandOptions(...args)], - ['toggle', (self, ...args) => self._onCommandToggle(...args)] -]); - window.yomichanBackend = new Backend(); window.yomichanBackend.prepare(); diff --git a/ext/bg/js/deinflector.js b/ext/bg/js/deinflector.js index e2ced965..d548d271 100644 --- a/ext/bg/js/deinflector.js +++ b/ext/bg/js/deinflector.js @@ -57,9 +57,9 @@ class Deinflector { static normalizeReasons(reasons) { const normalizedReasons = []; - for (const reason in reasons) { + for (const [reason, reasonInfo] of Object.entries(reasons)) { const variants = []; - for (const {kanaIn, kanaOut, rulesIn, rulesOut} of reasons[reason]) { + for (const {kanaIn, kanaOut, rulesIn, rulesOut} of reasonInfo) { variants.push([ kanaIn, kanaOut, diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js index f5c5b21b..ffeac80a 100644 --- a/ext/bg/js/dictionary.js +++ b/ext/bg/js/dictionary.js @@ -20,22 +20,16 @@ function dictEnabledSet(options) { const enabledDictionaryMap = new Map(); - const optionsDictionaries = options.dictionaries; - for (const title in optionsDictionaries) { - if (!hasOwn(optionsDictionaries, title)) { continue; } - const dictionary = optionsDictionaries[title]; - if (!dictionary.enabled) { continue; } - enabledDictionaryMap.set(title, { - priority: dictionary.priority || 0, - allowSecondarySearches: !!dictionary.allowSecondarySearches - }); + for (const [title, {enabled, priority, allowSecondarySearches}] of Object.entries(options.dictionaries)) { + if (!enabled) { continue; } + enabledDictionaryMap.set(title, {priority, allowSecondarySearches}); } return enabledDictionaryMap; } function dictConfigured(options) { - for (const title in options.dictionaries) { - if (options.dictionaries[title].enabled) { + for (const {enabled} of Object.values(options.dictionaries)) { + if (enabled) { return true; } } @@ -388,40 +382,39 @@ dictFieldFormat.markers = new Set([ ]); async function dictNoteFormat(definition, mode, options, templates) { - const note = {fields: {}, tags: options.anki.tags}; - let fields = []; - - if (mode === 'kanji') { - fields = options.anki.kanji.fields; - note.deckName = options.anki.kanji.deck; - note.modelName = options.anki.kanji.model; - } else { - fields = options.anki.terms.fields; - note.deckName = options.anki.terms.deck; - note.modelName = options.anki.terms.model; - - if (definition.audio) { - const audio = { - url: definition.audio.url, - filename: definition.audio.filename, - skipHash: '7e2c2f954ef6051373ba916f000168dc', - fields: [] - }; + const isKanji = (mode === 'kanji'); + const tags = options.anki.tags; + const modeOptions = isKanji ? options.anki.kanji : options.anki.terms; + const modeOptionsFieldEntries = Object.entries(modeOptions.fields); + + const note = { + fields: {}, + tags, + deckName: modeOptions.deck, + modelName: modeOptions.model + }; - for (const name in fields) { - if (fields[name].includes('{audio}')) { - audio.fields.push(name); - } - } + for (const [fieldName, fieldValue] of modeOptionsFieldEntries) { + note.fields[fieldName] = await dictFieldFormat(fieldValue, definition, mode, options, templates); + } + + if (!isKanji && definition.audio) { + const audioFields = []; - if (audio.fields.length > 0) { - note.audio = audio; + for (const [fieldName, fieldValue] of modeOptionsFieldEntries) { + if (fieldValue.includes('{audio}')) { + audioFields.push(fieldName); } } - } - for (const name in fields) { - note.fields[name] = await dictFieldFormat(fields[name], definition, mode, options, templates); + if (audioFields.length > 0) { + note.audio = { + url: definition.audio.url, + filename: definition.audio.filename, + skipHash: '7e2c2f954ef6051373ba916f000168dc', + fields: audioFields + }; + } } return note; diff --git a/ext/bg/js/search-query-parser-generator.js b/ext/bg/js/search-query-parser-generator.js index f842644e..1ab23a82 100644 --- a/ext/bg/js/search-query-parser-generator.js +++ b/ext/bg/js/search-query-parser-generator.js @@ -50,7 +50,7 @@ class QueryParserGenerator { const segmentTextContainer = segmentContainer.querySelector('.query-parser-segment-text'); const segmentReadingContainer = segmentContainer.querySelector('.query-parser-segment-reading'); segmentTextContainer.appendChild(this.createSegmentText(segment.text)); - segmentReadingContainer.innerText = segment.reading; + segmentReadingContainer.textContent = segment.reading; return segmentContainer; } @@ -58,7 +58,7 @@ class QueryParserGenerator { const fragment = document.createDocumentFragment(); for (const chr of text) { const charContainer = this._templateHandler.instantiate('char'); - charContainer.innerText = chr; + charContainer.textContent = chr; fragment.appendChild(charContainer); } return fragment; @@ -69,7 +69,7 @@ class QueryParserGenerator { for (const parseResult of parseResults) { const optionContainer = this._templateHandler.instantiate('select-option'); optionContainer.value = parseResult.id; - optionContainer.innerText = parseResult.name; + optionContainer.textContent = parseResult.name; optionContainer.defaultSelected = selectedParser === parseResult.id; selectContainer.appendChild(optionContainer); } diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js index 8c434990..11c7baa2 100644 --- a/ext/bg/js/search-query-parser.js +++ b/ext/bg/js/search-query-parser.js @@ -142,11 +142,11 @@ class QueryParser extends TextScanner { } if (this.search.options.parsing.enableMecabParser) { const mecabResults = await apiTextParseMecab(text, this.search.getOptionsContext()); - for (const mecabDictName in mecabResults) { + for (const [mecabDictName, mecabDictResults] of mecabResults) { results.push({ name: `MeCab: ${mecabDictName}`, id: `mecab-${mecabDictName}`, - parsedText: mecabResults[mecabDictName] + parsedText: mecabDictResults }); } } @@ -164,7 +164,7 @@ class QueryParser extends TextScanner { } renderParserSelect() { - this.queryParserSelect.innerHTML = ''; + this.queryParserSelect.textContent = ''; if (this.parseResults.length > 1) { const select = this.queryParserGenerator.createParserSelect(this.parseResults, this.selectedParser); select.addEventListener('change', this.onParserChange.bind(this)); diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 98e167ad..0a7a5fe1 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -39,6 +39,25 @@ class DisplaySearch extends Display { this.introAnimationTimer = null; this.clipboardMonitor = new ClipboardMonitor(); + + this._onKeyDownIgnoreKeys = new Map([ + ['ANY_MOD', new Set([ + 'Tab', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'PageDown', 'PageUp', 'Home', 'End', + 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', + 'F11', 'F12', 'F13', 'F14', 'F15', 'F16', 'F17', 'F18', 'F19', 'F20', + 'F21', 'F22', 'F23', 'F24' + ])], + ['Control', new Set(['C', 'A', 'Z', 'Y', 'X', 'F', 'G'])], + ['Meta', new Set(['C', 'A', 'Z', 'Y', 'X', 'F', 'G'])], + ['OS', new Set()], + ['Alt', new Set()], + ['AltGraph', new Set()], + ['Shift', new Set()] + ]); + + this._runtimeMessageHandlers = new Map([ + ['searchQueryUpdate', ({query}) => { this.onExternalSearchUpdate(query); }] + ]); } static create() { @@ -55,70 +74,37 @@ class DisplaySearch extends Display { const {queryParams: {query='', mode=''}} = parseUrl(window.location.href); - if (this.search !== null) { - this.search.addEventListener('click', (e) => this.onSearch(e), false); - } - if (this.query !== null) { - document.documentElement.dataset.searchMode = mode; - this.query.addEventListener('input', () => this.onSearchInput(), false); - - if (this.wanakanaEnable !== null) { - if (this.options.general.enableWanakana === true) { - this.wanakanaEnable.checked = true; - window.wanakana.bind(this.query); - } else { - this.wanakanaEnable.checked = false; - } - this.wanakanaEnable.addEventListener('change', (e) => { - const {queryParams: {query: query2=''}} = parseUrl(window.location.href); - if (e.target.checked) { - window.wanakana.bind(this.query); - apiOptionsSet({general: {enableWanakana: true}}, this.getOptionsContext()); - } else { - window.wanakana.unbind(this.query); - apiOptionsSet({general: {enableWanakana: false}}, this.getOptionsContext()); - } - this.setQuery(query2); - this.onSearchQueryUpdated(this.query.value, false); - }); - } + document.documentElement.dataset.searchMode = mode; - this.setQuery(query); - this.onSearchQueryUpdated(this.query.value, false); + if (this.options.general.enableWanakana === true) { + this.wanakanaEnable.checked = true; + window.wanakana.bind(this.query); + } else { + this.wanakanaEnable.checked = false; } - if (this.clipboardMonitorEnable !== null && mode !== 'popup') { + + this.setQuery(query); + this.onSearchQueryUpdated(this.query.value, false); + + if (mode !== 'popup') { if (this.options.general.enableClipboardMonitor === true) { this.clipboardMonitorEnable.checked = true; this.clipboardMonitor.start(); } else { this.clipboardMonitorEnable.checked = false; } - this.clipboardMonitorEnable.addEventListener('change', (e) => { - if (e.target.checked) { - chrome.permissions.request( - {permissions: ['clipboardRead']}, - (granted) => { - if (granted) { - this.clipboardMonitor.start(); - apiOptionsSet({general: {enableClipboardMonitor: true}}, this.getOptionsContext()); - } else { - e.target.checked = false; - } - } - ); - } else { - this.clipboardMonitor.stop(); - apiOptionsSet({general: {enableClipboardMonitor: false}}, this.getOptionsContext()); - } - }); + this.clipboardMonitorEnable.addEventListener('change', this.onClipboardMonitorEnableChange.bind(this)); } chrome.runtime.onMessage.addListener(this.onRuntimeMessage.bind(this)); - window.addEventListener('popstate', (e) => this.onPopState(e)); - window.addEventListener('copy', (e) => this.onCopy(e)); + this.search.addEventListener('click', this.onSearch.bind(this), false); + this.query.addEventListener('input', this.onSearchInput.bind(this), false); + this.wanakanaEnable.addEventListener('change', this.onWanakanaEnableChange.bind(this)); + window.addEventListener('popstate', this.onPopState.bind(this)); + window.addEventListener('copy', this.onCopy.bind(this)); - this.clipboardMonitor.onClipboardText = (text) => this.onExternalSearchUpdate(text); + this.clipboardMonitor.onClipboardText = this.onExternalSearchUpdate.bind(this); this.updateSearchButton(); } catch (e) { @@ -174,28 +160,30 @@ class DisplaySearch extends Display { } onRuntimeMessage({action, params}, sender, callback) { - const handler = DisplaySearch._runtimeMessageHandlers.get(action); + const handler = this._runtimeMessageHandlers.get(action); if (typeof handler !== 'function') { return false; } - const result = handler(this, params, sender); + const result = handler(params, sender); callback(result); return false; } onKeyDown(e) { const key = Display.getKeyFromEvent(e); - const ignoreKeys = DisplaySearch.onKeyDownIgnoreKeys; + const ignoreKeys = this._onKeyDownIgnoreKeys; - const activeModifierMap = { - 'Control': e.ctrlKey, - 'Meta': e.metaKey, - 'ANY_MOD': true - }; + const activeModifierMap = new Map([ + ['Control', e.ctrlKey], + ['Meta', e.metaKey], + ['Shift', e.shiftKey], + ['Alt', e.altKey], + ['ANY_MOD', true] + ]); let preventFocus = false; - for (const [modifier, keys] of Object.entries(ignoreKeys)) { - const modifierActive = activeModifierMap[modifier]; - if (key === modifier || (modifierActive && keys.includes(key))) { + for (const [modifier, keys] of ignoreKeys.entries()) { + const modifierActive = activeModifierMap.get(modifier); + if (key === modifier || (modifierActive && keys.has(key))) { preventFocus = true; break; } @@ -253,6 +241,38 @@ class DisplaySearch extends Display { } } + onWanakanaEnableChange(e) { + const {queryParams: {query=''}} = parseUrl(window.location.href); + const enableWanakana = e.target.checked; + if (enableWanakana) { + window.wanakana.bind(this.query); + } else { + window.wanakana.unbind(this.query); + } + this.setQuery(query); + this.onSearchQueryUpdated(this.query.value, false); + apiOptionsSet({general: {enableWanakana}}, this.getOptionsContext()); + } + + onClipboardMonitorEnableChange(e) { + if (e.target.checked) { + chrome.permissions.request( + {permissions: ['clipboardRead']}, + (granted) => { + if (granted) { + this.clipboardMonitor.start(); + apiOptionsSet({general: {enableClipboardMonitor: true}}, this.getOptionsContext()); + } else { + e.target.checked = false; + } + } + ); + } else { + this.clipboardMonitor.stop(); + apiOptionsSet({general: {enableClipboardMonitor: false}}, this.getOptionsContext()); + } + } + async updateOptions(options) { await super.updateOptions(options); this.queryParser.setOptions(this.options); @@ -346,23 +366,4 @@ class DisplaySearch extends Display { } } -DisplaySearch.onKeyDownIgnoreKeys = { - 'ANY_MOD': [ - 'Tab', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'PageDown', 'PageUp', 'Home', 'End', - 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', - 'F11', 'F12', 'F13', 'F14', 'F15', 'F16', 'F17', 'F18', 'F19', 'F20', - 'F21', 'F22', 'F23', 'F24' - ], - 'Control': ['C', 'A', 'Z', 'Y', 'X', 'F', 'G'], - 'Meta': ['C', 'A', 'Z', 'Y', 'X', 'F', 'G'], - 'OS': [], - 'Alt': [], - 'AltGraph': [], - 'Shift': [] -}; - -DisplaySearch._runtimeMessageHandlers = new Map([ - ['searchQueryUpdate', (self, {query}) => { self.onExternalSearchUpdate(query); }] -]); - DisplaySearch.instance = DisplaySearch.create(); diff --git a/ext/bg/js/settings/anki-templates.js b/ext/bg/js/settings/anki-templates.js index 2e80e334..770716d4 100644 --- a/ext/bg/js/settings/anki-templates.js +++ b/ext/bg/js/settings/anki-templates.js @@ -45,10 +45,10 @@ function ankiTemplatesInitialize() { node.addEventListener('click', onAnkiTemplateMarkerClicked, false); } - $('#field-templates').on('change', (e) => onAnkiFieldTemplatesChanged(e)); - $('#field-template-render').on('click', (e) => onAnkiTemplateRender(e)); - $('#field-templates-reset').on('click', (e) => onAnkiFieldTemplatesReset(e)); - $('#field-templates-reset-confirm').on('click', (e) => onAnkiFieldTemplatesResetConfirm(e)); + $('#field-templates').on('change', onAnkiFieldTemplatesChanged); + $('#field-template-render').on('click', onAnkiTemplateRender); + $('#field-templates-reset').on('click', onAnkiFieldTemplatesReset); + $('#field-templates-reset-confirm').on('click', onAnkiFieldTemplatesResetConfirm); ankiTemplatesUpdateValue(); } diff --git a/ext/bg/js/settings/anki.js b/ext/bg/js/settings/anki.js index 4263fc51..782691ab 100644 --- a/ext/bg/js/settings/anki.js +++ b/ext/bg/js/settings/anki.js @@ -154,10 +154,10 @@ async function _ankiFieldsPopulate(tabId, options) { container.appendChild(fragment); for (const node of container.querySelectorAll('.anki-field-value')) { - node.addEventListener('change', (e) => onFormOptionsChanged(e), false); + node.addEventListener('change', onFormOptionsChanged, false); } for (const node of container.querySelectorAll('.marker-link')) { - node.addEventListener('click', (e) => _onAnkiMarkerClicked(e), false); + node.addEventListener('click', _onAnkiMarkerClicked, false); } } @@ -267,7 +267,7 @@ function ankiGetFieldMarkers(type) { function ankiInitialize() { for (const node of document.querySelectorAll('#anki-terms-model,#anki-kanji-model')) { - node.addEventListener('change', (e) => _onAnkiModelChanged(e), false); + node.addEventListener('change', _onAnkiModelChanged, false); } } diff --git a/ext/bg/js/settings/audio-ui.js b/ext/bg/js/settings/audio-ui.js index 555380b4..206539a4 100644 --- a/ext/bg/js/settings/audio-ui.js +++ b/ext/bg/js/settings/audio-ui.js @@ -37,7 +37,7 @@ AudioSourceUI.Container = class Container { this.children.push(new AudioSourceUI.AudioSource(this, audioSource, this.children.length)); } - this._clickListener = () => this.onAddAudioSource(); + this._clickListener = this.onAddAudioSource.bind(this); this.addButton.addEventListener('click', this._clickListener, false); } @@ -105,8 +105,8 @@ AudioSourceUI.AudioSource = class AudioSource { this.select.value = audioSource; - this._selectChangeListener = () => this.onSelectChanged(); - this._removeClickListener = () => this.onRemoveClicked(); + this._selectChangeListener = this.onSelectChanged.bind(this); + this._removeClickListener = this.onRemoveClicked.bind(this); this.select.addEventListener('change', this._selectChangeListener, false); this.removeButton.addEventListener('click', this._removeClickListener, false); diff --git a/ext/bg/js/settings/audio.js b/ext/bg/js/settings/audio.js index 588d9a11..6d183a43 100644 --- a/ext/bg/js/settings/audio.js +++ b/ext/bg/js/settings/audio.js @@ -29,7 +29,7 @@ async function audioSettingsInitialize() { document.querySelector('.audio-source-list'), document.querySelector('.audio-source-add') ); - audioSourceUI.save = () => settingsSaveOptions(); + audioSourceUI.save = settingsSaveOptions; textToSpeechInitialize(); } @@ -37,11 +37,11 @@ async function audioSettingsInitialize() { function textToSpeechInitialize() { if (typeof speechSynthesis === 'undefined') { return; } - speechSynthesis.addEventListener('voiceschanged', () => updateTextToSpeechVoices(), false); + speechSynthesis.addEventListener('voiceschanged', updateTextToSpeechVoices, false); updateTextToSpeechVoices(); - document.querySelector('#text-to-speech-voice').addEventListener('change', (e) => onTextToSpeechVoiceChange(e), false); - document.querySelector('#text-to-speech-voice-test').addEventListener('click', () => textToSpeechTest(), false); + document.querySelector('#text-to-speech-voice').addEventListener('change', onTextToSpeechVoiceChange, false); + document.querySelector('#text-to-speech-voice-test').addEventListener('click', textToSpeechTest, false); } function updateTextToSpeechVoices() { diff --git a/ext/bg/js/settings/conditions-ui.js b/ext/bg/js/settings/conditions-ui.js index 5a271321..4ca86b07 100644 --- a/ext/bg/js/settings/conditions-ui.js +++ b/ext/bg/js/settings/conditions-ui.js @@ -41,7 +41,7 @@ ConditionsUI.Container = class Container { this.children.push(new ConditionsUI.ConditionGroup(this, conditionGroup)); } - this.addButton.on('click', () => this.onAddConditionGroup()); + this.addButton.on('click', this.onAddConditionGroup.bind(this)); } cleanup() { @@ -127,7 +127,7 @@ ConditionsUI.ConditionGroup = class ConditionGroup { this.children.push(new ConditionsUI.Condition(this, condition)); } - this.addButton.on('click', () => this.onAddCondition()); + this.addButton.on('click', this.onAddCondition.bind(this)); } cleanup() { @@ -185,10 +185,10 @@ ConditionsUI.Condition = class Condition { this.updateOperators(); this.updateInput(); - this.input.on('change', () => this.onInputChanged()); - this.typeSelect.on('change', () => this.onConditionTypeChanged()); - this.operatorSelect.on('change', () => this.onConditionOperatorChanged()); - this.removeButton.on('click', () => this.onRemoveClicked()); + this.input.on('change', this.onInputChanged.bind(this)); + this.typeSelect.on('change', this.onConditionTypeChanged.bind(this)); + this.operatorSelect.on('change', this.onConditionOperatorChanged.bind(this)); + this.removeButton.on('click', this.onRemoveClicked.bind(this)); } cleanup() { @@ -235,10 +235,10 @@ ConditionsUI.Condition = class Condition { updateInput() { const conditionDescriptors = this.parent.parent.conditionDescriptors; const {type, operator} = this.condition; - const props = { - placeholder: '', - type: 'text' - }; + const props = new Map([ + ['placeholder', ''], + ['type', 'text'] + ]); const objects = []; if (hasOwn(conditionDescriptors, type)) { @@ -252,20 +252,20 @@ ConditionsUI.Condition = class Condition { for (const object of objects) { if (hasOwn(object, 'placeholder')) { - props.placeholder = object.placeholder; + props.set('placeholder', object.placeholder); } if (object.type === 'number') { - props.type = 'number'; + props.set('type', 'number'); for (const prop of ['step', 'min', 'max']) { if (hasOwn(object, prop)) { - props[prop] = object[prop]; + props.set(prop, object[prop]); } } } } - for (const prop in props) { - this.input.prop(prop, props[prop]); + for (const [prop, value] of props.entries()) { + this.input.prop(prop, value); } const {valid} = this.validateValue(this.condition.value); diff --git a/ext/bg/js/settings/dictionaries.js b/ext/bg/js/settings/dictionaries.js index 70a22a16..b9551073 100644 --- a/ext/bg/js/settings/dictionaries.js +++ b/ext/bg/js/settings/dictionaries.js @@ -36,7 +36,7 @@ class SettingsDictionaryListUI { this.dictionaryEntries = []; this.extra = null; - document.querySelector('#dict-delete-confirm').addEventListener('click', (e) => this.onDictionaryConfirmDelete(e), false); + document.querySelector('#dict-delete-confirm').addEventListener('click', this.onDictionaryConfirmDelete.bind(this), false); } setOptionsDictionaries(optionsDictionaries) { @@ -198,10 +198,10 @@ class SettingsDictionaryEntryUI { this.applyValues(); - this.eventListeners.addEventListener(this.enabledCheckbox, 'change', (e) => this.onEnabledChanged(e), false); - this.eventListeners.addEventListener(this.allowSecondarySearchesCheckbox, 'change', (e) => this.onAllowSecondarySearchesChanged(e), false); - this.eventListeners.addEventListener(this.priorityInput, 'change', (e) => this.onPriorityChanged(e), false); - this.eventListeners.addEventListener(this.deleteButton, 'click', (e) => this.onDeleteButtonClicked(e), false); + this.eventListeners.addEventListener(this.enabledCheckbox, 'change', this.onEnabledChanged.bind(this), false); + this.eventListeners.addEventListener(this.allowSecondarySearchesCheckbox, 'change', this.onAllowSecondarySearchesChanged.bind(this), false); + this.eventListeners.addEventListener(this.priorityInput, 'change', this.onPriorityChanged.bind(this), false); + this.eventListeners.addEventListener(this.deleteButton, 'click', this.onDeleteButtonClicked.bind(this), false); } cleanup() { @@ -341,14 +341,14 @@ async function dictSettingsInitialize() { document.querySelector('#dict-groups-extra'), document.querySelector('#dict-extra-template') ); - dictionaryUI.save = () => settingsSaveOptions(); - - document.querySelector('#dict-purge-button').addEventListener('click', (e) => onDictionaryPurgeButtonClick(e), false); - document.querySelector('#dict-purge-confirm').addEventListener('click', (e) => onDictionaryPurge(e), false); - document.querySelector('#dict-file-button').addEventListener('click', (e) => onDictionaryImportButtonClick(e), false); - document.querySelector('#dict-file').addEventListener('change', (e) => onDictionaryImport(e), false); - document.querySelector('#dict-main').addEventListener('change', (e) => onDictionaryMainChanged(e), false); - document.querySelector('#database-enable-prefix-wildcard-searches').addEventListener('change', (e) => onDatabaseEnablePrefixWildcardSearchesChanged(e), false); + dictionaryUI.save = settingsSaveOptions; + + document.querySelector('#dict-purge-button').addEventListener('click', onDictionaryPurgeButtonClick, false); + document.querySelector('#dict-purge-confirm').addEventListener('click', onDictionaryPurge, false); + document.querySelector('#dict-file-button').addEventListener('click', onDictionaryImportButtonClick, false); + document.querySelector('#dict-file').addEventListener('change', onDictionaryImport, false); + document.querySelector('#dict-main').addEventListener('change', onDictionaryMainChanged, false); + document.querySelector('#database-enable-prefix-wildcard-searches').addEventListener('change', onDatabaseEnablePrefixWildcardSearchesChanged, false); await onDictionaryOptionsChanged(); await onDatabaseUpdated(); diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js index c6683427..127a6d2b 100644 --- a/ext/bg/js/settings/main.js +++ b/ext/bg/js/settings/main.js @@ -68,7 +68,7 @@ async function formRead(options) { options.general.popupVerticalOffset = parseInt($('#popup-vertical-offset').val(), 10); options.general.popupHorizontalOffset2 = parseInt($('#popup-horizontal-offset2').val(), 0); options.general.popupVerticalOffset2 = parseInt($('#popup-vertical-offset2').val(), 10); - options.general.popupScalingFactor = parseInt($('#popup-scaling-factor').val(), 10); + options.general.popupScalingFactor = parseFloat($('#popup-scaling-factor').val()); options.general.popupScaleRelativeToPageZoom = $('#popup-scale-relative-to-page-zoom').prop('checked'); options.general.popupScaleRelativeToVisualViewport = $('#popup-scale-relative-to-visual-viewport').prop('checked'); options.general.popupTheme = $('#popup-theme').val(); @@ -200,7 +200,7 @@ async function formWrite(options) { } function formSetupEventListeners() { - $('input, select, textarea').not('.anki-model').not('.ignore-form-changes *').change((e) => onFormOptionsChanged(e)); + $('input, select, textarea').not('.anki-model').not('.ignore-form-changes *').change(onFormOptionsChanged); } function formUpdateVisibility(options) { diff --git a/ext/bg/js/settings/popup-preview-frame.js b/ext/bg/js/settings/popup-preview-frame.js index aa2b6100..1ceac177 100644 --- a/ext/bg/js/settings/popup-preview-frame.js +++ b/ext/bg/js/settings/popup-preview-frame.js @@ -28,6 +28,12 @@ class SettingsPopupPreview { this.themeChangeTimeout = null; this.textSource = null; this._targetOrigin = chrome.runtime.getURL('/').replace(/\/$/, ''); + + this._windowMessageHandlers = new Map([ + ['setText', ({text}) => this.setText(text)], + ['setCustomCss', ({css}) => this.setCustomCss(css)], + ['setCustomOuterCss', ({css}) => this.setCustomOuterCss(css)] + ]); } static create() { @@ -38,15 +44,12 @@ class SettingsPopupPreview { async prepare() { // Setup events - window.addEventListener('message', (e) => this.onMessage(e), false); + window.addEventListener('message', this.onMessage.bind(this), false); - const themeDarkCheckbox = document.querySelector('#theme-dark-checkbox'); - if (themeDarkCheckbox !== null) { - themeDarkCheckbox.addEventListener('change', () => this.onThemeDarkCheckboxChanged(themeDarkCheckbox), false); - } + document.querySelector('#theme-dark-checkbox').addEventListener('change', this.onThemeDarkCheckboxChanged.bind(this), false); // Overwrite API functions - window.apiOptionsGet = (...args) => this.apiOptionsGet(...args); + window.apiOptionsGet = this.apiOptionsGet.bind(this); // Overwrite frontend const popupHost = new PopupProxyHost(); @@ -56,7 +59,7 @@ class SettingsPopupPreview { this.popup.setChildrenSupported(false); this.popupSetCustomOuterCssOld = this.popup.setCustomOuterCss; - this.popup.setCustomOuterCss = (...args) => this.popupSetCustomOuterCss(...args); + this.popup.setCustomOuterCss = this.popupSetCustomOuterCss.bind(this); this.frontend = new Frontend(this.popup); @@ -101,14 +104,14 @@ class SettingsPopupPreview { if (e.origin !== this._targetOrigin) { return; } const {action, params} = e.data; - const handler = SettingsPopupPreview._messageHandlers.get(action); + const handler = this._windowMessageHandlers.get(action); if (typeof handler !== 'function') { return; } - handler(this, params); + handler(params); } - onThemeDarkCheckboxChanged(node) { - document.documentElement.classList.toggle('dark', node.checked); + onThemeDarkCheckboxChanged(e) { + document.documentElement.classList.toggle('dark', e.target.checked); if (this.themeChangeTimeout !== null) { clearTimeout(this.themeChangeTimeout); } @@ -171,12 +174,6 @@ class SettingsPopupPreview { } } -SettingsPopupPreview._messageHandlers = new Map([ - ['setText', (self, {text}) => self.setText(text)], - ['setCustomCss', (self, {css}) => self.setCustomCss(css)], - ['setCustomOuterCss', (self, {css}) => self.setCustomOuterCss(css)] -]); - SettingsPopupPreview.instance = SettingsPopupPreview.create(); diff --git a/ext/bg/js/settings/profiles.js b/ext/bg/js/settings/profiles.js index 3e589809..f946a33a 100644 --- a/ext/bg/js/settings/profiles.js +++ b/ext/bg/js/settings/profiles.js @@ -39,16 +39,16 @@ async function profileOptionsSetup() { } function profileOptionsSetupEventListeners() { - $('#profile-target').change((e) => onTargetProfileChanged(e)); - $('#profile-name').change((e) => onProfileNameChanged(e)); - $('#profile-add').click((e) => onProfileAdd(e)); - $('#profile-remove').click((e) => onProfileRemove(e)); - $('#profile-remove-confirm').click((e) => onProfileRemoveConfirm(e)); - $('#profile-copy').click((e) => onProfileCopy(e)); - $('#profile-copy-confirm').click((e) => onProfileCopyConfirm(e)); + $('#profile-target').change(onTargetProfileChanged); + $('#profile-name').change(onProfileNameChanged); + $('#profile-add').click(onProfileAdd); + $('#profile-remove').click(onProfileRemove); + $('#profile-remove-confirm').click(onProfileRemoveConfirm); + $('#profile-copy').click(onProfileCopy); + $('#profile-copy-confirm').click(onProfileCopyConfirm); $('#profile-move-up').click(() => onProfileMove(-1)); $('#profile-move-down').click(() => onProfileMove(1)); - $('.profile-form').find('input, select, textarea').not('.profile-form-manual').change((e) => onProfileOptionsChanged(e)); + $('.profile-form').find('input, select, textarea').not('.profile-form-manual').change(onProfileOptionsChanged); } function tryGetIntegerValue(selector, min, max) { diff --git a/ext/bg/js/settings/storage.js b/ext/bg/js/settings/storage.js index cbe1bb4d..8978414e 100644 --- a/ext/bg/js/settings/storage.js +++ b/ext/bg/js/settings/storage.js @@ -57,7 +57,7 @@ async function storageInfoInitialize() { await storageShowInfo(); - document.querySelector('#storage-refresh').addEventListener('click', () => storageShowInfo(), false); + document.querySelector('#storage-refresh').addEventListener('click', storageShowInfo, false); } async function storageUpdateStats() { diff --git a/ext/fg/float.html b/ext/fg/float.html index 082755f5..352a866a 100644 --- a/ext/fg/float.html +++ b/ext/fg/float.html @@ -51,6 +51,7 @@ <script src="/mixed/js/display.js"></script> <script src="/mixed/js/display-generator.js"></script> <script src="/mixed/js/scroll.js"></script> + <script src="/mixed/js/template-handler.js"></script> <script src="/fg/js/float.js"></script> diff --git a/ext/fg/js/document.js b/ext/fg/js/document.js index ea9ac965..35861475 100644 --- a/ext/fg/js/document.js +++ b/ext/fg/js/document.js @@ -50,7 +50,9 @@ function docImposterCreate(element, isTextarea) { const imposter = document.createElement('div'); const imposterStyle = imposter.style; - imposter.innerText = element.value; + let value = element.value; + if (value.endsWith('\n')) { value += '\n'; } + imposter.textContent = value; for (let i = 0, ii = elementStyle.length; i < ii; ++i) { const property = elementStyle[i]; diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 8f21a9c5..bc459d23 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -33,8 +33,27 @@ class DisplayFloat extends Display { this._messageToken = null; this._messageTokenPromise = null; - yomichan.on('orphaned', () => this.onOrphaned()); - window.addEventListener('message', (e) => this.onMessage(e), false); + this._onKeyDownHandlers = new Map([ + ['C', (e) => { + if (e.ctrlKey && !window.getSelection().toString()) { + this.onSelectionCopy(); + return true; + } + return false; + }], + ...this._onKeyDownHandlers + ]); + + this._windowMessageHandlers = new Map([ + ['setContent', ({type, details}) => this.setContent(type, details)], + ['clearAutoPlayTimer', () => this.clearAutoPlayTimer()], + ['setCustomCss', ({css}) => this.setCustomCss(css)], + ['prepare', ({options, popupInfo, url, childrenSupported, scale, uniqueId}) => this.prepare(options, popupInfo, url, childrenSupported, scale, uniqueId)], + ['setContentScale', ({scale}) => this.setContentScale(scale)] + ]); + + yomichan.on('orphaned', this.onOrphaned.bind(this)); + window.addEventListener('message', this.onMessage.bind(this), false); } async prepare(options, popupInfo, url, childrenSupported, scale, uniqueId) { @@ -96,18 +115,6 @@ class DisplayFloat extends Display { } } - onKeyDown(e) { - const key = Display.getKeyFromEvent(e); - const handler = DisplayFloat._onKeyDownHandlers.get(key); - if (typeof handler === 'function') { - if (handler(this, e)) { - e.preventDefault(); - return true; - } - } - return super.onKeyDown(e); - } - async getMessageToken() { // this._messageTokenPromise is used to ensure that only one call to apiGetMessageToken is made. if (this._messageTokenPromise === null) { @@ -126,10 +133,10 @@ class DisplayFloat extends Display { return; } - const handler = DisplayFloat._messageHandlers.get(action); + const handler = this._windowMessageHandlers.get(action); if (typeof handler !== 'function') { return; } - handler(this, params); + handler(params); } getOptionsContext() { @@ -153,22 +160,4 @@ class DisplayFloat extends Display { } } -DisplayFloat._onKeyDownHandlers = new Map([ - ['C', (self, e) => { - if (e.ctrlKey && !window.getSelection().toString()) { - self.onSelectionCopy(); - return true; - } - return false; - }] -]); - -DisplayFloat._messageHandlers = new Map([ - ['setContent', (self, {type, details}) => self.setContent(type, details)], - ['clearAutoPlayTimer', (self) => self.clearAutoPlayTimer()], - ['setCustomCss', (self, {css}) => self.setCustomCss(css)], - ['prepare', (self, {options, popupInfo, url, childrenSupported, scale, uniqueId}) => self.prepare(options, popupInfo, url, childrenSupported, scale, uniqueId)], - ['setContentScale', (self, {scale}) => self.setContentScale(scale)] -]); - DisplayFloat.instance = new DisplayFloat(); diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 67045241..929ab56a 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -39,6 +39,15 @@ class Frontend extends TextScanner { this._contentScale = 1.0; this._orphaned = true; this._lastShowPromise = Promise.resolve(); + + this._windowMessageHandlers = new Map([ + ['popupClose', () => this.onSearchClear(true)], + ['selectionCopy', () => document.execCommand('copy')] + ]); + + this._runtimeMessageHandlers = new Map([ + ['popupSetVisibleOverride', ({visible}) => { this.popup.setVisibleOverride(visible); }] + ]); } async prepare() { @@ -55,9 +64,9 @@ class Frontend extends TextScanner { window.visualViewport.addEventListener('resize', this.onVisualViewportResize.bind(this)); } - yomichan.on('orphaned', () => this.onOrphaned()); - yomichan.on('optionsUpdated', () => this.updateOptions()); - yomichan.on('zoomChanged', (e) => this.onZoomChanged(e)); + yomichan.on('orphaned', this.onOrphaned.bind(this)); + yomichan.on('optionsUpdated', this.updateOptions.bind(this)); + yomichan.on('zoomChanged', this.onZoomChanged.bind(this)); chrome.runtime.onMessage.addListener(this.onRuntimeMessage.bind(this)); this._updateContentScale(); @@ -72,17 +81,17 @@ class Frontend extends TextScanner { onWindowMessage(e) { const action = e.data; - const handler = Frontend._windowMessageHandlers.get(action); + const handler = this._windowMessageHandlers.get(action); if (typeof handler !== 'function') { return false; } - handler(this); + handler(); } onRuntimeMessage({action, params}, sender, callback) { - const handler = Frontend._runtimeMessageHandlers.get(action); + const handler = this._runtimeMessageHandlers.get(action); if (typeof handler !== 'function') { return false; } - const result = handler(this, params, sender); + const result = handler(params, sender); callback(result); return false; } @@ -237,12 +246,3 @@ class Frontend extends TextScanner { return visualViewport !== null && typeof visualViewport === 'object' ? visualViewport.scale : 1.0; } } - -Frontend._windowMessageHandlers = new Map([ - ['popupClose', (self) => self.onSearchClear(true)], - ['selectionCopy', () => document.execCommand('copy')] -]); - -Frontend._runtimeMessageHandlers = new Map([ - ['popupSetVisibleOverride', (self, {visible}) => { self.popup.setVisibleOverride(visible); }] -]); diff --git a/ext/fg/js/popup-proxy-host.js b/ext/fg/js/popup-proxy-host.js index e55801ff..bef2cb16 100644 --- a/ext/fg/js/popup-proxy-host.js +++ b/ext/fg/js/popup-proxy-host.js @@ -34,16 +34,16 @@ class PopupProxyHost { if (typeof frameId !== 'number') { return; } this._apiReceiver = new FrontendApiReceiver(`popup-proxy-host#${frameId}`, new Map([ - ['getOrCreatePopup', ({id, parentId}) => this._onApiGetOrCreatePopup(id, parentId)], - ['setOptions', ({id, options}) => this._onApiSetOptions(id, options)], - ['hide', ({id, changeFocus}) => this._onApiHide(id, changeFocus)], - ['isVisible', ({id}) => this._onApiIsVisibleAsync(id)], - ['setVisibleOverride', ({id, visible}) => this._onApiSetVisibleOverride(id, visible)], - ['containsPoint', ({id, x, y}) => this._onApiContainsPoint(id, x, y)], - ['showContent', ({id, elementRect, writingMode, type, details}) => this._onApiShowContent(id, elementRect, writingMode, type, details)], - ['setCustomCss', ({id, css}) => this._onApiSetCustomCss(id, css)], - ['clearAutoPlayTimer', ({id}) => this._onApiClearAutoPlayTimer(id)], - ['setContentScale', ({id, scale}) => this._onApiSetContentScale(id, scale)] + ['getOrCreatePopup', this._onApiGetOrCreatePopup.bind(this)], + ['setOptions', this._onApiSetOptions.bind(this)], + ['hide', this._onApiHide.bind(this)], + ['isVisible', this._onApiIsVisibleAsync.bind(this)], + ['setVisibleOverride', this._onApiSetVisibleOverride.bind(this)], + ['containsPoint', this._onApiContainsPoint.bind(this)], + ['showContent', this._onApiShowContent.bind(this)], + ['setCustomCss', this._onApiSetCustomCss.bind(this)], + ['clearAutoPlayTimer', this._onApiClearAutoPlayTimer.bind(this)], + ['setContentScale', this._onApiSetContentScale.bind(this)] ])); } @@ -87,56 +87,56 @@ class PopupProxyHost { // Message handlers - async _onApiGetOrCreatePopup(id, parentId) { + async _onApiGetOrCreatePopup({id, parentId}) { const popup = this.getOrCreatePopup(id, parentId); return { id: popup.id }; } - async _onApiSetOptions(id, options) { + async _onApiSetOptions({id, options}) { const popup = this._getPopup(id); return await popup.setOptions(options); } - async _onApiHide(id, changeFocus) { + async _onApiHide({id, changeFocus}) { const popup = this._getPopup(id); return popup.hide(changeFocus); } - async _onApiIsVisibleAsync(id) { + async _onApiIsVisibleAsync({id}) { const popup = this._getPopup(id); return await popup.isVisible(); } - async _onApiSetVisibleOverride(id, visible) { + async _onApiSetVisibleOverride({id, visible}) { const popup = this._getPopup(id); return await popup.setVisibleOverride(visible); } - async _onApiContainsPoint(id, x, y) { + async _onApiContainsPoint({id, x, y}) { const popup = this._getPopup(id); return await popup.containsPoint(x, y); } - async _onApiShowContent(id, elementRect, writingMode, type, details) { + async _onApiShowContent({id, elementRect, writingMode, type, details}) { const popup = this._getPopup(id); elementRect = PopupProxyHost._convertJsonRectToDOMRect(popup, elementRect); if (!PopupProxyHost._popupCanShow(popup)) { return; } return await popup.showContent(elementRect, writingMode, type, details); } - async _onApiSetCustomCss(id, css) { + async _onApiSetCustomCss({id, css}) { const popup = this._getPopup(id); return popup.setCustomCss(css); } - async _onApiClearAutoPlayTimer(id) { + async _onApiClearAutoPlayTimer({id}) { const popup = this._getPopup(id); return popup.clearAutoPlayTimer(); } - async _onApiSetContentScale(id, scale) { + async _onApiSetContentScale({id, scale}) { const popup = this._getPopup(id); return popup.setContentScale(scale); } diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index 4927f4bd..bc40a8c4 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -260,7 +260,7 @@ class Popup { 'mozfullscreenchange', 'webkitfullscreenchange' ]; - const onFullscreenChanged = () => this._onFullscreenChanged(); + const onFullscreenChanged = this._onFullscreenChanged.bind(this); for (const eventName of fullscreenEvents) { this._fullscreenEventListeners.addEventListener(document, eventName, onFullscreenChanged, false); } diff --git a/ext/fg/js/source.js b/ext/fg/js/source.js index fa785ec4..6dc482bd 100644 --- a/ext/fg/js/source.js +++ b/ext/fg/js/source.js @@ -366,7 +366,7 @@ class TextSourceElement { setEndOffset(length) { switch (this.element.nodeName.toUpperCase()) { case 'BUTTON': - this.content = this.element.innerHTML; + this.content = this.element.textContent; break; case 'IMG': this.content = this.element.getAttribute('alt'); diff --git a/ext/manifest.json b/ext/manifest.json index b86459f9..09e89729 100644 --- a/ext/manifest.json +++ b/ext/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "Yomichan", - "version": "20.1.26.0", + "version": "20.2.24.0", "description": "Japanese dictionary with Anki integration", "icons": {"16": "mixed/img/icon16.png", "48": "mixed/img/icon48.png", "128": "mixed/img/icon128.png"}, diff --git a/ext/mixed/css/display.css b/ext/mixed/css/display.css index 6a5383bc..c4758235 100644 --- a/ext/mixed/css/display.css +++ b/ext/mixed/css/display.css @@ -30,8 +30,8 @@ * General */ -html:root[data-yomichan-page=float]:not([data-yomichan-theme]), -html:root[data-yomichan-page=float]:not([data-yomichan-theme]) body { +:root[data-yomichan-page=float]:not([data-yomichan-theme]), +:root[data-yomichan-page=float]:not([data-yomichan-theme]) body { background-color: transparent; } @@ -65,10 +65,6 @@ ol, ul { height: 2.28571428em; /* 14px => 32px */ } -.invisible { - visibility: hidden; -} - /* * Navigation */ @@ -82,17 +78,18 @@ ol, ul { padding: 0.25em 0.5em; border-bottom-width: 0.07142857em; /* 14px => 1px */ border-bottom-style: solid; + z-index: 10; } -html:root[data-yomichan-page=search] .navigation-header { +:root[data-yomichan-page=search] .navigation-header { position: sticky; } -html:root[data-yomichan-page=float] .navigation-header { +:root[data-yomichan-page=float] .navigation-header { position: fixed; } -html:root[data-yomichan-page=float] .navigation-header:not([hidden])~.navigation-header-spacer { +:root[data-yomichan-page=float] .navigation-header:not([hidden])~.navigation-header-spacer { height: 2.1em; } @@ -136,7 +133,7 @@ html:root[data-yomichan-page=float] .navigation-header:not([hidden])~.navigation margin-right: 0.2em; } -html:root[data-yomichan-page=search][data-search-mode=popup] .search-input { +:root[data-yomichan-page=search][data-search-mode=popup] .search-input { display: none; } @@ -150,7 +147,7 @@ html:root[data-yomichan-page=search][data-search-mode=popup] .search-input { padding-bottom: 0.72em; } -html:root[data-yomichan-page=float] .entry { +:root[data-yomichan-page=float] .entry { padding-left: 0.72em; padding-right: 0.72em; } @@ -231,7 +228,7 @@ button.action-button { margin-right: 0.375em; } -html:root:not([data-enable-search-tags=true]) .tag[data-category=search] { +:root:not([data-enable-search-tags=true]) .tag[data-category=search] { display: none; } diff --git a/ext/mixed/js/display-generator.js b/ext/mixed/js/display-generator.js index 46f3d17e..d7e77cc0 100644 --- a/ext/mixed/js/display-generator.js +++ b/ext/mixed/js/display-generator.js @@ -16,36 +16,20 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -/*global apiGetDisplayTemplatesHtml*/ +/*global apiGetDisplayTemplatesHtml, TemplateHandler*/ class DisplayGenerator { constructor() { - this._termEntryTemplate = null; - this._termExpressionTemplate = null; - this._termDefinitionItemTemplate = null; - this._termDefinitionOnlyTemplate = null; - this._termGlossaryItemTemplate = null; - this._termReasonTemplate = null; - - this._kanjiEntryTemplate = null; - this._kanjiInfoTableTemplate = null; - this._kanjiInfoTableItemTemplate = null; - this._kanjiInfoTableEmptyTemplate = null; - this._kanjiGlossaryItemTemplate = null; - this._kanjiReadingTemplate = null; - - this._tagTemplate = null; - this._tagFrequencyTemplate = null; + this._templateHandler = null; } async prepare() { const html = await apiGetDisplayTemplatesHtml(); - const doc = new DOMParser().parseFromString(html, 'text/html'); - this._setTemplates(doc); + this._templateHandler = new TemplateHandler(html); } createTermEntry(details) { - const node = DisplayGenerator._instantiateTemplate(this._termEntryTemplate); + const node = this._templateHandler.instantiate('term-entry'); const expressionsContainer = node.querySelector('.term-expression-list'); const reasonsContainer = node.querySelector('.term-reasons'); @@ -78,7 +62,7 @@ class DisplayGenerator { } createTermExpression([details, termTags]) { - const node = DisplayGenerator._instantiateTemplate(this._termExpressionTemplate); + const node = this._templateHandler.instantiate('term-expression'); const expressionContainer = node.querySelector('.term-expression-text'); const tagContainer = node.querySelector('.tags'); @@ -112,7 +96,7 @@ class DisplayGenerator { } createTermReason(reason) { - const fragment = DisplayGenerator._instantiateTemplateFragment(this._termReasonTemplate); + const fragment = this._templateHandler.instantiateFragment('term-reason'); const node = fragment.querySelector('.term-reason'); node.textContent = reason; node.dataset.reason = reason; @@ -120,7 +104,7 @@ class DisplayGenerator { } createTermDefinitionItem(details) { - const node = DisplayGenerator._instantiateTemplate(this._termDefinitionItemTemplate); + const node = this._templateHandler.instantiate('term-definition-item'); const tagListContainer = node.querySelector('.term-definition-tag-list'); const onlyListContainer = node.querySelector('.term-definition-only-list'); @@ -136,7 +120,7 @@ class DisplayGenerator { } createTermGlossaryItem(glossary) { - const node = DisplayGenerator._instantiateTemplate(this._termGlossaryItemTemplate); + const node = this._templateHandler.instantiate('term-glossary-item'); const container = node.querySelector('.term-glossary'); if (container !== null) { DisplayGenerator._appendMultilineText(container, glossary); @@ -145,7 +129,7 @@ class DisplayGenerator { } createTermOnly(only) { - const node = DisplayGenerator._instantiateTemplate(this._termDefinitionOnlyTemplate); + const node = this._templateHandler.instantiate('term-definition-only'); node.dataset.only = only; node.textContent = only; return node; @@ -160,7 +144,7 @@ class DisplayGenerator { } createKanjiEntry(details) { - const node = DisplayGenerator._instantiateTemplate(this._kanjiEntryTemplate); + const node = this._templateHandler.instantiate('kanji-entry'); const glyphContainer = node.querySelector('.kanji-glyph'); const frequenciesContainer = node.querySelector('.frequencies'); @@ -205,7 +189,7 @@ class DisplayGenerator { } createKanjiGlossaryItem(glossary) { - const node = DisplayGenerator._instantiateTemplate(this._kanjiGlossaryItemTemplate); + const node = this._templateHandler.instantiate('kanji-glossary-item'); const container = node.querySelector('.kanji-glossary'); if (container !== null) { DisplayGenerator._appendMultilineText(container, glossary); @@ -214,13 +198,13 @@ class DisplayGenerator { } createKanjiReading(reading) { - const node = DisplayGenerator._instantiateTemplate(this._kanjiReadingTemplate); + const node = this._templateHandler.instantiate('kanji-reading'); node.textContent = reading; return node; } createKanjiInfoTable(details) { - const node = DisplayGenerator._instantiateTemplate(this._kanjiInfoTableTemplate); + const node = this._templateHandler.instantiate('kanji-info-table'); const container = node.querySelector('.kanji-info-table-body'); @@ -236,7 +220,7 @@ class DisplayGenerator { } createKanjiInfoTableItem(details) { - const node = DisplayGenerator._instantiateTemplate(this._kanjiInfoTableItemTemplate); + const node = this._templateHandler.instantiate('kanji-info-table-item'); const nameNode = node.querySelector('.kanji-info-table-item-header'); const valueNode = node.querySelector('.kanji-info-table-item-value'); if (nameNode !== null) { @@ -249,11 +233,11 @@ class DisplayGenerator { } createKanjiInfoTableItemEmpty() { - return DisplayGenerator._instantiateTemplate(this._kanjiInfoTableEmptyTemplate); + return this._templateHandler.instantiate('kanji-info-table-empty'); } createTag(details) { - const node = DisplayGenerator._instantiateTemplate(this._tagTemplate); + const node = this._templateHandler.instantiate('tag'); const inner = node.querySelector('.tag-inner'); @@ -265,7 +249,7 @@ class DisplayGenerator { } createSearchTag(details) { - const node = DisplayGenerator._instantiateTemplate(this._tagSearchTemplate); + const node = this._templateHandler.instantiate('tag-search'); node.textContent = details.query; @@ -275,7 +259,7 @@ class DisplayGenerator { } createFrequencyTag(details) { - const node = DisplayGenerator._instantiateTemplate(this._tagFrequencyTemplate); + const node = this._templateHandler.instantiate('tag-frequency'); let n = node.querySelector('.term-frequency-dictionary-name'); if (n !== null) { @@ -293,26 +277,6 @@ class DisplayGenerator { return node; } - _setTemplates(doc) { - this._termEntryTemplate = doc.querySelector('#term-entry-template'); - this._termExpressionTemplate = doc.querySelector('#term-expression-template'); - this._termDefinitionItemTemplate = doc.querySelector('#term-definition-item-template'); - this._termDefinitionOnlyTemplate = doc.querySelector('#term-definition-only-template'); - this._termGlossaryItemTemplate = doc.querySelector('#term-glossary-item-template'); - this._termReasonTemplate = doc.querySelector('#term-reason-template'); - - this._kanjiEntryTemplate = doc.querySelector('#kanji-entry-template'); - this._kanjiInfoTableTemplate = doc.querySelector('#kanji-info-table-template'); - this._kanjiInfoTableItemTemplate = doc.querySelector('#kanji-info-table-item-template'); - this._kanjiInfoTableEmptyTemplate = doc.querySelector('#kanji-info-table-empty-template'); - this._kanjiGlossaryItemTemplate = doc.querySelector('#kanji-glossary-item-template'); - this._kanjiReadingTemplate = doc.querySelector('#kanji-reading-template'); - - this._tagTemplate = doc.querySelector('#tag-template'); - this._tagSearchTemplate = doc.querySelector('#tag-search-template'); - this._tagFrequencyTemplate = doc.querySelector('#tag-frequency-template'); - } - _appendKanjiLinks(container, text) { let part = ''; for (const c of text) { @@ -382,12 +346,4 @@ class DisplayGenerator { container.appendChild(document.createTextNode(parts[i])); } } - - static _instantiateTemplate(template) { - return document.importNode(template.content.firstChild, true); - } - - static _instantiateTemplateFragment(template) { - return document.importNode(template.content, true); - } } diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 8113260c..631f9e34 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -45,6 +45,110 @@ class Display { this.displayGenerator = new DisplayGenerator(); this.windowScroll = new WindowScroll(); + this._onKeyDownHandlers = new Map([ + ['Escape', () => { + this.onSearchClear(); + return true; + }], + ['PageUp', (e) => { + if (e.altKey) { + this.entryScrollIntoView(this.index - 3, null, true); + return true; + } + return false; + }], + ['PageDown', (e) => { + if (e.altKey) { + this.entryScrollIntoView(this.index + 3, null, true); + return true; + } + return false; + }], + ['End', (e) => { + if (e.altKey) { + this.entryScrollIntoView(this.definitions.length - 1, null, true); + return true; + } + return false; + }], + ['Home', (e) => { + if (e.altKey) { + this.entryScrollIntoView(0, null, true); + return true; + } + return false; + }], + ['ArrowUp', (e) => { + if (e.altKey) { + this.entryScrollIntoView(this.index - 1, null, true); + return true; + } + return false; + }], + ['ArrowDown', (e) => { + if (e.altKey) { + this.entryScrollIntoView(this.index + 1, null, true); + return true; + } + return false; + }], + ['B', (e) => { + if (e.altKey) { + this.sourceTermView(); + return true; + } + return false; + }], + ['F', (e) => { + if (e.altKey) { + this.nextTermView(); + return true; + } + return false; + }], + ['E', (e) => { + if (e.altKey) { + this.noteTryAdd('term-kanji'); + return true; + } + return false; + }], + ['K', (e) => { + if (e.altKey) { + this.noteTryAdd('kanji'); + return true; + } + return false; + }], + ['R', (e) => { + if (e.altKey) { + this.noteTryAdd('term-kana'); + return true; + } + return false; + }], + ['P', (e) => { + if (e.altKey) { + const index = this.index; + if (index < 0 || index >= this.definitions.length) { return; } + + const entry = this.getEntry(index); + if (entry !== null && entry.dataset.type === 'term') { + this.audioPlay(this.definitions[index], this.firstExpressionIndex, index); + } + return true; + } + return false; + }], + ['V', (e) => { + if (e.altKey) { + this.noteTryView(); + return true; + } + return false; + }] + ]); + this.setInteractive(true); } @@ -215,9 +319,9 @@ class Display { onKeyDown(e) { const key = Display.getKeyFromEvent(e); - const handler = Display._onKeyDownHandlers.get(key); + const handler = this._onKeyDownHandlers.get(key); if (typeof handler === 'function') { - if (handler(this, e)) { + if (handler(e)) { e.preventDefault(); return true; } @@ -303,7 +407,7 @@ class Display { if (interactive) { const actionPrevious = document.querySelector('.action-previous'); const actionNext = document.querySelector('.action-next'); - const navigationHeader = document.querySelector('.navigation-header'); + // const navigationHeader = document.querySelector('.navigation-header'); this.persistentEventListeners.addEventListener(document, 'keydown', this.onKeyDown.bind(this), false); this.persistentEventListeners.addEventListener(document, 'wheel', this.onWheel.bind(this), {passive: false}); @@ -313,9 +417,10 @@ class Display { if (actionNext !== null) { this.persistentEventListeners.addEventListener(actionNext, 'click', this.onNextTermView.bind(this)); } - if (navigationHeader !== null) { - this.persistentEventListeners.addEventListener(navigationHeader, 'wheel', this.onHistoryWheel.bind(this), {passive: false}); - } + // temporarily disabled + // if (navigationHeader !== null) { + // this.persistentEventListeners.addEventListener(navigationHeader, 'wheel', this.onHistoryWheel.bind(this), {passive: false}); + // } } else { this.persistentEventListeners.removeAllEventListeners(); } @@ -519,15 +624,13 @@ class Display { updateAdderButtons(states) { for (let i = 0; i < states.length; ++i) { - const state = states[i]; let noteId = null; - for (const mode in state) { + for (const [mode, info] of Object.entries(states[i])) { const button = this.adderButtonFind(i, mode); if (button === null) { continue; } - const info = state[mode]; if (!info.canAdd && noteId === null && info.noteId) { noteId = info.noteId; } @@ -634,7 +737,7 @@ class Display { this.setSpinnerVisible(true); const context = {}; - if (this.noteUsesScreenshot()) { + if (this.noteUsesScreenshot(mode)) { const screenshot = await this.getScreenshot(); if (screenshot) { context.screenshot = screenshot; @@ -704,10 +807,11 @@ class Display { } } - noteUsesScreenshot() { - const fields = this.options.anki.terms.fields; - for (const name in fields) { - if (fields[name].includes('{screenshot}')) { + noteUsesScreenshot(mode) { + const optionsAnki = this.options.anki; + const fields = (mode === 'kanji' ? optionsAnki.kanji : optionsAnki.terms).fields; + for (const fieldValue of Object.values(fields)) { + if (fieldValue.includes('{screenshot}')) { return true; } } @@ -814,120 +918,3 @@ class Display { return (typeof key === 'string' ? (key.length === 1 ? key.toUpperCase() : key) : ''); } } - -Display._onKeyDownHandlers = new Map([ - ['Escape', (self) => { - self.onSearchClear(); - return true; - }], - - ['PageUp', (self, e) => { - if (e.altKey) { - self.entryScrollIntoView(self.index - 3, null, true); - return true; - } - return false; - }], - - ['PageDown', (self, e) => { - if (e.altKey) { - self.entryScrollIntoView(self.index + 3, null, true); - return true; - } - return false; - }], - - ['End', (self, e) => { - if (e.altKey) { - self.entryScrollIntoView(self.definitions.length - 1, null, true); - return true; - } - return false; - }], - - ['Home', (self, e) => { - if (e.altKey) { - self.entryScrollIntoView(0, null, true); - return true; - } - return false; - }], - - ['ArrowUp', (self, e) => { - if (e.altKey) { - self.entryScrollIntoView(self.index - 1, null, true); - return true; - } - return false; - }], - - ['ArrowDown', (self, e) => { - if (e.altKey) { - self.entryScrollIntoView(self.index + 1, null, true); - return true; - } - return false; - }], - - ['B', (self, e) => { - if (e.altKey) { - self.sourceTermView(); - return true; - } - return false; - }], - - ['F', (self, e) => { - if (e.altKey) { - self.nextTermView(); - return true; - } - return false; - }], - - ['E', (self, e) => { - if (e.altKey) { - self.noteTryAdd('term-kanji'); - return true; - } - return false; - }], - - ['K', (self, e) => { - if (e.altKey) { - self.noteTryAdd('kanji'); - return true; - } - return false; - }], - - ['R', (self, e) => { - if (e.altKey) { - self.noteTryAdd('term-kana'); - return true; - } - return false; - }], - - ['P', (self, e) => { - if (e.altKey) { - const index = self.index; - if (index < 0 || index >= self.definitions.length) { return; } - - const entry = self.getEntry(index); - if (entry !== null && entry.dataset.type === 'term') { - self.audioPlay(self.definitions[index], self.firstExpressionIndex, index); - } - return true; - } - return false; - }], - - ['V', (self, e) => { - if (e.altKey) { - self.noteTryView(); - return true; - } - return false; - }] -]); diff --git a/ext/mixed/js/scroll.js b/ext/mixed/js/scroll.js index 5829d294..72da8b65 100644 --- a/ext/mixed/js/scroll.js +++ b/ext/mixed/js/scroll.js @@ -26,7 +26,7 @@ class WindowScroll { this.animationEndTime = 0; this.animationEndX = 0; this.animationEndY = 0; - this.requestAnimationFrameCallback = (t) => this.onAnimationFrame(t); + this.requestAnimationFrameCallback = this.onAnimationFrame.bind(this); } toY(y) { diff --git a/package.json b/package.json index 04bfd4af..74d89dda 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,10 @@ "url": "git+https://github.com/FooSoft/yomichan.git" }, "author": "FooSoft", + "license": "GPL-3.0-or-later", "licenses": [ { - "type": "GPLv3", + "type": "GPL-3.0-or-later", "url": "https://www.gnu.org/licenses/gpl-3.0.html" } ], diff --git a/test/dictionary-validate.js b/test/dictionary-validate.js index 25a5de88..14eee2ed 100644 --- a/test/dictionary-validate.js +++ b/test/dictionary-validate.js @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2020 Alex Yatskov <alex@foosoft.net> + * Author: Alex Yatskov <alex@foosoft.net> + * + * 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 <https://www.gnu.org/licenses/>. + */ + const fs = require('fs'); const path = require('path'); const yomichanTest = require('./yomichan-test'); diff --git a/test/schema-validate.js b/test/schema-validate.js index 1271a611..a4f2d94c 100644 --- a/test/schema-validate.js +++ b/test/schema-validate.js @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2020 Alex Yatskov <alex@foosoft.net> + * Author: Alex Yatskov <alex@foosoft.net> + * + * 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 <https://www.gnu.org/licenses/>. + */ + const fs = require('fs'); const yomichanTest = require('./yomichan-test'); diff --git a/test/test-database.js b/test/test-database.js index 44f409dd..35f22523 100644 --- a/test/test-database.js +++ b/test/test-database.js @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2020 Alex Yatskov <alex@foosoft.net> + * Author: Alex Yatskov <alex@foosoft.net> + * + * 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 <https://www.gnu.org/licenses/>. + */ + const fs = require('fs'); const url = require('url'); const path = require('path'); @@ -72,7 +90,7 @@ class XMLHttpRequest { const {JsonSchema} = yomichanTest.requireScript('ext/bg/js/json-schema.js', ['JsonSchema']); const {dictFieldSplit, dictTagSanitize} = yomichanTest.requireScript('ext/bg/js/dictionary.js', ['dictFieldSplit', 'dictTagSanitize']); -const {stringReverse, hasOwn} = yomichanTest.requireScript('ext/mixed/js/core.js', ['stringReverse', 'hasOwn'], {chrome}); +const {stringReverse} = yomichanTest.requireScript('ext/mixed/js/core.js', ['stringReverse'], {chrome}); const {requestJson} = yomichanTest.requireScript('ext/bg/js/request.js', ['requestJson'], {XMLHttpRequest}); const databaseGlobals = { @@ -80,7 +98,6 @@ const databaseGlobals = { JsonSchema, requestJson, stringReverse, - hasOwn, dictFieldSplit, dictTagSanitize, indexedDB: global.indexedDB, @@ -109,7 +126,8 @@ function countKanjiWithCharacter(kanji, character) { function clearDatabase(timeout) { return new Promise((resolve, reject) => { - const timer = setTimeout(() => { + let timer = setTimeout(() => { + timer = null; reject(new Error(`clearDatabase failed to resolve after ${timeout}ms`)); }, timeout); @@ -122,7 +140,9 @@ function clearDatabase(timeout) { request.onsuccess = () => resolve2(); }); } - clearTimeout(timer); + if (timer !== null) { + clearTimeout(timer); + } resolve(); })(); }); diff --git a/test/test-dictionary.js b/test/test-dictionary.js index b157dd5d..74f9e62b 100644 --- a/test/test-dictionary.js +++ b/test/test-dictionary.js @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2020 Alex Yatskov <alex@foosoft.net> + * Author: Alex Yatskov <alex@foosoft.net> + * + * 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 <https://www.gnu.org/licenses/>. + */ + const yomichanTest = require('./yomichan-test'); const dictionaryValidate = require('./dictionary-validate'); diff --git a/test/test-schema.js b/test/test-schema.js index 8ca63167..f4612f86 100644 --- a/test/test-schema.js +++ b/test/test-schema.js @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2020 Alex Yatskov <alex@foosoft.net> + * Author: Alex Yatskov <alex@foosoft.net> + * + * 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 <https://www.gnu.org/licenses/>. + */ + const assert = require('assert'); const yomichanTest = require('./yomichan-test'); diff --git a/test/yomichan-test.js b/test/yomichan-test.js index 939e0ad2..78bfb9c6 100644 --- a/test/yomichan-test.js +++ b/test/yomichan-test.js @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2020 Alex Yatskov <alex@foosoft.net> + * Author: Alex Yatskov <alex@foosoft.net> + * + * 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 <https://www.gnu.org/licenses/>. + */ + const fs = require('fs'); const path = require('path'); |