aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.eslintrc.json3
-rw-r--r--ext/bg/js/audio.js6
-rw-r--r--ext/bg/js/backend.js107
-rw-r--r--ext/bg/js/deinflector.js4
-rw-r--r--ext/bg/js/dictionary.js73
-rw-r--r--ext/bg/js/search-query-parser-generator.js6
-rw-r--r--ext/bg/js/search-query-parser.js6
-rw-r--r--ext/bg/js/search.js163
-rw-r--r--ext/bg/js/settings/anki-templates.js8
-rw-r--r--ext/bg/js/settings/anki.js6
-rw-r--r--ext/bg/js/settings/audio-ui.js6
-rw-r--r--ext/bg/js/settings/audio.js8
-rw-r--r--ext/bg/js/settings/conditions-ui.js30
-rw-r--r--ext/bg/js/settings/dictionaries.js26
-rw-r--r--ext/bg/js/settings/main.js4
-rw-r--r--ext/bg/js/settings/popup-preview-frame.js31
-rw-r--r--ext/bg/js/settings/profiles.js16
-rw-r--r--ext/bg/js/settings/storage.js2
-rw-r--r--ext/fg/float.html1
-rw-r--r--ext/fg/js/document.js4
-rw-r--r--ext/fg/js/float.js57
-rw-r--r--ext/fg/js/frontend.js32
-rw-r--r--ext/fg/js/popup-proxy-host.js40
-rw-r--r--ext/fg/js/popup.js2
-rw-r--r--ext/fg/js/source.js2
-rw-r--r--ext/manifest.json2
-rw-r--r--ext/mixed/css/display.css21
-rw-r--r--ext/mixed/js/display-generator.js80
-rw-r--r--ext/mixed/js/display.js249
-rw-r--r--ext/mixed/js/scroll.js2
-rw-r--r--package.json3
-rw-r--r--test/dictionary-validate.js18
-rw-r--r--test/schema-validate.js18
-rw-r--r--test/test-database.js28
-rw-r--r--test/test-dictionary.js18
-rw-r--r--test/test-schema.js18
-rw-r--r--test/yomichan-test.js18
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');